fix: Crashing due to input element hack (#36461)

This PR addresses a crash in text input element due to a hack to prevent
text input being trimmed.

The updated behavior will only add `zero width space` unicode character
to the node if there's no text content instead of adding it constantly.
Also by adding the same `zero width space` unicode character to text
area when there's no text content within will allow text area element to
properly display caret.

<img width="197" alt="截圖 2025-04-11 中午12 51 00"
src="https://github.com/user-attachments/assets/10bc7314-9aa3-49df-baac-ef26d39a96d8"
/>

This PR also addresses issues with multiple glyph runs:


https://github.com/user-attachments/assets/658db56f-b166-47ec-bc77-c6c13114d669

Testing: This PR is tested using:
- `./mach run -d 'data:text/html,<input id="input_element"
value="xxxxxxxxxxxxxxxxxxxx">'`
- `./mach run -d 'data:text/html,<textarea id="input_element"
value="xxxxxxxxxxxxxxxxxxxx">'`

without causing crashes, while the results should be covered by WPT
tests

Fixes: https://github.com/servo/servo/issues/36449

---------

Signed-off-by: DK Liao <dklassic@gmail.com>
This commit is contained in:
DK Liao 2025-04-11 15:51:52 +09:00 committed by GitHub
parent 972ca77ce1
commit 5df4c760d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 14 additions and 11 deletions

View file

@ -1223,9 +1223,7 @@ fn glyphs(
glyphs glyphs
} }
// TODO: This implementation has not been tested against `TextFragment` with mutiple runs. // TODO: The implementation here does not account for multiple glyph runs properly.
// It is possible that the `glyphs` function above will need to be modified to
// handle multiple runs correctly.
fn glyphs_advance_by_index( fn glyphs_advance_by_index(
glyph_runs: &[Arc<GlyphStore>], glyph_runs: &[Arc<GlyphStore>],
index: fonts_traits::ByteIndex, index: fonts_traits::ByteIndex,
@ -1233,11 +1231,13 @@ fn glyphs_advance_by_index(
justification_adjustment: Au, justification_adjustment: Au,
) -> PhysicalPoint<Au> { ) -> PhysicalPoint<Au> {
let mut point = baseline_origin; let mut point = baseline_origin;
let mut index = index;
for run in glyph_runs { for run in glyph_runs {
let total_advance = run.advance_for_byte_range( let total_advance = run.advance_for_byte_range(
&ServoRange::new(fonts::ByteIndex(0), index), &ServoRange::new(fonts::ByteIndex(0), index.min(run.len())),
justification_adjustment, justification_adjustment,
); );
index = index - index.min(run.len());
point.x += total_advance; point.x += total_advance;
} }
point point

View file

@ -212,12 +212,17 @@ fn traverse_children_of<'dom, Node>(
if is_text_input_element || is_textarea_element { if is_text_input_element || is_textarea_element {
let info = NodeAndStyleInfo::new(parent_element, parent_element.style(context)); let info = NodeAndStyleInfo::new(parent_element, parent_element.style(context));
if is_text_input_element { if parent_element
.to_threadsafe()
.node_text_content()
.is_empty()
{
// The addition of zero-width space here forces the text input to have an inline formatting // The addition of zero-width space here forces the text input to have an inline formatting
// context that might otherwise be trimmed if there's no text. This is important to ensure // context that might otherwise be trimmed if there's no text. This is important to ensure
// that the input element is at least as tall as the line gap of the caret: // that the input element is at least as tall as the line gap of the caret:
// <https://drafts.csswg.org/css-ui/#element-with-default-preferred-size>. // <https://drafts.csswg.org/css-ui/#element-with-default-preferred-size>.
// //
// This is also used to ensure that the caret will still be rendered when the input is empty.
// TODO: Is there a less hacky way to do this? // TODO: Is there a less hacky way to do this?
handler.handle_text(&info, "\u{200B}".into()); handler.handle_text(&info, "\u{200B}".into());
} }

View file

@ -166,7 +166,7 @@ impl TextRunSegment {
self.bidi_level, self.bidi_level,
ServoRange::<ByteIndex>::new( ServoRange::<ByteIndex>::new(
byte_processed + ByteIndex(self.range.start as isize), byte_processed + ByteIndex(self.range.start as isize),
run.range.length(), ByteIndex(self.range.len() as isize) - byte_processed,
), ),
); );
byte_processed = byte_processed + run.range.length(); byte_processed = byte_processed + run.range.length();

View file

@ -2,8 +2,8 @@
[.target > * 9] [.target > * 9]
expected: FAIL expected: FAIL
[.target > * 1]
expected: FAIL
[.target > * 11] [.target > * 11]
expected: FAIL expected: FAIL
[.target > * 7]
expected: FAIL

View file

@ -1,2 +0,0 @@
[input-type-button-clip.html]
expected: FAIL