layout: Make lines non-phantom if they have inline padding/border/margin (#39058)

According to https://drafts.csswg.org/css-inline/#invisible-line-boxes,
if a line box contains non-zero inline-axis margins, padding or borders,
then it can't be phantom.

Therefore, this patch makes adds a `has_inline_pbm` flag to the line.
Note that we can't use the `has_content` flag, because that would add a
soft wrap opportunity between the padding/border/margin and the first
content of the line.

The patch also renames `InlineFormattingContext::had_inflow_content` to
`has_line_boxes`, which is what we care about for collapsing margins
through.

Testing: Adding new tests
Fixes: #39057

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2025-09-17 00:18:46 +02:00 committed by GitHub
parent 4451ce0ef1
commit 18b3e5fe21
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 416 additions and 15 deletions

View file

@ -333,6 +333,10 @@ struct LineUnderConstruction {
/// indicates that the next run that exceeds the line length can cause a line break.
has_content: bool,
/// Whether any active linebox has added some inline-axis padding, border or margin
/// to this line.
has_inline_pbm: bool,
/// Whether or not there are floats that did not fit on the current line. Before
/// the [`LineItem`]s of this line are laid out, these floats will need to be
/// placed directly below this line, but still as children of this line's Fragments.
@ -356,6 +360,7 @@ impl LineUnderConstruction {
start_position,
max_block_size: LineBlockSizes::zero(),
has_content: false,
has_inline_pbm: false,
has_floats_waiting_to_be_placed: false,
placement_among_floats: OnceCell::new(),
line_items: Vec::new(),
@ -558,6 +563,10 @@ struct UnbreakableSegmentUnderConstruction {
/// a line break.
has_content: bool,
/// Whether any active linebox has added some inline-axis padding, border or margin
/// to this line segment.
has_inline_pbm: bool,
/// The inline size of any trailing whitespace in this segment.
trailing_whitespace_size: Au,
}
@ -574,6 +583,7 @@ impl UnbreakableSegmentUnderConstruction {
line_items: Vec::new(),
inline_box_hierarchy_depth: None,
has_content: false,
has_inline_pbm: false,
trailing_whitespace_size: Au::zero(),
}
}
@ -732,8 +742,9 @@ pub(super) struct InlineFormattingContextLayout<'layout_data> {
/// is encountered.
pub have_deferred_soft_wrap_opportunity: bool,
/// Whether or not this InlineFormattingContext has processed any in flow content at all.
had_inflow_content: bool,
/// Whether or not this InlineFormattingContext contains line boxes, excluding
/// [phantom line boxes](https://drafts.csswg.org/css-inline-3/#phantom-line-box).
has_line_boxes: bool,
/// Whether or not the layout of this InlineFormattingContext depends on the block size
/// of its container for the purposes of flexbox layout.
@ -837,9 +848,15 @@ impl InlineFormattingContextLayout<'_> {
}
if inline_box.is_first_split {
self.current_line_segment.inline_size += inline_box_state.pbm.padding.inline_start +
inline_box_state.pbm.border.inline_start +
inline_box_state.pbm.margin.inline_start.auto_is(Au::zero);
let padding = inline_box_state.pbm.padding.inline_start;
let border = inline_box_state.pbm.border.inline_start;
let margin = inline_box_state.pbm.margin.inline_start.auto_is(Au::zero);
// We can't just check if the sum is zero because the margin can be negative,
// we need to check the values separately.
if !padding.is_zero() || !border.is_zero() || !margin.is_zero() {
self.current_line_segment.has_inline_pbm = true;
}
self.current_line_segment.inline_size += padding + border + margin;
self.current_line_segment
.line_items
.push(LineItem::InlineStartBoxPaddingBorderMargin(
@ -881,10 +898,15 @@ impl InlineFormattingContextLayout<'_> {
}
if inline_box_state.is_last_fragment {
let pbm_end = inline_box_state.pbm.padding.inline_end +
inline_box_state.pbm.border.inline_end +
inline_box_state.pbm.margin.inline_end.auto_is(Au::zero);
self.current_line_segment.inline_size += pbm_end;
let padding = inline_box_state.pbm.padding.inline_end;
let border = inline_box_state.pbm.border.inline_end;
let margin = inline_box_state.pbm.margin.inline_end.auto_is(Au::zero);
// We can't just check if the sum is zero because the margin can be negative,
// we need to check the values separately.
if !padding.is_zero() || !border.is_zero() || !margin.is_zero() {
self.current_line_segment.has_inline_pbm = true;
}
self.current_line_segment.inline_size += padding + border + margin;
self.current_line_segment
.line_items
.push(LineItem::InlineEndBoxPaddingBorderMargin(
@ -984,10 +1006,16 @@ impl InlineFormattingContextLayout<'_> {
justification_adjustment,
);
if line_to_layout.has_content {
// https://drafts.csswg.org/css-inline-3/#invisible-line-boxes
// > Line boxes that contain no text, no preserved white space, no inline boxes with non-zero
// > inline-axis margins, padding, or borders, and no other in-flow content (such as atomic
// > inlines or ruby annotations), and do not end with a forced line break are phantom line boxes.
// > Such boxes [...] must be treated as not existing for any other layout or rendering purpose.
if line_to_layout.has_content || line_to_layout.has_inline_pbm {
let baseline = baseline_offset + block_start_position;
self.baselines.first.get_or_insert(baseline);
self.baselines.last = Some(baseline);
self.has_line_boxes = true;
}
// If the line doesn't have any fragments, we don't need to add a containing fragment for it.
@ -1350,8 +1378,6 @@ impl InlineFormattingContextLayout<'_> {
SegmentContentFlags::empty(),
);
}
self.had_inflow_content = true;
}
pub(super) fn possibly_flush_deferred_forced_line_break(&mut self) {
@ -1486,7 +1512,6 @@ impl InlineFormattingContextLayout<'_> {
}
if !flags.is_collapsible_whitespace() {
self.current_line_segment.has_content = true;
self.had_inflow_content = true;
}
// This may or may not include the size of the strut depending on the quirks mode setting.
@ -1601,6 +1626,7 @@ impl InlineFormattingContextLayout<'_> {
self.current_line.line_items.extend(segment_items);
self.current_line.has_content |= self.current_line_segment.has_content;
self.current_line.has_inline_pbm |= self.current_line_segment.has_inline_pbm;
self.current_line_segment.reset();
}
@ -1792,7 +1818,7 @@ impl InlineFormattingContext {
linebreak_before_new_content: false,
deferred_br_clear: Clear::None,
have_deferred_soft_wrap_opportunity: false,
had_inflow_content: false,
has_line_boxes: false,
depends_on_block_constraints: false,
white_space_collapse: style_text.white_space_collapse,
text_wrap_mode: style_text.text_wrap_mode,
@ -1846,7 +1872,7 @@ impl InlineFormattingContext {
let mut collapsible_margins_in_children = CollapsedBlockMargins::zero();
let content_block_size = layout.current_line.start_position.block;
collapsible_margins_in_children.collapsed_through = !layout.had_inflow_content &&
collapsible_margins_in_children.collapsed_through = !layout.has_line_boxes &&
content_block_size.is_zero() &&
collapsible_with_parent_start_margin.0;