Stop sending " " to linebreaker for replaced content (#30740)

We previously sent a " " to the linebreaker in order to ensure that the
next text had a soft wrap opportunity at the start. Calling `next(" ")`
without waiting until the returned index was 1, violated some
invariants of linebreaker ultimately causing a panic.

Instead of using the linebreaker for this, simply keep a flag in the
IFC layout state, which avoids the problem entirely.

Fixes #30703.
This commit is contained in:
Martin Robinson 2023-11-30 15:46:14 +01:00 committed by GitHub
parent 53b0fa827d
commit f1c291853e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 57 additions and 13 deletions

View file

@ -220,7 +220,7 @@ impl<'a> TextRun {
if text.len() == 0 {
return (glyphs, true);
}
*breaker = Some(LineBreakLeafIter::new(&text, 0));
*breaker = Some(LineBreakLeafIter::new(text, 0));
}
let breaker = breaker.as_mut().unwrap();

View file

@ -21,7 +21,7 @@ use style::values::generics::text::LineHeight;
use style::values::specified::text::{TextAlignKeyword, TextDecorationLine};
use style::Zero;
use webrender_api::FontInstanceKey;
use xi_unicode::LineBreakLeafIter;
use xi_unicode::{linebreak_property, LineBreakLeafIter};
use super::float::PlacementAmongFloats;
use super::CollapsibleWithParentStartMargin;
@ -44,6 +44,12 @@ use crate::style_ext::{
};
use crate::ContainingBlock;
// These constants are the xi-unicode line breaking classes that are defined in
// `table.rs`. Unfortunately, they are only identified by number.
const XI_LINE_BREAKING_CLASS_GL: u8 = 12;
const XI_LINE_BREAKING_CLASS_WJ: u8 = 30;
const XI_LINE_BREAKING_CLASS_ZWJ: u8 = 40;
#[derive(Debug, Serialize)]
pub(crate) struct InlineFormattingContext {
pub(super) inline_level_boxes: Vec<ArcRefCell<InlineLevelBox>>,
@ -364,6 +370,11 @@ struct InlineFormattingContextState<'a, 'b> {
/// The line breaking state for this inline formatting context.
linebreaker: Option<LineBreakLeafIter>,
/// Whether or not a soft wrap opportunity is queued. Soft wrap opportunities are
/// queued after replaced content and they are processed when the next text content
/// is encountered.
have_deferred_soft_wrap_opportunity: bool,
/// The currently white-space setting of this line. This is stored on the
/// [`InlineFormattingContextState`] because when a soft wrap opportunity is defined
/// by the boundary between two characters, the white-space property of their nearest
@ -1248,6 +1259,7 @@ impl InlineFormattingContext {
linebreak_before_new_content: false,
white_space: containing_block.style.get_inherited_text().white_space,
linebreaker: None,
have_deferred_soft_wrap_opportunity: false,
root_nesting_level: InlineContainerState {
nested_block_size: line_height_from_style(layout_context, &containing_block.style),
has_content: false,
@ -1544,9 +1556,8 @@ impl IndependentFormattingContext {
true,
);
if let Some(linebreaker) = ifc.linebreaker.as_mut() {
linebreaker.next(" ");
}
// Defer a soft wrap opportunity for when we next process text content.
ifc.have_deferred_soft_wrap_opportunity = true;
}
}
@ -1666,6 +1677,31 @@ impl TextRun {
},
};
// We either have a soft wrap opportunity if specified by the breaker or if we are
// following replaced content.
let have_deferred_soft_wrap_opportunity =
mem::replace(&mut ifc.have_deferred_soft_wrap_opportunity, false);
let mut break_at_start = break_at_start || have_deferred_soft_wrap_opportunity;
// From https://www.w3.org/TR/css-text-3/#line-break-details:
//
// > For Web-compatibility there is a soft wrap opportunity before and after each
// > replaced element or other atomic inline, even when adjacent to a character that
// > would normally suppress them, including U+00A0 NO-BREAK SPACE. However, with
// > the exception of U+00A0 NO-BREAK SPACE, there must be no soft wrap opportunity
// > between atomic inlines and adjacent characters belonging to the Unicode GL, WJ,
// > or ZWJ line breaking classes.
if have_deferred_soft_wrap_opportunity {
if let Some(first_character) = self.text.chars().nth(0) {
let class = linebreak_property(first_character);
break_at_start = break_at_start &&
(first_character == '\u{00A0}' ||
(class != XI_LINE_BREAKING_CLASS_GL &&
class != XI_LINE_BREAKING_CLASS_WJ &&
class != XI_LINE_BREAKING_CLASS_ZWJ));
}
}
for (run_index, run) in runs.into_iter().enumerate() {
ifc.possibly_flush_deferred_forced_line_break();

View file

@ -382,6 +382,13 @@
]
]
},
"soft-wrap-opportunity-after-replaced-content-crash.html": [
"2f2707f2d5d6039718e5f904a1cdeb05866959a4",
[
null,
{}
]
],
"video-needs-layout-crash.html": [
"b8c52edf8272abc6f7c30372b1d37e9545d08c74",
[

View file

@ -1,2 +0,0 @@
[line-breaking-atomic-002.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[line-breaking-atomic-009.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[line-breaking-replaced-001.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[line-breaking-replaced-006.html]
expected: FAIL

View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<link rel="help" href="https://github.com/servo/servo/issues/30703">
<link rel="author" href="mailto:mrobinson@igalia.com">
<body>
<br><img>&nbsp;
</body>
</html>