1use std::borrow::ToOwned;
8use std::cmp::min;
9use std::default::Default;
10use std::ops::{Add, AddAssign, Range};
11
12use bitflags::bitflags;
13use keyboard_types::{Key, KeyState, Modifiers, NamedKey, ShortcutMatcher};
14use script_bindings::match_domstring_ascii;
15use unicode_segmentation::UnicodeSegmentation;
16
17use crate::clipboard_provider::ClipboardProvider;
18use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
19use crate::dom::bindings::inheritance::Castable;
20use crate::dom::bindings::refcounted::Trusted;
21use crate::dom::bindings::reflector::DomGlobal;
22use crate::dom::bindings::str::DOMString;
23use crate::dom::compositionevent::CompositionEvent;
24use crate::dom::event::Event;
25use crate::dom::eventtarget::EventTarget;
26use crate::dom::inputevent::InputEvent;
27use crate::dom::keyboardevent::KeyboardEvent;
28use crate::dom::types::ClipboardEvent;
29use crate::drag_data_store::Kind;
30use crate::script_runtime::CanGc;
31
32#[derive(Clone, Copy, PartialEq)]
33pub enum Selection {
34 Selected,
35 NotSelected,
36}
37
38#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
39pub enum SelectionDirection {
40 Forward,
41 Backward,
42 None,
43}
44
45#[derive(Clone, Copy, Debug, Eq, JSTraceable, MallocSizeOf, Ord, PartialEq, PartialOrd)]
46pub struct UTF8Bytes(pub usize);
47
48impl UTF8Bytes {
49 pub fn zero() -> UTF8Bytes {
50 UTF8Bytes(0)
51 }
52
53 pub fn one() -> UTF8Bytes {
54 UTF8Bytes(1)
55 }
56
57 pub(crate) fn unwrap_range(byte_range: Range<UTF8Bytes>) -> Range<usize> {
58 byte_range.start.0..byte_range.end.0
59 }
60
61 pub(crate) fn saturating_sub(self, other: UTF8Bytes) -> UTF8Bytes {
62 if self > other {
63 UTF8Bytes(self.0 - other.0)
64 } else {
65 UTF8Bytes::zero()
66 }
67 }
68}
69
70impl Add for UTF8Bytes {
71 type Output = UTF8Bytes;
72
73 fn add(self, other: UTF8Bytes) -> UTF8Bytes {
74 UTF8Bytes(self.0 + other.0)
75 }
76}
77
78impl AddAssign for UTF8Bytes {
79 fn add_assign(&mut self, other: UTF8Bytes) {
80 *self = UTF8Bytes(self.0 + other.0)
81 }
82}
83
84trait StrExt {
85 fn len_utf8(&self) -> UTF8Bytes;
86}
87impl StrExt for DOMString {
88 fn len_utf8(&self) -> UTF8Bytes {
89 UTF8Bytes(self.len())
90 }
91}
92
93impl StrExt for str {
94 fn len_utf8(&self) -> UTF8Bytes {
95 UTF8Bytes(self.len())
96 }
97}
98
99#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq, PartialOrd)]
100pub struct UTF16CodeUnits(pub usize);
101
102impl UTF16CodeUnits {
103 pub fn zero() -> UTF16CodeUnits {
104 UTF16CodeUnits(0)
105 }
106
107 pub fn one() -> UTF16CodeUnits {
108 UTF16CodeUnits(1)
109 }
110
111 pub(crate) fn saturating_sub(self, other: UTF16CodeUnits) -> UTF16CodeUnits {
112 if self > other {
113 UTF16CodeUnits(self.0 - other.0)
114 } else {
115 UTF16CodeUnits::zero()
116 }
117 }
118}
119
120impl Add for UTF16CodeUnits {
121 type Output = UTF16CodeUnits;
122
123 fn add(self, other: UTF16CodeUnits) -> UTF16CodeUnits {
124 UTF16CodeUnits(self.0 + other.0)
125 }
126}
127
128impl AddAssign for UTF16CodeUnits {
129 fn add_assign(&mut self, other: UTF16CodeUnits) {
130 *self = UTF16CodeUnits(self.0 + other.0)
131 }
132}
133
134impl From<DOMString> for SelectionDirection {
135 fn from(direction: DOMString) -> SelectionDirection {
136 match_domstring_ascii!(direction,
137 "forward" => SelectionDirection::Forward,
138 "backward" => SelectionDirection::Backward,
139 _ => SelectionDirection::None,
140 )
141 }
142}
143
144impl From<SelectionDirection> for DOMString {
145 fn from(direction: SelectionDirection) -> DOMString {
146 match direction {
147 SelectionDirection::Forward => DOMString::from("forward"),
148 SelectionDirection::Backward => DOMString::from("backward"),
149 SelectionDirection::None => DOMString::from("none"),
150 }
151 }
152}
153
154#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq, PartialOrd)]
155pub struct TextPoint {
156 pub line: usize,
158 pub index: UTF8Bytes,
160}
161
162impl TextPoint {
163 fn constrain_to(&self, lines: &[DOMString]) -> TextPoint {
165 let line = min(self.line, lines.len() - 1);
166
167 TextPoint {
168 line,
169 index: min(self.index, lines[line].len_utf8()),
170 }
171 }
172}
173
174#[derive(Clone, Copy, PartialEq)]
175pub(crate) struct SelectionState {
176 start: TextPoint,
177 end: TextPoint,
178 direction: SelectionDirection,
179}
180
181#[derive(JSTraceable, MallocSizeOf)]
183pub struct TextInput<T: ClipboardProvider> {
184 lines: Vec<DOMString>,
186
187 edit_point: TextPoint,
189
190 selection_origin: Option<TextPoint>,
193 selection_direction: SelectionDirection,
194
195 multiline: bool,
197
198 #[ignore_malloc_size_of = "Can't easily measure this generic type"]
199 clipboard_provider: T,
200
201 max_length: Option<UTF16CodeUnits>,
205 min_length: Option<UTF16CodeUnits>,
206
207 was_last_change_by_set_content: bool,
209}
210
211#[derive(Clone, Copy, PartialEq)]
212pub enum IsComposing {
213 Composing,
214 NotComposing,
215}
216
217impl From<IsComposing> for bool {
218 fn from(is_composing: IsComposing) -> Self {
219 match is_composing {
220 IsComposing::Composing => true,
221 IsComposing::NotComposing => false,
222 }
223 }
224}
225
226#[derive(Clone, Copy, PartialEq)]
228pub enum InputType {
229 InsertText,
230 InsertLineBreak,
231 InsertFromPaste,
232 InsertCompositionText,
233 DeleteByCut,
234 DeleteContentBackward,
235 DeleteContentForward,
236 Nothing,
237}
238
239impl InputType {
240 fn as_str(&self) -> &str {
241 match *self {
242 InputType::InsertText => "insertText",
243 InputType::InsertLineBreak => "insertLineBreak",
244 InputType::InsertFromPaste => "insertFromPaste",
245 InputType::InsertCompositionText => "insertCompositionText",
246 InputType::DeleteByCut => "deleteByCut",
247 InputType::DeleteContentBackward => "deleteContentBackward",
248 InputType::DeleteContentForward => "deleteContentForward",
249 InputType::Nothing => "",
250 }
251 }
252}
253
254pub enum KeyReaction {
256 TriggerDefaultAction,
257 DispatchInput(Option<String>, IsComposing, InputType),
258 RedrawSelection,
259 Nothing,
260}
261
262bitflags! {
263 #[derive(Clone, Copy)]
266 pub struct ClipboardEventFlags: u8 {
267 const QueueInputEvent = 1 << 0;
268 const FireClipboardChangedEvent = 1 << 1;
269 }
270}
271
272pub struct ClipboardEventReaction {
273 pub flags: ClipboardEventFlags,
274 pub text: Option<String>,
275 pub input_type: InputType,
276}
277
278impl ClipboardEventReaction {
279 fn new(flags: ClipboardEventFlags) -> Self {
280 Self {
281 flags,
282 text: None,
283 input_type: InputType::Nothing,
284 }
285 }
286
287 fn with_text(mut self, text: String) -> Self {
288 self.text = Some(text);
289 self
290 }
291
292 fn with_input_type(mut self, input_type: InputType) -> Self {
293 self.input_type = input_type;
294 self
295 }
296
297 fn empty() -> Self {
298 Self::new(ClipboardEventFlags::empty())
299 }
300}
301
302impl Default for TextPoint {
303 fn default() -> TextPoint {
304 TextPoint {
305 line: 0,
306 index: UTF8Bytes::zero(),
307 }
308 }
309}
310
311#[derive(Eq, PartialEq)]
313pub enum Lines {
314 Single,
315 Multiple,
316}
317
318#[derive(Clone, Copy, Eq, PartialEq)]
320pub enum Direction {
321 Forward,
322 Backward,
323}
324
325#[cfg(target_os = "macos")]
327pub(crate) const CMD_OR_CONTROL: Modifiers = Modifiers::META;
328#[cfg(not(target_os = "macos"))]
329pub(crate) const CMD_OR_CONTROL: Modifiers = Modifiers::CONTROL;
330
331fn len_of_first_n_chars(text: &DOMString, n: usize) -> UTF8Bytes {
336 match text.str().char_indices().take(n).last() {
337 Some((index, ch)) => UTF8Bytes(index + ch.len_utf8()),
338 None => UTF8Bytes::zero(),
339 }
340}
341
342fn len_of_first_n_code_units(text: &DOMString, n: UTF16CodeUnits) -> UTF8Bytes {
346 let mut utf8_len = UTF8Bytes::zero();
347 let mut utf16_len = UTF16CodeUnits::zero();
348 for c in text.str().chars() {
349 utf16_len += UTF16CodeUnits(c.len_utf16());
350 if utf16_len > n {
351 break;
352 }
353 utf8_len += UTF8Bytes(c.len_utf8());
354 }
355 utf8_len
356}
357
358impl<T: ClipboardProvider> TextInput<T> {
359 pub fn new(
361 lines: Lines,
362 initial: DOMString,
363 clipboard_provider: T,
364 max_length: Option<UTF16CodeUnits>,
365 min_length: Option<UTF16CodeUnits>,
366 selection_direction: SelectionDirection,
367 ) -> TextInput<T> {
368 let mut i = TextInput {
369 lines: vec![],
370 edit_point: Default::default(),
371 selection_origin: None,
372 multiline: lines == Lines::Multiple,
373 clipboard_provider,
374 max_length,
375 min_length,
376 selection_direction,
377 was_last_change_by_set_content: true,
378 };
379 i.set_content(initial);
380 i
381 }
382
383 pub fn edit_point(&self) -> TextPoint {
384 self.edit_point
385 }
386
387 pub fn selection_origin(&self) -> Option<TextPoint> {
388 self.selection_origin
389 }
390
391 pub fn selection_origin_or_edit_point(&self) -> TextPoint {
394 self.selection_origin.unwrap_or(self.edit_point)
395 }
396
397 pub fn selection_direction(&self) -> SelectionDirection {
398 self.selection_direction
399 }
400
401 pub(crate) fn set_max_length(&mut self, length: Option<UTF16CodeUnits>) {
402 self.max_length = length;
403 }
404
405 pub(crate) fn set_min_length(&mut self, length: Option<UTF16CodeUnits>) {
406 self.min_length = length;
407 }
408
409 pub(crate) fn was_last_change_by_set_content(&self) -> bool {
411 self.was_last_change_by_set_content
412 }
413
414 pub fn delete_char(&mut self, dir: Direction) -> bool {
418 if self.selection_origin.is_none() || self.selection_origin == Some(self.edit_point) {
419 self.adjust_horizontal_by_one(dir, Selection::Selected);
420 }
421 if self.selection_start() == self.selection_end() {
422 false
423 } else {
424 self.replace_selection(DOMString::new());
425 true
426 }
427 }
428
429 pub fn insert_char(&mut self, ch: char) {
431 self.insert_string(ch.to_string());
432 }
433
434 pub fn insert_string<S: Into<String>>(&mut self, s: S) {
437 if self.selection_origin.is_none() {
438 self.selection_origin = Some(self.edit_point);
439 }
440 self.replace_selection(DOMString::from(s.into()));
441 }
442
443 pub fn selection_start(&self) -> TextPoint {
446 match self.selection_direction {
447 SelectionDirection::None | SelectionDirection::Forward => {
448 self.selection_origin_or_edit_point()
449 },
450 SelectionDirection::Backward => self.edit_point,
451 }
452 }
453
454 pub fn selection_start_offset(&self) -> UTF8Bytes {
456 self.text_point_to_offset(&self.selection_start())
457 }
458
459 pub fn selection_end(&self) -> TextPoint {
462 match self.selection_direction {
463 SelectionDirection::None | SelectionDirection::Forward => self.edit_point,
464 SelectionDirection::Backward => self.selection_origin_or_edit_point(),
465 }
466 }
467
468 pub fn selection_end_offset(&self) -> UTF8Bytes {
470 self.text_point_to_offset(&self.selection_end())
471 }
472
473 #[inline]
475 pub(crate) fn has_selection(&self) -> bool {
476 self.selection_origin.is_some()
477 }
478
479 pub fn sorted_selection_bounds(&self) -> (TextPoint, TextPoint) {
482 (self.selection_start(), self.selection_end())
483 }
484
485 pub(crate) fn sorted_selection_offsets_range(&self) -> Range<UTF8Bytes> {
489 self.selection_start_offset()..self.selection_end_offset()
490 }
491
492 pub(crate) fn selection_state(&self) -> SelectionState {
494 SelectionState {
495 start: self.selection_start(),
496 end: self.selection_end(),
497 direction: self.selection_direction,
498 }
499 }
500
501 fn assert_ok_selection(&self) {
503 debug!(
504 "edit_point: {:?}, selection_origin: {:?}, direction: {:?}",
505 self.edit_point, self.selection_origin, self.selection_direction
506 );
507 if let Some(begin) = self.selection_origin {
508 debug_assert!(begin.line < self.lines.len());
509 debug_assert!(begin.index <= self.lines[begin.line].len_utf8());
510
511 match self.selection_direction {
512 SelectionDirection::None | SelectionDirection::Forward => {
513 debug_assert!(begin <= self.edit_point)
514 },
515
516 SelectionDirection::Backward => debug_assert!(self.edit_point <= begin),
517 }
518 }
519
520 debug_assert!(self.edit_point.line < self.lines.len());
521 debug_assert!(self.edit_point.index <= self.lines[self.edit_point.line].len_utf8());
522 }
523
524 pub(crate) fn get_selection_text(&self) -> Option<String> {
525 let text = self.fold_selection_slices(String::new(), |s, slice| s.push_str(slice));
526 if text.is_empty() {
527 return None;
528 }
529 Some(text)
530 }
531
532 fn selection_utf16_len(&self) -> UTF16CodeUnits {
534 self.fold_selection_slices(UTF16CodeUnits::zero(), |len, slice| {
535 *len += UTF16CodeUnits(slice.chars().map(char::len_utf16).sum::<usize>())
536 })
537 }
538
539 fn fold_selection_slices<B, F: FnMut(&mut B, &str)>(&self, mut acc: B, mut f: F) -> B {
543 if self.has_selection() {
544 let (start, end) = self.sorted_selection_bounds();
545 let UTF8Bytes(start_offset) = start.index;
546 let UTF8Bytes(end_offset) = end.index;
547
548 if start.line == end.line {
549 f(
550 &mut acc,
551 &self.lines[start.line].str()[start_offset..end_offset],
552 )
553 } else {
554 f(&mut acc, &self.lines[start.line].str()[start_offset..]);
555 for line in &self.lines[start.line + 1..end.line] {
556 f(&mut acc, "\n");
557 f(&mut acc, &line.str());
558 }
559 f(&mut acc, "\n");
560 f(&mut acc, &self.lines[end.line].str()[..end_offset])
561 }
562 }
563
564 acc
565 }
566
567 pub fn replace_selection(&mut self, insert: DOMString) {
568 if !self.has_selection() {
569 return;
570 }
571
572 let allowed_to_insert_count = if let Some(max_length) = self.max_length {
573 let len_after_selection_replaced =
574 self.utf16_len().saturating_sub(self.selection_utf16_len());
575 max_length.saturating_sub(len_after_selection_replaced)
576 } else {
577 UTF16CodeUnits(usize::MAX)
578 };
579
580 let UTF8Bytes(last_char_index) =
581 len_of_first_n_code_units(&insert, allowed_to_insert_count);
582 let to_insert = &insert.str()[..last_char_index];
583
584 let (start, end) = self.sorted_selection_bounds();
585 let UTF8Bytes(start_offset) = start.index;
586 let UTF8Bytes(end_offset) = end.index;
587
588 let new_lines = {
589 let prefix = &self.lines[start.line].str()[..start_offset];
590 let suffix = &self.lines[end.line].str()[end_offset..];
591 let lines_prefix = &self.lines[..start.line];
592 let lines_suffix = &self.lines[end.line + 1..];
593
594 let mut insert_lines = if self.multiline {
595 to_insert.split('\n').map(DOMString::from).collect()
596 } else {
597 vec![DOMString::from(to_insert)]
598 };
599
600 let mut new_line = prefix.to_owned();
602
603 new_line.push_str(&insert_lines[0].str());
604 insert_lines[0] = DOMString::from(new_line);
605
606 let last_insert_lines_index = insert_lines.len() - 1;
607 self.edit_point.index = insert_lines[last_insert_lines_index].len_utf8();
608 self.edit_point.line = start.line + last_insert_lines_index;
609
610 insert_lines[last_insert_lines_index].push_str(suffix);
612
613 let mut new_lines = vec![];
614 new_lines.extend_from_slice(lines_prefix);
615 new_lines.extend_from_slice(&insert_lines);
616 new_lines.extend_from_slice(lines_suffix);
617 new_lines
618 };
619
620 self.lines = new_lines;
621 self.was_last_change_by_set_content = false;
622 self.clear_selection();
623 self.assert_ok_selection();
624 }
625
626 pub fn current_line_length(&self) -> UTF8Bytes {
628 self.lines[self.edit_point.line].len_utf8()
629 }
630
631 pub fn adjust_vertical(&mut self, adjust: isize, select: Selection) {
634 if !self.multiline {
635 return;
636 }
637
638 if select == Selection::Selected {
639 if self.selection_origin.is_none() {
640 self.selection_origin = Some(self.edit_point);
641 }
642 } else {
643 self.clear_selection();
644 }
645
646 assert!(self.edit_point.line < self.lines.len());
647
648 let target_line: isize = self.edit_point.line as isize + adjust;
649
650 if target_line < 0 {
651 self.edit_point.line = 0;
652 self.edit_point.index = UTF8Bytes::zero();
653 if self.selection_origin.is_some() &&
654 (self.selection_direction == SelectionDirection::None ||
655 self.selection_direction == SelectionDirection::Forward)
656 {
657 self.selection_origin = Some(TextPoint {
658 line: 0,
659 index: UTF8Bytes::zero(),
660 });
661 }
662 return;
663 } else if target_line as usize >= self.lines.len() {
664 self.edit_point.line = self.lines.len() - 1;
665 self.edit_point.index = self.current_line_length();
666 if self.selection_origin.is_some() &&
667 (self.selection_direction == SelectionDirection::Backward)
668 {
669 self.selection_origin = Some(self.edit_point);
670 }
671 return;
672 }
673
674 let UTF8Bytes(edit_index) = self.edit_point.index;
675 let col = self.lines[self.edit_point.line].str()[..edit_index]
676 .chars()
677 .count();
678 self.edit_point.line = target_line as usize;
679 self.edit_point.index = len_of_first_n_chars(&self.lines[self.edit_point.line], col);
681 if let Some(origin) = self.selection_origin {
682 if ((self.selection_direction == SelectionDirection::None ||
683 self.selection_direction == SelectionDirection::Forward) &&
684 self.edit_point <= origin) ||
685 (self.selection_direction == SelectionDirection::Backward &&
686 origin <= self.edit_point)
687 {
688 self.selection_origin = Some(self.edit_point);
689 }
690 }
691 self.assert_ok_selection();
692 }
693
694 pub fn adjust_horizontal(
698 &mut self,
699 adjust: UTF8Bytes,
700 direction: Direction,
701 select: Selection,
702 ) {
703 if self.adjust_selection_for_horizontal_change(direction, select) {
704 return;
705 }
706 self.perform_horizontal_adjustment(adjust, direction, select);
707 }
708
709 pub fn adjust_horizontal_by_one(&mut self, direction: Direction, select: Selection) {
713 if self.adjust_selection_for_horizontal_change(direction, select) {
714 return;
715 }
716 let adjust = {
717 let current_line = self.lines[self.edit_point.line].str();
718 let UTF8Bytes(current_offset) = self.edit_point.index;
719 let next_ch = match direction {
720 Direction::Forward => current_line[current_offset..].graphemes(true).next(),
721 Direction::Backward => current_line[..current_offset].graphemes(true).next_back(),
722 };
723 match next_ch {
724 Some(c) => UTF8Bytes(c.len()),
725 None => UTF8Bytes::one(), }
727 };
728 self.perform_horizontal_adjustment(adjust, direction, select);
729 }
730
731 fn adjust_selection_for_horizontal_change(
733 &mut self,
734 adjust: Direction,
735 select: Selection,
736 ) -> bool {
737 if select == Selection::Selected {
738 if self.selection_origin.is_none() {
739 self.selection_origin = Some(self.edit_point);
740 }
741 } else if self.has_selection() {
742 self.edit_point = match adjust {
743 Direction::Backward => self.selection_start(),
744 Direction::Forward => self.selection_end(),
745 };
746 self.clear_selection();
747 return true;
748 }
749 false
750 }
751
752 fn update_selection_direction(&mut self) {
757 debug!(
758 "edit_point: {:?}, selection_origin: {:?}",
759 self.edit_point, self.selection_origin
760 );
761 self.selection_direction = if Some(self.edit_point) < self.selection_origin {
762 SelectionDirection::Backward
763 } else {
764 SelectionDirection::Forward
765 }
766 }
767
768 fn perform_horizontal_adjustment(
769 &mut self,
770 adjust: UTF8Bytes,
771 direction: Direction,
772 select: Selection,
773 ) {
774 match direction {
775 Direction::Backward => {
776 let remaining = self.edit_point.index;
777 if adjust > remaining && self.edit_point.line > 0 {
778 let selection_origin_temp = self.selection_origin;
782 self.adjust_vertical(-1, select);
783 self.edit_point.index = self.current_line_length();
784 self.selection_origin = selection_origin_temp;
786 self.adjust_horizontal(
788 adjust.saturating_sub(remaining + UTF8Bytes::one()),
789 direction,
790 select,
791 );
792 } else {
793 self.edit_point.index = remaining.saturating_sub(adjust);
794 }
795 },
796 Direction::Forward => {
797 let remaining = self
798 .current_line_length()
799 .saturating_sub(self.edit_point.index);
800 if adjust > remaining && self.lines.len() > self.edit_point.line + 1 {
801 self.adjust_vertical(1, select);
802 self.edit_point.index = UTF8Bytes::zero();
803 self.adjust_horizontal(
805 adjust.saturating_sub(remaining + UTF8Bytes::one()),
806 direction,
807 select,
808 );
809 } else {
810 self.edit_point.index =
811 min(self.current_line_length(), self.edit_point.index + adjust);
812 }
813 },
814 };
815 self.update_selection_direction();
816 self.assert_ok_selection();
817 }
818
819 pub fn handle_return(&mut self) -> KeyReaction {
821 if !self.multiline {
822 KeyReaction::TriggerDefaultAction
823 } else {
824 self.insert_char('\n');
825 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::InsertLineBreak)
826 }
827 }
828
829 pub fn select_all(&mut self) {
831 self.selection_origin = Some(TextPoint {
832 line: 0,
833 index: UTF8Bytes::zero(),
834 });
835 let last_line = self.lines.len() - 1;
836 self.edit_point.line = last_line;
837 self.edit_point.index = self.lines[last_line].len_utf8();
838 self.selection_direction = SelectionDirection::Forward;
839 self.assert_ok_selection();
840 }
841
842 pub fn clear_selection(&mut self) {
844 self.selection_origin = None;
845 self.selection_direction = SelectionDirection::None;
846 }
847
848 pub(crate) fn clear_selection_to_limit(&mut self, direction: Direction) {
850 self.clear_selection();
851 self.adjust_horizontal_to_limit(direction, Selection::NotSelected);
852 }
853
854 pub fn adjust_horizontal_by_word(&mut self, direction: Direction, select: Selection) {
855 if self.adjust_selection_for_horizontal_change(direction, select) {
856 return;
857 }
858 let shift_increment: UTF8Bytes = {
859 let current_index = self.edit_point.index;
860 let current_line_index = self.edit_point.line;
861 let current_line = self.lines[current_line_index].str();
862 let mut newline_adjustment = UTF8Bytes::zero();
863 let mut shift_temp = UTF8Bytes::zero();
864 match direction {
865 Direction::Backward => {
866 let previous_line = current_line_index
867 .checked_sub(1)
868 .and_then(|index| self.lines.get(index))
869 .map(|s| s.str());
870
871 let input: &str;
872 if current_index == UTF8Bytes::zero() && current_line_index > 0 {
873 input = previous_line.as_ref().unwrap();
874 newline_adjustment = UTF8Bytes::one();
875 } else {
876 let UTF8Bytes(remaining) = current_index;
877 input = ¤t_line[..remaining];
878 }
879
880 let mut iter = input.split_word_bounds().rev();
881 loop {
882 match iter.next() {
883 None => break,
884 Some(x) => {
885 shift_temp += UTF8Bytes(x.len());
886 if x.chars().any(|x| x.is_alphabetic() || x.is_numeric()) {
887 break;
888 }
889 },
890 }
891 }
892 },
893 Direction::Forward => {
894 let input: &str;
895 let next_line = self.lines.get(current_line_index + 1).map(|s| s.str());
896 let remaining = self.current_line_length().saturating_sub(current_index);
897 if remaining == UTF8Bytes::zero() && self.lines.len() > self.edit_point.line + 1
898 {
899 input = next_line.as_ref().unwrap();
900 newline_adjustment = UTF8Bytes::one();
901 } else {
902 let UTF8Bytes(current_offset) = current_index;
903 input = ¤t_line[current_offset..];
904 }
905
906 let mut iter = input.split_word_bounds();
907 loop {
908 match iter.next() {
909 None => break,
910 Some(x) => {
911 shift_temp += UTF8Bytes(x.len());
912 if x.chars().any(|x| x.is_alphabetic() || x.is_numeric()) {
913 break;
914 }
915 },
916 }
917 }
918 },
919 };
920
921 shift_temp + newline_adjustment
922 };
923
924 self.adjust_horizontal(shift_increment, direction, select);
925 }
926
927 pub fn adjust_horizontal_to_line_end(&mut self, direction: Direction, select: Selection) {
928 if self.adjust_selection_for_horizontal_change(direction, select) {
929 return;
930 }
931 let shift: usize = {
932 let current_line = &self.lines[self.edit_point.line];
933 let UTF8Bytes(current_offset) = self.edit_point.index;
934 match direction {
935 Direction::Backward => current_line.str()[..current_offset].len(),
936 Direction::Forward => current_line.str()[current_offset..].len(),
937 }
938 };
939 self.perform_horizontal_adjustment(UTF8Bytes(shift), direction, select);
940 }
941
942 pub(crate) fn adjust_horizontal_to_limit(&mut self, direction: Direction, select: Selection) {
943 if self.adjust_selection_for_horizontal_change(direction, select) {
944 return;
945 }
946 match direction {
947 Direction::Backward => {
948 self.edit_point.line = 0;
949 self.edit_point.index = UTF8Bytes::zero();
950 },
951 Direction::Forward => {
952 self.edit_point.line = &self.lines.len() - 1;
953 self.edit_point.index = (self.lines[&self.lines.len() - 1]).len_utf8();
954 },
955 }
956 }
957
958 pub(crate) fn handle_keydown(&mut self, event: &KeyboardEvent) -> KeyReaction {
960 let key = event.key();
961 let mods = event.modifiers();
962 self.handle_keydown_aux(key, mods, cfg!(target_os = "macos"))
963 }
964
965 pub fn handle_keydown_aux(
968 &mut self,
969 key: Key,
970 mut mods: Modifiers,
971 macos: bool,
972 ) -> KeyReaction {
973 let maybe_select = if mods.contains(Modifiers::SHIFT) {
974 Selection::Selected
975 } else {
976 Selection::NotSelected
977 };
978 mods.remove(Modifiers::SHIFT);
979 ShortcutMatcher::new(KeyState::Down, key.clone(), mods)
980 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'B', || {
981 self.adjust_horizontal_by_word(Direction::Backward, maybe_select);
982 KeyReaction::RedrawSelection
983 })
984 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'F', || {
985 self.adjust_horizontal_by_word(Direction::Forward, maybe_select);
986 KeyReaction::RedrawSelection
987 })
988 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'A', || {
989 self.adjust_horizontal_to_line_end(Direction::Backward, maybe_select);
990 KeyReaction::RedrawSelection
991 })
992 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'E', || {
993 self.adjust_horizontal_to_line_end(Direction::Forward, maybe_select);
994 KeyReaction::RedrawSelection
995 })
996 .optional_shortcut(macos, Modifiers::CONTROL, 'A', || {
997 self.adjust_horizontal_to_line_end(Direction::Backward, maybe_select);
998 KeyReaction::RedrawSelection
999 })
1000 .optional_shortcut(macos, Modifiers::CONTROL, 'E', || {
1001 self.adjust_horizontal_to_line_end(Direction::Forward, maybe_select);
1002 KeyReaction::RedrawSelection
1003 })
1004 .shortcut(CMD_OR_CONTROL, 'A', || {
1005 self.select_all();
1006 KeyReaction::RedrawSelection
1007 })
1008 .shortcut(CMD_OR_CONTROL, 'X', || {
1009 if let Some(text) = self.get_selection_text() {
1010 self.clipboard_provider.set_text(text);
1011 self.delete_char(Direction::Backward);
1012 }
1013 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::DeleteByCut)
1014 })
1015 .shortcut(CMD_OR_CONTROL, 'C', || {
1016 if let Some(text) = self.get_selection_text() {
1018 self.clipboard_provider.set_text(text);
1019 }
1020 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::Nothing)
1021 })
1022 .shortcut(CMD_OR_CONTROL, 'V', || {
1023 if let Ok(text_content) = self.clipboard_provider.get_text() {
1024 self.insert_string(&text_content);
1025 KeyReaction::DispatchInput(
1026 Some(text_content),
1027 IsComposing::NotComposing,
1028 InputType::InsertFromPaste,
1029 )
1030 } else {
1031 KeyReaction::DispatchInput(
1032 Some("".to_string()),
1033 IsComposing::NotComposing,
1034 InputType::InsertFromPaste,
1035 )
1036 }
1037 })
1038 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Delete), || {
1039 if self.delete_char(Direction::Forward) {
1040 KeyReaction::DispatchInput(
1041 None,
1042 IsComposing::NotComposing,
1043 InputType::DeleteContentForward,
1044 )
1045 } else {
1046 KeyReaction::Nothing
1047 }
1048 })
1049 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Backspace), || {
1050 if self.delete_char(Direction::Backward) {
1051 KeyReaction::DispatchInput(
1052 None,
1053 IsComposing::NotComposing,
1054 InputType::DeleteContentBackward,
1055 )
1056 } else {
1057 KeyReaction::Nothing
1058 }
1059 })
1060 .optional_shortcut(
1061 macos,
1062 Modifiers::META,
1063 Key::Named(NamedKey::ArrowLeft),
1064 || {
1065 self.adjust_horizontal_to_line_end(Direction::Backward, maybe_select);
1066 KeyReaction::RedrawSelection
1067 },
1068 )
1069 .optional_shortcut(
1070 macos,
1071 Modifiers::META,
1072 Key::Named(NamedKey::ArrowRight),
1073 || {
1074 self.adjust_horizontal_to_line_end(Direction::Forward, maybe_select);
1075 KeyReaction::RedrawSelection
1076 },
1077 )
1078 .optional_shortcut(
1079 macos,
1080 Modifiers::META,
1081 Key::Named(NamedKey::ArrowUp),
1082 || {
1083 self.adjust_horizontal_to_limit(Direction::Backward, maybe_select);
1084 KeyReaction::RedrawSelection
1085 },
1086 )
1087 .optional_shortcut(
1088 macos,
1089 Modifiers::META,
1090 Key::Named(NamedKey::ArrowDown),
1091 || {
1092 self.adjust_horizontal_to_limit(Direction::Forward, maybe_select);
1093 KeyReaction::RedrawSelection
1094 },
1095 )
1096 .shortcut(Modifiers::ALT, Key::Named(NamedKey::ArrowLeft), || {
1097 self.adjust_horizontal_by_word(Direction::Backward, maybe_select);
1098 KeyReaction::RedrawSelection
1099 })
1100 .shortcut(Modifiers::ALT, Key::Named(NamedKey::ArrowRight), || {
1101 self.adjust_horizontal_by_word(Direction::Forward, maybe_select);
1102 KeyReaction::RedrawSelection
1103 })
1104 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowLeft), || {
1105 self.adjust_horizontal_by_one(Direction::Backward, maybe_select);
1106 KeyReaction::RedrawSelection
1107 })
1108 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowRight), || {
1109 self.adjust_horizontal_by_one(Direction::Forward, maybe_select);
1110 KeyReaction::RedrawSelection
1111 })
1112 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowUp), || {
1113 self.adjust_vertical(-1, maybe_select);
1114 KeyReaction::RedrawSelection
1115 })
1116 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowDown), || {
1117 self.adjust_vertical(1, maybe_select);
1118 KeyReaction::RedrawSelection
1119 })
1120 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Enter), || {
1121 self.handle_return()
1122 })
1123 .optional_shortcut(
1124 macos,
1125 Modifiers::empty(),
1126 Key::Named(NamedKey::Home),
1127 || {
1128 self.edit_point.index = UTF8Bytes::zero();
1129 KeyReaction::RedrawSelection
1130 },
1131 )
1132 .optional_shortcut(macos, Modifiers::empty(), Key::Named(NamedKey::End), || {
1133 self.edit_point.index = self.current_line_length();
1134 self.assert_ok_selection();
1135 KeyReaction::RedrawSelection
1136 })
1137 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageUp), || {
1138 self.adjust_vertical(-28, maybe_select);
1139 KeyReaction::RedrawSelection
1140 })
1141 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageDown), || {
1142 self.adjust_vertical(28, maybe_select);
1143 KeyReaction::RedrawSelection
1144 })
1145 .otherwise(|| {
1146 if let Key::Character(ref c) = key {
1147 self.insert_string(c.as_str());
1148 return KeyReaction::DispatchInput(
1149 Some(c.to_string()),
1150 IsComposing::NotComposing,
1151 InputType::InsertText,
1152 );
1153 }
1154 if matches!(key, Key::Named(NamedKey::Process)) {
1155 return KeyReaction::DispatchInput(
1156 None,
1157 IsComposing::Composing,
1158 InputType::Nothing,
1159 );
1160 }
1161 KeyReaction::Nothing
1162 })
1163 .unwrap()
1164 }
1165
1166 pub(crate) fn handle_compositionend(&mut self, event: &CompositionEvent) -> KeyReaction {
1167 let ch = event.data().str();
1168 self.insert_string(ch.as_ref());
1169 KeyReaction::DispatchInput(
1170 Some(ch.to_string()),
1171 IsComposing::NotComposing,
1172 InputType::InsertCompositionText,
1173 )
1174 }
1175
1176 pub(crate) fn handle_compositionupdate(&mut self, event: &CompositionEvent) -> KeyReaction {
1177 let ch = event.data().str();
1178 let start = self.selection_start_offset().0;
1179 self.insert_string(ch.as_ref());
1180 self.set_selection_range(
1181 start as u32,
1182 (start + event.data().len_utf8().0) as u32,
1183 SelectionDirection::Forward,
1184 );
1185 KeyReaction::DispatchInput(
1186 Some(ch.to_string()),
1187 IsComposing::Composing,
1188 InputType::InsertCompositionText,
1189 )
1190 }
1191
1192 pub(crate) fn is_empty(&self) -> bool {
1194 self.lines.len() <= 1 && self.lines.first().is_none_or(|line| line.is_empty())
1195 }
1196
1197 pub(crate) fn len_utf8(&self) -> UTF8Bytes {
1199 self.lines
1200 .iter()
1201 .fold(UTF8Bytes::zero(), |m, l| {
1202 m + l.len_utf8() + UTF8Bytes::one() })
1204 .saturating_sub(UTF8Bytes::one())
1205 }
1206
1207 pub(crate) fn utf16_len(&self) -> UTF16CodeUnits {
1209 self.lines
1210 .iter()
1211 .fold(UTF16CodeUnits::zero(), |m, l| {
1212 m + UTF16CodeUnits(l.str().chars().map(char::len_utf16).sum::<usize>() + 1)
1213 })
1215 .saturating_sub(UTF16CodeUnits::one())
1216 }
1217
1218 pub(crate) fn char_count(&self) -> usize {
1220 self.lines.iter().fold(0, |m, l| {
1221 m + l.str().chars().count() + 1 }) - 1
1223 }
1224
1225 pub fn get_content(&self) -> DOMString {
1227 let mut content = "".to_owned();
1228 for (i, line) in self.lines.iter().enumerate() {
1229 content.push_str(&line.str());
1230 if i < self.lines.len() - 1 {
1231 content.push('\n');
1232 }
1233 }
1234 DOMString::from(content)
1235 }
1236
1237 pub(crate) fn single_line_content(&self) -> &DOMString {
1239 assert!(!self.multiline);
1240 &self.lines[0]
1241 }
1242
1243 pub fn set_content(&mut self, content: DOMString) {
1246 self.lines = if self.multiline {
1247 content
1249 .str()
1250 .replace("\r\n", "\n")
1251 .split(['\n', '\r'])
1252 .map(DOMString::from)
1253 .collect()
1254 } else {
1255 vec![content]
1256 };
1257
1258 self.was_last_change_by_set_content = true;
1259 self.edit_point = self.edit_point.constrain_to(&self.lines);
1260
1261 if let Some(origin) = self.selection_origin {
1262 self.selection_origin = Some(origin.constrain_to(&self.lines));
1263 }
1264 self.assert_ok_selection();
1265 }
1266
1267 fn text_point_to_offset(&self, text_point: &TextPoint) -> UTF8Bytes {
1269 self.lines
1270 .iter()
1271 .enumerate()
1272 .fold(UTF8Bytes::zero(), |acc, (i, val)| {
1273 if i < text_point.line {
1274 acc + val.len_utf8() + UTF8Bytes::one() } else {
1276 acc
1277 }
1278 }) +
1279 text_point.index
1280 }
1281
1282 fn offset_to_text_point(&self, abs_point: UTF8Bytes) -> TextPoint {
1284 let mut index = abs_point;
1285 let mut line = 0;
1286 let last_line_idx = self.lines.len() - 1;
1287 self.lines
1288 .iter()
1289 .enumerate()
1290 .fold(UTF8Bytes::zero(), |acc, (i, val)| {
1291 if i != last_line_idx {
1292 let line_end = val.len_utf8();
1293 let new_acc = acc + line_end + UTF8Bytes::one();
1294 if abs_point >= new_acc && index > line_end {
1295 index = index.saturating_sub(line_end + UTF8Bytes::one());
1296 line += 1;
1297 }
1298 new_acc
1299 } else {
1300 acc
1301 }
1302 });
1303
1304 TextPoint { line, index }
1305 }
1306
1307 pub fn set_selection_range(&mut self, start: u32, end: u32, direction: SelectionDirection) {
1308 let mut start = UTF8Bytes(start as usize);
1309 let mut end = UTF8Bytes(end as usize);
1310 let text_end = self.get_content().len_utf8();
1311
1312 if end > text_end {
1313 end = text_end;
1314 }
1315 if start > end {
1316 start = end;
1317 }
1318
1319 self.selection_direction = direction;
1320
1321 match direction {
1322 SelectionDirection::None | SelectionDirection::Forward => {
1323 self.selection_origin = Some(self.offset_to_text_point(start));
1324 self.edit_point = self.offset_to_text_point(end);
1325 },
1326 SelectionDirection::Backward => {
1327 self.selection_origin = Some(self.offset_to_text_point(end));
1328 self.edit_point = self.offset_to_text_point(start);
1329 },
1330 }
1331 self.assert_ok_selection();
1332 }
1333
1334 pub fn set_edit_point_index(&mut self, index: usize) {
1336 let byte_offset = self.lines[self.edit_point.line]
1337 .str()
1338 .graphemes(true)
1339 .take(index)
1340 .fold(UTF8Bytes::zero(), |acc, x| acc + x.len_utf8());
1341 self.edit_point.index = byte_offset;
1342 }
1343
1344 pub(crate) fn handle_clipboard_event(
1352 &mut self,
1353 clipboard_event: &ClipboardEvent,
1354 ) -> ClipboardEventReaction {
1355 let event = clipboard_event.upcast::<Event>();
1356 if !event.IsTrusted() {
1357 return ClipboardEventReaction::empty();
1358 }
1359
1360 if event.DefaultPrevented() {
1363 return ClipboardEventReaction::empty();
1366 }
1367
1368 let event_type = event.Type();
1369 match_domstring_ascii!(event_type,
1370 "copy" => {
1371 let selection = self.get_selection_text();
1373
1374 if let Some(text) = selection {
1376 self.clipboard_provider.set_text(text);
1377 }
1378
1379 ClipboardEventReaction::new(ClipboardEventFlags::FireClipboardChangedEvent)
1381 },
1382 "cut" => {
1383 let selection = self.get_selection_text();
1385
1386 let Some(text) = selection else {
1388 return ClipboardEventReaction::empty();
1390 };
1391
1392 self.clipboard_provider.set_text(text);
1394
1395 self.delete_char(Direction::Backward);
1397
1398 ClipboardEventReaction::new(
1401 ClipboardEventFlags::FireClipboardChangedEvent |
1402 ClipboardEventFlags::QueueInputEvent,
1403 )
1404 .with_input_type(InputType::DeleteByCut)
1405 },
1406 "paste" => {
1407 let Some(data_transfer) = clipboard_event.get_clipboard_data() else {
1409 return ClipboardEventReaction::empty();
1410 };
1411 let Some(drag_data_store) = data_transfer.data_store() else {
1412 return ClipboardEventReaction::empty();
1413 };
1414
1415 let Some(text_content) =
1425 drag_data_store
1426 .iter_item_list()
1427 .find_map(|item| match item {
1428 Kind::Text { data, .. } => Some(data.to_string()),
1429 _ => None,
1430 })
1431 else {
1432 return ClipboardEventReaction::empty();
1433 };
1434 if text_content.is_empty() {
1435 return ClipboardEventReaction::empty();
1436 }
1437
1438 self.insert_string(&text_content);
1439
1440 ClipboardEventReaction::new(ClipboardEventFlags::QueueInputEvent)
1443 .with_text(text_content)
1444 .with_input_type(InputType::InsertFromPaste)
1445 },
1446 _ => ClipboardEventReaction::empty(),)
1447 }
1448
1449 pub(crate) fn queue_input_event(
1451 &self,
1452 target: &EventTarget,
1453 data: Option<String>,
1454 is_composing: IsComposing,
1455 input_type: InputType,
1456 ) {
1457 let global = target.global();
1458 let target = Trusted::new(target);
1459 global.task_manager().user_interaction_task_source().queue(
1460 task!(fire_input_event: move || {
1461 let target = target.root();
1462 let global = target.global();
1463 let window = global.as_window();
1464 let event = InputEvent::new(
1465 window,
1466 None,
1467 DOMString::from("input"),
1468 true,
1469 false,
1470 Some(window),
1471 0,
1472 data.map(DOMString::from),
1473 is_composing.into(),
1474 input_type.as_str().into(),
1475 CanGc::note(),
1476 );
1477 let event = event.upcast::<Event>();
1478 event.set_composed(true);
1479 event.fire(&target, CanGc::note());
1480 }),
1481 );
1482 }
1483}