mirror of
https://github.com/servo/servo.git
synced 2025-07-24 15:50:21 +01:00
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:
parent
53b0fa827d
commit
f1c291853e
8 changed files with 57 additions and 13 deletions
|
@ -220,7 +220,7 @@ impl<'a> TextRun {
|
||||||
if text.len() == 0 {
|
if text.len() == 0 {
|
||||||
return (glyphs, true);
|
return (glyphs, true);
|
||||||
}
|
}
|
||||||
*breaker = Some(LineBreakLeafIter::new(&text, 0));
|
*breaker = Some(LineBreakLeafIter::new(text, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
let breaker = breaker.as_mut().unwrap();
|
let breaker = breaker.as_mut().unwrap();
|
||||||
|
|
|
@ -21,7 +21,7 @@ use style::values::generics::text::LineHeight;
|
||||||
use style::values::specified::text::{TextAlignKeyword, TextDecorationLine};
|
use style::values::specified::text::{TextAlignKeyword, TextDecorationLine};
|
||||||
use style::Zero;
|
use style::Zero;
|
||||||
use webrender_api::FontInstanceKey;
|
use webrender_api::FontInstanceKey;
|
||||||
use xi_unicode::LineBreakLeafIter;
|
use xi_unicode::{linebreak_property, LineBreakLeafIter};
|
||||||
|
|
||||||
use super::float::PlacementAmongFloats;
|
use super::float::PlacementAmongFloats;
|
||||||
use super::CollapsibleWithParentStartMargin;
|
use super::CollapsibleWithParentStartMargin;
|
||||||
|
@ -44,6 +44,12 @@ use crate::style_ext::{
|
||||||
};
|
};
|
||||||
use crate::ContainingBlock;
|
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)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) struct InlineFormattingContext {
|
pub(crate) struct InlineFormattingContext {
|
||||||
pub(super) inline_level_boxes: Vec<ArcRefCell<InlineLevelBox>>,
|
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.
|
/// The line breaking state for this inline formatting context.
|
||||||
linebreaker: Option<LineBreakLeafIter>,
|
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
|
/// The currently white-space setting of this line. This is stored on the
|
||||||
/// [`InlineFormattingContextState`] because when a soft wrap opportunity is defined
|
/// [`InlineFormattingContextState`] because when a soft wrap opportunity is defined
|
||||||
/// by the boundary between two characters, the white-space property of their nearest
|
/// by the boundary between two characters, the white-space property of their nearest
|
||||||
|
@ -1248,6 +1259,7 @@ impl InlineFormattingContext {
|
||||||
linebreak_before_new_content: false,
|
linebreak_before_new_content: false,
|
||||||
white_space: containing_block.style.get_inherited_text().white_space,
|
white_space: containing_block.style.get_inherited_text().white_space,
|
||||||
linebreaker: None,
|
linebreaker: None,
|
||||||
|
have_deferred_soft_wrap_opportunity: false,
|
||||||
root_nesting_level: InlineContainerState {
|
root_nesting_level: InlineContainerState {
|
||||||
nested_block_size: line_height_from_style(layout_context, &containing_block.style),
|
nested_block_size: line_height_from_style(layout_context, &containing_block.style),
|
||||||
has_content: false,
|
has_content: false,
|
||||||
|
@ -1544,9 +1556,8 @@ impl IndependentFormattingContext {
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(linebreaker) = ifc.linebreaker.as_mut() {
|
// Defer a soft wrap opportunity for when we next process text content.
|
||||||
linebreaker.next(" ");
|
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() {
|
for (run_index, run) in runs.into_iter().enumerate() {
|
||||||
ifc.possibly_flush_deferred_forced_line_break();
|
ifc.possibly_flush_deferred_forced_line_break();
|
||||||
|
|
||||||
|
|
|
@ -382,6 +382,13 @@
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"soft-wrap-opportunity-after-replaced-content-crash.html": [
|
||||||
|
"2f2707f2d5d6039718e5f904a1cdeb05866959a4",
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"video-needs-layout-crash.html": [
|
"video-needs-layout-crash.html": [
|
||||||
"b8c52edf8272abc6f7c30372b1d37e9545d08c74",
|
"b8c52edf8272abc6f7c30372b1d37e9545d08c74",
|
||||||
[
|
[
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
[line-breaking-atomic-002.html]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[line-breaking-atomic-009.html]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[line-breaking-replaced-001.html]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[line-breaking-replaced-006.html]
|
|
||||||
expected: FAIL
|
|
|
@ -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>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue