script: Add a Rope type and use it for TextInput (#41650)

`TextInput` internally stores a rope of text content, one segment for
each line. This change separates out the rope into a separate `Rope`
data structure that can be shared with other parts of Servo.

`TextInput` needs to move through text content in a variety of ways,
depending on the keys pressed when interacting with a text area. This
change provides a unified movement API in both `Rope` and in
`TextInput` ensuring that selection is handled consistently (apart from
a few minor quirks [^1]). This simplifies the code an improves
interactive behavior.

[^1]: One of these quirks is that the edit point will move to
directional end of the motion when collapsing a selection, but only when
moving by grapheme or word (and not by line). These quirks existed in an
undocumented way previously and they are preserved with code comments.

Testing: This is covered by existing unit tests (updated for the new
API) and
the WPT suite.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson
2026-01-06 15:52:08 +01:00
committed by GitHub
parent 9ace4a16b3
commit 2efa7d5d96
10 changed files with 1058 additions and 1310 deletions

View File

@@ -6,6 +6,7 @@ use std::cell::Cell;
use std::default::Default;
use std::ops::Range;
use base::Lines;
use base::text::{Utf8CodeUnitLength, Utf16CodeUnitLength};
use dom_struct::dom_struct;
use embedder_traits::{EmbedderControlRequest, InputMethodRequest, InputMethodType};
@@ -47,7 +48,7 @@ use crate::dom::validitystate::{ValidationFlags, ValidityState};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
use crate::textinput::{
ClipboardEventFlags, Direction, IsComposing, KeyReaction, Lines, SelectionDirection, TextInput,
ClipboardEventFlags, IsComposing, KeyReaction, SelectionDirection, TextInput,
};
#[dom_struct]
@@ -383,7 +384,7 @@ impl HTMLTextAreaElementMethods<crate::DomTypeHolder> for HTMLTextAreaElement {
if old_value != textinput.get_content() {
// Step 4
textinput.clear_selection_to_limit(Direction::Forward);
textinput.clear_selection_to_end();
}
}