From 32f781234a07c6aa52e8105d5aa32bec3f4791a1 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 14 Feb 2018 22:04:28 +0100 Subject: [PATCH] Disallow mutating the internals of TextInput The TextInput::assert_ok_selection() method is meant to ensure that we are not getting into a state where a selection refers to a location in the control's contents which doesn't exist. However, before this change we could have a situation where the internals of the TextInput are changed by another part of the code, without using its public API. This could lead to us having an invalid selection. I did manage to trigger such a situation (see the test added in this commit) although it is quite contrived. There may be others that I didn't think of, and it's also possible that future changes could introduce new cases. (Including ones which trigger panics, if indexing is used on the assumption that the selection indices are always valid.) The current HTML specification doesn't explicitly say that selectionStart/End must remain within the length of the content, but that does seems to be the consensus reached in a discussion of this: https://github.com/whatwg/html/issues/2424 The test case I've added here is currently undefined in the spec which is why I've added it in tests/wpt/mozilla. --- components/script/dom/htmlinputelement.rs | 107 ++++---- components/script/dom/htmltextareaelement.rs | 3 - components/script/dom/textcontrol.rs | 2 +- components/script/textinput.rs | 75 ++++-- tests/unit/script/textinput.rs | 231 +++++++++--------- tests/wpt/mozilla/meta/MANIFEST.json | 10 + ...ntrol-selection-cannot-exceed-content.html | 25 ++ 7 files changed, 255 insertions(+), 198 deletions(-) create mode 100644 tests/wpt/mozilla/tests/mozilla/textcontrol-selection-cannot-exceed-content.html diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index b0fbfb36f36..05ca796abd2 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -48,7 +48,6 @@ use script_traits::ScriptToConstellationChan; use servo_atoms::Atom; use std::borrow::ToOwned; use std::cell::Cell; -use std::mem; use std::ops::Range; use style::attr::AttrValue; use style::element_state::ElementState; @@ -990,42 +989,34 @@ impl HTMLInputElement { } // https://html.spec.whatwg.org/multipage/#value-sanitization-algorithm - fn sanitize_value(&self) { + fn sanitize_value(&self, value: &mut DOMString) { match self.input_type() { InputType::Text | InputType::Search | InputType::Tel | InputType::Password => { - self.textinput.borrow_mut().single_line_content_mut().strip_newlines(); + value.strip_newlines(); } InputType::Url => { - let mut textinput = self.textinput.borrow_mut(); - let content = textinput.single_line_content_mut(); - content.strip_newlines(); - content.strip_leading_and_trailing_ascii_whitespace(); + value.strip_newlines(); + value.strip_leading_and_trailing_ascii_whitespace(); } InputType::Date => { - let mut textinput = self.textinput.borrow_mut(); - if !textinput.single_line_content().is_valid_date_string() { - textinput.single_line_content_mut().clear(); + if !value.is_valid_date_string() { + value.clear(); } } InputType::Month => { - let mut textinput = self.textinput.borrow_mut(); - if !textinput.single_line_content().is_valid_month_string() { - textinput.single_line_content_mut().clear(); + if !value.is_valid_month_string() { + value.clear(); } } InputType::Week => { - let mut textinput = self.textinput.borrow_mut(); - if !textinput.single_line_content().is_valid_week_string() { - textinput.single_line_content_mut().clear(); + if !value.is_valid_week_string() { + value.clear(); } } InputType::Color => { - let mut textinput = self.textinput.borrow_mut(); - let is_valid = { - let content = textinput.single_line_content(); - let mut chars = content.chars(); - if content.len() == 7 && chars.next() == Some('#') { + let mut chars = value.chars(); + if value.len() == 7 && chars.next() == Some('#') { chars.all(|c| c.is_digit(16)) } else { false @@ -1033,38 +1024,29 @@ impl HTMLInputElement { }; if is_valid { - let content = textinput.single_line_content_mut(); - content.make_ascii_lowercase(); + value.make_ascii_lowercase(); } else { - textinput.set_content("#000000".into(), true); + *value = "#000000".into(); } } InputType::Time => { - let mut textinput = self.textinput.borrow_mut(); - - if !textinput.single_line_content().is_valid_time_string() { - textinput.single_line_content_mut().clear(); + if !value.is_valid_time_string() { + value.clear(); } } InputType::DatetimeLocal => { - let mut textinput = self.textinput.borrow_mut(); - if textinput.single_line_content_mut() - .convert_valid_normalized_local_date_and_time_string().is_err() { - textinput.single_line_content_mut().clear(); + if value.convert_valid_normalized_local_date_and_time_string().is_err() { + value.clear(); } } InputType::Number => { - let mut textinput = self.textinput.borrow_mut(); - if !textinput.single_line_content().is_valid_floating_point_number_string() { - textinput.single_line_content_mut().clear(); + if !value.is_valid_floating_point_number_string() { + value.clear(); } } // https://html.spec.whatwg.org/multipage/#range-state-(type=range):value-sanitization-algorithm InputType::Range => { - self.textinput - .borrow_mut() - .single_line_content_mut() - .set_best_representation_of_the_floating_point_number(); + value.set_best_representation_of_the_floating_point_number(); } _ => () } @@ -1075,20 +1057,23 @@ impl HTMLInputElement { TextControlSelection::new(&self, &self.textinput) } - fn update_text_contents(&self, value: DOMString, update_text_cursor: bool) -> ErrorResult { + fn update_text_contents(&self, mut value: DOMString, update_text_cursor: bool) -> ErrorResult { match self.value_mode() { ValueMode::Value => { - // Steps 1-2. - let old_value = mem::replace(self.textinput.borrow_mut().single_line_content_mut(), value); // Step 3. self.value_dirty.set(true); + // Step 4. - if update_text_cursor { - self.sanitize_value(); - } - // Step 5. - if *self.textinput.borrow().single_line_content() != old_value { - self.textinput.borrow_mut().clear_selection_to_limit(Direction::Forward, update_text_cursor); + self.sanitize_value(&mut value); + + let mut textinput = self.textinput.borrow_mut(); + + if *textinput.single_line_content() != value { + // Steps 1-2 + textinput.set_content(value, update_text_cursor); + + // Step 5. + textinput.clear_selection_to_limit(Direction::Forward, update_text_cursor); } } ValueMode::Default | @@ -1215,11 +1200,14 @@ impl VirtualMethods for HTMLInputElement { } // Step 6 - self.sanitize_value(); + let mut textinput = self.textinput.borrow_mut(); + let mut value = textinput.single_line_content().clone(); + self.sanitize_value(&mut value); + textinput.set_content(value, true); // Steps 7-9 if !previously_selectable && self.selection_api_applies() { - self.textinput.borrow_mut().clear_selection_to_limit(Direction::Backward, true); + textinput.clear_selection_to_limit(Direction::Backward, true); } }, AttributeMutation::Removed => { @@ -1240,9 +1228,10 @@ impl VirtualMethods for HTMLInputElement { }, &local_name!("value") if !self.value_dirty.get() => { let value = mutation.new_value(attr).map(|value| (**value).to_owned()); - self.textinput.borrow_mut().set_content( - value.map_or(DOMString::new(), DOMString::from), true); - self.sanitize_value(); + let mut value = value.map_or(DOMString::new(), DOMString::from); + + self.sanitize_value(&mut value); + self.textinput.borrow_mut().set_content(value, true); self.update_placeholder_shown_state(); }, &local_name!("name") if self.input_type() == InputType::Radio => { @@ -1252,10 +1241,12 @@ impl VirtualMethods for HTMLInputElement { &local_name!("maxlength") => { match *attr.value() { AttrValue::Int(_, value) => { + let mut textinput = self.textinput.borrow_mut(); + if value < 0 { - self.textinput.borrow_mut().max_length = None + textinput.set_max_length(None); } else { - self.textinput.borrow_mut().max_length = Some(value as usize) + textinput.set_max_length(Some(value as usize)) } }, _ => panic!("Expected an AttrValue::Int"), @@ -1264,10 +1255,12 @@ impl VirtualMethods for HTMLInputElement { &local_name!("minlength") => { match *attr.value() { AttrValue::Int(_, value) => { + let mut textinput = self.textinput.borrow_mut(); + if value < 0 { - self.textinput.borrow_mut().min_length = None + textinput.set_min_length(None); } else { - self.textinput.borrow_mut().min_length = Some(value as usize) + textinput.set_min_length(Some(value as usize)) } }, _ => panic!("Expected an AttrValue::Int"), diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index a5038918517..a6899b05a06 100755 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -323,7 +323,6 @@ impl HTMLTextAreaElement { // Step 1 let old_value = textinput.get_content(); - let old_selection = textinput.selection_origin; // Step 2 textinput.set_content(value, update_text_cursor); @@ -334,8 +333,6 @@ impl HTMLTextAreaElement { if old_value != textinput.get_content() { // Step 4 textinput.clear_selection_to_limit(Direction::Forward, update_text_cursor); - } else { - textinput.selection_origin = old_selection; } self.upcast::().dirty(NodeDamage::OtherNodeDamage); diff --git a/components/script/dom/textcontrol.rs b/components/script/dom/textcontrol.rs index b47167ae937..fa6c8d0b604 100644 --- a/components/script/dom/textcontrol.rs +++ b/components/script/dom/textcontrol.rs @@ -257,7 +257,7 @@ impl<'a, E: TextControlElement> TextControlSelection<'a, E> { } fn direction(&self) -> SelectionDirection { - self.textinput.borrow().selection_direction + self.textinput.borrow().selection_direction() } // https://html.spec.whatwg.org/multipage/#set-the-selection-range diff --git a/components/script/textinput.rs b/components/script/textinput.rs index a4a5d5e8965..a61799dadb0 100644 --- a/components/script/textinput.rs +++ b/components/script/textinput.rs @@ -56,6 +56,18 @@ pub struct TextPoint { pub index: usize, } +impl TextPoint { + /// Returns a TextPoint constrained to be a valid location within lines + fn constrain_to(&self, lines: &[DOMString]) -> TextPoint { + let line = min(self.line, lines.len() - 1); + + TextPoint { + line, + index: min(self.index, lines[line].len()), + } + } +} + #[derive(Clone, Copy, PartialEq)] pub struct SelectionState { start: TextPoint, @@ -68,21 +80,26 @@ pub struct SelectionState { pub struct TextInput { /// Current text input content, split across lines without trailing '\n' lines: Vec, + /// Current cursor input point - pub edit_point: TextPoint, + edit_point: TextPoint, + /// The current selection goes from the selection_origin until the edit_point. Note that the /// selection_origin may be after the edit_point, in the case of a backward selection. - pub selection_origin: Option, + selection_origin: Option, + selection_direction: SelectionDirection, + /// Is this a multiline input? multiline: bool, + #[ignore_malloc_size_of = "Can't easily measure this generic type"] clipboard_provider: T, + /// The maximum number of UTF-16 code units this text input is allowed to hold. /// /// - pub max_length: Option, - pub min_length: Option, - pub selection_direction: SelectionDirection, + max_length: Option, + min_length: Option, } /// Resulting action to be taken by the owner of a text input that is handling an event. @@ -175,6 +192,32 @@ impl TextInput { i } + pub fn edit_point(&self) -> TextPoint { + self.edit_point + } + + pub fn selection_origin(&self) -> Option { + self.selection_origin + } + + /// The selection origin, or the edit point if there is no selection. Note that the selection + /// origin may be after the edit point, in the case of a backward selection. + pub fn selection_origin_or_edit_point(&self) -> TextPoint { + self.selection_origin.unwrap_or(self.edit_point) + } + + pub fn selection_direction(&self) -> SelectionDirection { + self.selection_direction + } + + pub fn set_max_length(&mut self, length: Option) { + self.max_length = length; + } + + pub fn set_min_length(&mut self, length: Option) { + self.min_length = length; + } + /// Remove a character at the current editing point pub fn delete_char(&mut self, dir: Direction) { if self.selection_origin.is_none() || self.selection_origin == Some(self.edit_point) { @@ -196,12 +239,6 @@ impl TextInput { self.replace_selection(DOMString::from(s.into())); } - /// The selection origin, or the edit point if there is no selection. Note that the selection - /// origin may be after the edit point, in the case of a backward selection. - pub fn selection_origin_or_edit_point(&self) -> TextPoint { - self.selection_origin.unwrap_or(self.edit_point) - } - /// The start of the selection (or the edit point, if there is no selection). Always less than /// or equal to selection_end(), regardless of the selection direction. pub fn selection_start(&self) -> TextPoint { @@ -832,12 +869,6 @@ impl TextInput { &self.lines[0] } - /// Get a mutable reference to the contents of a single-line text input. Panics if self is a multiline input. - pub fn single_line_content_mut(&mut self) -> &mut DOMString { - assert!(!self.multiline); - &mut self.lines[0] - } - /// Set the current contents of the text input. If this is control supports multiple lines, /// any \n encountered will be stripped and force a new logical line. pub fn set_content(&mut self, content: DOMString, update_text_cursor: bool) { @@ -850,11 +881,15 @@ impl TextInput { } else { vec!(content) }; + if update_text_cursor { - self.edit_point.line = min(self.edit_point.line, self.lines.len() - 1); - self.edit_point.index = min(self.edit_point.index, self.current_line_length()); + self.edit_point = self.edit_point.constrain_to(&self.lines); } - self.selection_origin = None; + + if let Some(origin) = self.selection_origin { + self.selection_origin = Some(origin.constrain_to(&self.lines)); + } + self.assert_ok_selection(); } diff --git a/tests/unit/script/textinput.rs b/tests/unit/script/textinput.rs index 0bdeec59cda..916e26c9164 100644 --- a/tests/unit/script/textinput.rs +++ b/tests/unit/script/textinput.rs @@ -42,7 +42,7 @@ fn test_textinput_when_inserting_multiple_lines_over_a_selection_respects_max_le SelectionDirection::None, ); - textinput.edit_point = TextPoint { line: 0, index: 1 }; + textinput.adjust_horizontal(1, Selection::NotSelected); textinput.adjust_horizontal(3, Selection::Selected); textinput.adjust_vertical(1, Selection::Selected); @@ -67,8 +67,7 @@ fn test_textinput_when_inserting_multiple_lines_still_respects_max_length() { SelectionDirection::None ); - textinput.edit_point = TextPoint { line: 1, index: 0 }; - + textinput.adjust_vertical(1, Selection::NotSelected); textinput.insert_string("cruel\nterrible".to_string()); assert_eq!(textinput.get_content(), "hello\ncruel\nworld"); @@ -117,7 +116,7 @@ fn test_single_line_textinput_with_max_length_doesnt_allow_appending_characters_ SelectionDirection::None, ); - textinput.edit_point = TextPoint { line: 0, index: 1 }; + textinput.adjust_horizontal(1, Selection::NotSelected); textinput.adjust_horizontal(3, Selection::Selected); // Selection is now "abcde" @@ -220,9 +219,7 @@ fn test_textinput_delete_char() { assert_eq!(textinput.get_content(), "ab"); let mut textinput = text_input(Lines::Single, "abcdefg"); - textinput.adjust_horizontal(2, Selection::NotSelected); - // Set an empty selection range. - textinput.selection_origin = Some(textinput.edit_point); + textinput.set_selection_range(2, 2, SelectionDirection::None); textinput.delete_char(Direction::Backward); assert_eq!(textinput.get_content(), "acdefg"); } @@ -300,16 +297,16 @@ fn test_textinput_adjust_vertical() { let mut textinput = text_input(Lines::Multiple, "abc\nde\nf"); textinput.adjust_horizontal(3, Selection::NotSelected); textinput.adjust_vertical(1, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 1); - assert_eq!(textinput.edit_point.index, 2); + assert_eq!(textinput.edit_point().line, 1); + assert_eq!(textinput.edit_point().index, 2); textinput.adjust_vertical(-1, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 2); + assert_eq!(textinput.edit_point().line, 0); + assert_eq!(textinput.edit_point().index, 2); textinput.adjust_vertical(2, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 2); - assert_eq!(textinput.edit_point.index, 1); + assert_eq!(textinput.edit_point().line, 2); + assert_eq!(textinput.edit_point().index, 1); } #[test] @@ -317,32 +314,32 @@ fn test_textinput_adjust_vertical_multibyte() { let mut textinput = text_input(Lines::Multiple, "áé\nae"); textinput.adjust_horizontal_by_one(Direction::Forward, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 2); + assert_eq!(textinput.edit_point().line, 0); + assert_eq!(textinput.edit_point().index, 2); textinput.adjust_vertical(1, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 1); - assert_eq!(textinput.edit_point.index, 1); + assert_eq!(textinput.edit_point().line, 1); + assert_eq!(textinput.edit_point().index, 1); } #[test] fn test_textinput_adjust_horizontal() { let mut textinput = text_input(Lines::Multiple, "abc\nde\nf"); textinput.adjust_horizontal(4, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 1); - assert_eq!(textinput.edit_point.index, 0); + assert_eq!(textinput.edit_point().line, 1); + assert_eq!(textinput.edit_point().index, 0); textinput.adjust_horizontal(1, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 1); - assert_eq!(textinput.edit_point.index, 1); + assert_eq!(textinput.edit_point().line, 1); + assert_eq!(textinput.edit_point().index, 1); textinput.adjust_horizontal(2, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 2); - assert_eq!(textinput.edit_point.index, 0); + assert_eq!(textinput.edit_point().line, 2); + assert_eq!(textinput.edit_point().index, 0); textinput.adjust_horizontal(-1, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 1); - assert_eq!(textinput.edit_point.index, 2); + assert_eq!(textinput.edit_point().line, 1); + assert_eq!(textinput.edit_point().index, 2); } #[test] @@ -351,45 +348,45 @@ fn test_textinput_adjust_horizontal_by_word() { let mut textinput = text_input(Lines::Single, "abc def"); textinput.adjust_horizontal_by_word(Direction::Forward, Selection::NotSelected); textinput.adjust_horizontal_by_word(Direction::Forward, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 7); + assert_eq!(textinput.edit_point().line, 0); + assert_eq!(textinput.edit_point().index, 7); textinput.adjust_horizontal_by_word(Direction::Backward, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 4); + assert_eq!(textinput.edit_point().line, 0); + assert_eq!(textinput.edit_point().index, 4); textinput.adjust_horizontal_by_word(Direction::Backward, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 0); + assert_eq!(textinput.edit_point().line, 0); + assert_eq!(textinput.edit_point().index, 0); // Test new line case of movement word by word based on UAX#29 rules let mut textinput_2 = text_input(Lines::Multiple, "abc\ndef"); textinput_2.adjust_horizontal_by_word(Direction::Forward, Selection::NotSelected); textinput_2.adjust_horizontal_by_word(Direction::Forward, Selection::NotSelected); - assert_eq!(textinput_2.edit_point.line, 1); - assert_eq!(textinput_2.edit_point.index, 3); + assert_eq!(textinput_2.edit_point().line, 1); + assert_eq!(textinput_2.edit_point().index, 3); textinput_2.adjust_horizontal_by_word(Direction::Backward, Selection::NotSelected); - assert_eq!(textinput_2.edit_point.line, 1); - assert_eq!(textinput_2.edit_point.index, 0); + assert_eq!(textinput_2.edit_point().line, 1); + assert_eq!(textinput_2.edit_point().index, 0); textinput_2.adjust_horizontal_by_word(Direction::Backward, Selection::NotSelected); - assert_eq!(textinput_2.edit_point.line, 0); - assert_eq!(textinput_2.edit_point.index, 0); + assert_eq!(textinput_2.edit_point().line, 0); + assert_eq!(textinput_2.edit_point().index, 0); // Test non-standard sized characters case of movement word by word based on UAX#29 rules let mut textinput_3 = text_input(Lines::Single, "áéc d🌠bc"); textinput_3.adjust_horizontal_by_word(Direction::Forward, Selection::NotSelected); - assert_eq!(textinput_3.edit_point.line, 0); - assert_eq!(textinput_3.edit_point.index, 5); + assert_eq!(textinput_3.edit_point().line, 0); + assert_eq!(textinput_3.edit_point().index, 5); textinput_3.adjust_horizontal_by_word(Direction::Forward, Selection::NotSelected); - assert_eq!(textinput_3.edit_point.line, 0); - assert_eq!(textinput_3.edit_point.index, 7); + assert_eq!(textinput_3.edit_point().line, 0); + assert_eq!(textinput_3.edit_point().index, 7); textinput_3.adjust_horizontal_by_word(Direction::Forward, Selection::NotSelected); - assert_eq!(textinput_3.edit_point.line, 0); - assert_eq!(textinput_3.edit_point.index, 13); + assert_eq!(textinput_3.edit_point().line, 0); + assert_eq!(textinput_3.edit_point().index, 13); textinput_3.adjust_horizontal_by_word(Direction::Backward, Selection::NotSelected); - assert_eq!(textinput_3.edit_point.line, 0); - assert_eq!(textinput_3.edit_point.index, 11); + assert_eq!(textinput_3.edit_point().line, 0); + assert_eq!(textinput_3.edit_point().index, 11); textinput_3.adjust_horizontal_by_word(Direction::Backward, Selection::NotSelected); - assert_eq!(textinput_3.edit_point.line, 0); - assert_eq!(textinput_3.edit_point.index, 6); + assert_eq!(textinput_3.edit_point().line, 0); + assert_eq!(textinput_3.edit_point().index, 6); } #[test] @@ -397,29 +394,29 @@ fn test_textinput_adjust_horizontal_to_line_end() { // Test standard case of movement to end based on UAX#29 rules let mut textinput = text_input(Lines::Single, "abc def"); textinput.adjust_horizontal_to_line_end(Direction::Forward, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 7); + assert_eq!(textinput.edit_point().line, 0); + assert_eq!(textinput.edit_point().index, 7); // Test new line case of movement to end based on UAX#29 rules let mut textinput_2 = text_input(Lines::Multiple, "abc\ndef"); textinput_2.adjust_horizontal_to_line_end(Direction::Forward, Selection::NotSelected); - assert_eq!(textinput_2.edit_point.line, 0); - assert_eq!(textinput_2.edit_point.index, 3); + assert_eq!(textinput_2.edit_point().line, 0); + assert_eq!(textinput_2.edit_point().index, 3); textinput_2.adjust_horizontal_to_line_end(Direction::Forward, Selection::NotSelected); - assert_eq!(textinput_2.edit_point.line, 0); - assert_eq!(textinput_2.edit_point.index, 3); + assert_eq!(textinput_2.edit_point().line, 0); + assert_eq!(textinput_2.edit_point().index, 3); textinput_2.adjust_horizontal_to_line_end(Direction::Backward, Selection::NotSelected); - assert_eq!(textinput_2.edit_point.line, 0); - assert_eq!(textinput_2.edit_point.index, 0); + assert_eq!(textinput_2.edit_point().line, 0); + assert_eq!(textinput_2.edit_point().index, 0); // Test non-standard sized characters case of movement to end based on UAX#29 rules let mut textinput_3 = text_input(Lines::Single, "áéc d🌠bc"); textinput_3.adjust_horizontal_to_line_end(Direction::Forward, Selection::NotSelected); - assert_eq!(textinput_3.edit_point.line, 0); - assert_eq!(textinput_3.edit_point.index, 13); + assert_eq!(textinput_3.edit_point().line, 0); + assert_eq!(textinput_3.edit_point().index, 13); textinput_3.adjust_horizontal_to_line_end(Direction::Backward, Selection::NotSelected); - assert_eq!(textinput_3.edit_point.line, 0); - assert_eq!(textinput_3.edit_point.index, 0); + assert_eq!(textinput_3.edit_point().line, 0); + assert_eq!(textinput_3.edit_point().index, 0); } #[test] @@ -429,29 +426,29 @@ fn test_navigation_keyboard_shortcuts() { // Test that CMD + Right moves to the end of the current line. textinput.handle_keydown_aux(None, Key::Right, KeyModifiers::SUPER); - assert_eq!(textinput.edit_point.index, 11); + assert_eq!(textinput.edit_point().index, 11); // Test that CMD + Right moves to the beginning of the current line. textinput.handle_keydown_aux(None, Key::Left, KeyModifiers::SUPER); - assert_eq!(textinput.edit_point.index, 0); + assert_eq!(textinput.edit_point().index, 0); // Test that CTRL + ALT + E moves to the end of the current line also. textinput.handle_keydown_aux(None, Key::E, KeyModifiers::CONTROL | KeyModifiers::ALT); - assert_eq!(textinput.edit_point.index, 11); + assert_eq!(textinput.edit_point().index, 11); // Test that CTRL + ALT + A moves to the beginning of the current line also. textinput.handle_keydown_aux(None, Key::A, KeyModifiers::CONTROL | KeyModifiers::ALT); - assert_eq!(textinput.edit_point.index, 0); + assert_eq!(textinput.edit_point().index, 0); // Test that ALT + Right moves to the end of the word. textinput.handle_keydown_aux(None, Key::Right, KeyModifiers::ALT); - assert_eq!(textinput.edit_point.index, 5); + assert_eq!(textinput.edit_point().index, 5); // Test that CTRL + ALT + F moves to the end of the word also. textinput.handle_keydown_aux(None, Key::F, KeyModifiers::CONTROL | KeyModifiers::ALT); - assert_eq!(textinput.edit_point.index, 11); + assert_eq!(textinput.edit_point().index, 11); // Test that ALT + Left moves to the end of the word. textinput.handle_keydown_aux(None, Key::Left, KeyModifiers::ALT); - assert_eq!(textinput.edit_point.index, 6); + assert_eq!(textinput.edit_point().index, 6); // Test that CTRL + ALT + B moves to the end of the word also. textinput.handle_keydown_aux(None, Key::B, KeyModifiers::CONTROL | KeyModifiers::ALT); - assert_eq!(textinput.edit_point.index, 0); + assert_eq!(textinput.edit_point().index, 0); } #[test] @@ -470,12 +467,12 @@ fn test_textinput_handle_return() { #[test] fn test_textinput_select_all() { let mut textinput = text_input(Lines::Multiple, "abc\nde\nf"); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 0); + assert_eq!(textinput.edit_point().line, 0); + assert_eq!(textinput.edit_point().index, 0); textinput.select_all(); - assert_eq!(textinput.edit_point.line, 2); - assert_eq!(textinput.edit_point.index, 1); + assert_eq!(textinput.edit_point().line, 2); + assert_eq!(textinput.edit_point().index, 1); } #[test] @@ -495,15 +492,15 @@ fn test_textinput_set_content() { textinput.set_content(DOMString::from("abc\nf"), true); assert_eq!(textinput.get_content(), "abc\nf"); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 0); + assert_eq!(textinput.edit_point().line, 0); + assert_eq!(textinput.edit_point().index, 0); textinput.adjust_horizontal(3, Selection::Selected); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 3); + assert_eq!(textinput.edit_point().line, 0); + assert_eq!(textinput.edit_point().index, 3); textinput.set_content(DOMString::from("de"), true); assert_eq!(textinput.get_content(), "de"); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 2); + assert_eq!(textinput.edit_point().line, 0); + assert_eq!(textinput.edit_point().index, 2); } #[test] @@ -520,7 +517,7 @@ fn test_clipboard_paste() { None, SelectionDirection::None); assert_eq!(textinput.get_content(), "defg"); - assert_eq!(textinput.edit_point.index, 0); + assert_eq!(textinput.edit_point().index, 0); textinput.handle_keydown_aux(Some('v'), Key::V, MODIFIERS); assert_eq!(textinput.get_content(), "abcdefg"); } @@ -532,23 +529,23 @@ fn test_textinput_cursor_position_correct_after_clearing_selection() { // Single line - Forward textinput.adjust_horizontal(3, Selection::Selected); textinput.adjust_horizontal(1, Selection::NotSelected); - assert_eq!(textinput.edit_point.index, 3); + assert_eq!(textinput.edit_point().index, 3); textinput.adjust_horizontal(-3, Selection::NotSelected); textinput.adjust_horizontal(3, Selection::Selected); textinput.adjust_horizontal_by_one(Direction::Forward, Selection::NotSelected); - assert_eq!(textinput.edit_point.index, 3); + assert_eq!(textinput.edit_point().index, 3); // Single line - Backward textinput.adjust_horizontal(-3, Selection::NotSelected); textinput.adjust_horizontal(3, Selection::Selected); textinput.adjust_horizontal(-1, Selection::NotSelected); - assert_eq!(textinput.edit_point.index, 0); + assert_eq!(textinput.edit_point().index, 0); textinput.adjust_horizontal(-3, Selection::NotSelected); textinput.adjust_horizontal(3, Selection::Selected); textinput.adjust_horizontal_by_one(Direction::Backward, Selection::NotSelected); - assert_eq!(textinput.edit_point.index, 0); + assert_eq!(textinput.edit_point().index, 0); let mut textinput = text_input(Lines::Multiple, "abc\nde\nf"); @@ -556,27 +553,27 @@ fn test_textinput_cursor_position_correct_after_clearing_selection() { // Multiline - Forward textinput.adjust_horizontal(4, Selection::Selected); textinput.adjust_horizontal(1, Selection::NotSelected); - assert_eq!(textinput.edit_point.index, 0); - assert_eq!(textinput.edit_point.line, 1); + assert_eq!(textinput.edit_point().index, 0); + assert_eq!(textinput.edit_point().line, 1); textinput.adjust_horizontal(-4, Selection::NotSelected); textinput.adjust_horizontal(4, Selection::Selected); textinput.adjust_horizontal_by_one(Direction::Forward, Selection::NotSelected); - assert_eq!(textinput.edit_point.index, 0); - assert_eq!(textinput.edit_point.line, 1); + assert_eq!(textinput.edit_point().index, 0); + assert_eq!(textinput.edit_point().line, 1); // Multiline - Backward textinput.adjust_horizontal(-4, Selection::NotSelected); textinput.adjust_horizontal(4, Selection::Selected); textinput.adjust_horizontal(-1, Selection::NotSelected); - assert_eq!(textinput.edit_point.index, 0); - assert_eq!(textinput.edit_point.line, 0); + assert_eq!(textinput.edit_point().index, 0); + assert_eq!(textinput.edit_point().line, 0); textinput.adjust_horizontal(-4, Selection::NotSelected); textinput.adjust_horizontal(4, Selection::Selected); textinput.adjust_horizontal_by_one(Direction::Backward, Selection::NotSelected); - assert_eq!(textinput.edit_point.index, 0); - assert_eq!(textinput.edit_point.line, 0); + assert_eq!(textinput.edit_point().index, 0); + assert_eq!(textinput.edit_point().line, 0); } @@ -584,53 +581,53 @@ fn test_textinput_cursor_position_correct_after_clearing_selection() { fn test_textinput_set_selection_with_direction() { let mut textinput = text_input(Lines::Single, "abcdef"); textinput.set_selection_range(2, 6, SelectionDirection::Forward); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 6); - assert_eq!(textinput.selection_direction, SelectionDirection::Forward); + assert_eq!(textinput.edit_point().line, 0); + assert_eq!(textinput.edit_point().index, 6); + assert_eq!(textinput.selection_direction(), SelectionDirection::Forward); - assert!(textinput.selection_origin.is_some()); - assert_eq!(textinput.selection_origin.unwrap().line, 0); - assert_eq!(textinput.selection_origin.unwrap().index, 2); + assert!(textinput.selection_origin().is_some()); + assert_eq!(textinput.selection_origin().unwrap().line, 0); + assert_eq!(textinput.selection_origin().unwrap().index, 2); textinput.set_selection_range(2, 6, SelectionDirection::Backward); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 2); - assert_eq!(textinput.selection_direction, SelectionDirection::Backward); + assert_eq!(textinput.edit_point().line, 0); + assert_eq!(textinput.edit_point().index, 2); + assert_eq!(textinput.selection_direction(), SelectionDirection::Backward); - assert!(textinput.selection_origin.is_some()); - assert_eq!(textinput.selection_origin.unwrap().line, 0); - assert_eq!(textinput.selection_origin.unwrap().index, 6); + assert!(textinput.selection_origin().is_some()); + assert_eq!(textinput.selection_origin().unwrap().line, 0); + assert_eq!(textinput.selection_origin().unwrap().index, 6); textinput = text_input(Lines::Multiple, "\n\n"); textinput.set_selection_range(0, 1, SelectionDirection::Forward); - assert_eq!(textinput.edit_point.line, 1); - assert_eq!(textinput.edit_point.index, 0); - assert_eq!(textinput.selection_direction, SelectionDirection::Forward); + assert_eq!(textinput.edit_point().line, 1); + assert_eq!(textinput.edit_point().index, 0); + assert_eq!(textinput.selection_direction(), SelectionDirection::Forward); - assert!(textinput.selection_origin.is_some()); - assert_eq!(textinput.selection_origin.unwrap().line, 0); - assert_eq!(textinput.selection_origin.unwrap().index, 0); + assert!(textinput.selection_origin().is_some()); + assert_eq!(textinput.selection_origin().unwrap().line, 0); + assert_eq!(textinput.selection_origin().unwrap().index, 0); textinput = text_input(Lines::Multiple, "\n"); textinput.set_selection_range(0, 1, SelectionDirection::Forward); - assert_eq!(textinput.edit_point.line, 1); - assert_eq!(textinput.edit_point.index, 0); - assert_eq!(textinput.selection_direction, SelectionDirection::Forward); + assert_eq!(textinput.edit_point().line, 1); + assert_eq!(textinput.edit_point().index, 0); + assert_eq!(textinput.selection_direction(), SelectionDirection::Forward); - assert!(textinput.selection_origin.is_some()); - assert_eq!(textinput.selection_origin.unwrap().line, 0); - assert_eq!(textinput.selection_origin.unwrap().index, 0); + assert!(textinput.selection_origin().is_some()); + assert_eq!(textinput.selection_origin().unwrap().line, 0); + assert_eq!(textinput.selection_origin().unwrap().index, 0); } #[test] fn test_textinput_unicode_handling() { let mut textinput = text_input(Lines::Single, "éèùµ$£"); - assert_eq!(textinput.edit_point.index, 0); + assert_eq!(textinput.edit_point().index, 0); textinput.set_edit_point_index(1); - assert_eq!(textinput.edit_point.index, 2); + assert_eq!(textinput.edit_point().index, 2); textinput.set_edit_point_index(4); - assert_eq!(textinput.edit_point.index, 8); + assert_eq!(textinput.edit_point().index, 8); } #[test] diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index f8a55cc49d5..3a750e74cfb 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -39947,6 +39947,12 @@ {} ] ], + "mozilla/textcontrol-selection-cannot-exceed-content.html": [ + [ + "/_mozilla/mozilla/textcontrol-selection-cannot-exceed-content.html", + {} + ] + ], "mozilla/timeout-in-discarded-document.html": [ [ "/_mozilla/mozilla/timeout-in-discarded-document.html", @@ -71956,6 +71962,10 @@ "094fbd794b78520d6c2d8aae549557bed3529a7a", "testharness" ], + "mozilla/textcontrol-selection-cannot-exceed-content.html": [ + "f8b982ba0abb23f8e53ef28413e8da2f8dd7ca17", + "testharness" + ], "mozilla/timeout-in-discarded-document.html": [ "7ff88491f20ee72ed40aa5ce4e84840b98d5eef5", "testharness" diff --git a/tests/wpt/mozilla/tests/mozilla/textcontrol-selection-cannot-exceed-content.html b/tests/wpt/mozilla/tests/mozilla/textcontrol-selection-cannot-exceed-content.html new file mode 100644 index 00000000000..b162fba0ec5 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/textcontrol-selection-cannot-exceed-content.html @@ -0,0 +1,25 @@ + + +The selection bounds of a text control should never exceed the content + + + + + +