LibWeb: Correctly calculate static position rect when absolutely

positioned element is a descendant of inline-block

Sets inline block offsets in InlineFormattingContext.cpp, but this is
not enough. When static position rect is calculated during layout,
not all ancestors of abspos box may have their offsets calculated yet
(more info here: https://github.com/LadybirdBrowser/ladybird/pull/2583#issuecomment-2507140272).
So now static position rect is calculated relative to static containing
block during layout and calculation relative to actual containing block
is done later in
FormattingContext::layout_absolutely_positioned_element.

Fixes wpt/css/CSS2/abspos/static-inside-inline-block.html
This commit is contained in:
stasoid 2025-03-06 18:10:38 +05:00 committed by Alexander Kalenik
parent 1821896ecf
commit a6935299eb
Notes: github-actions[bot] 2025-03-17 14:56:06 +00:00
11 changed files with 45 additions and 16 deletions

View file

@ -672,8 +672,7 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain
if (box.is_absolutely_positioned()) {
StaticPositionRect static_position;
auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space(box, *box.containing_block());
static_position.rect = { offset_to_static_parent.location().translated(0, m_y_offset_of_current_block_container.value()), { 0, 0 } };
static_position.rect = { { 0, m_y_offset_of_current_block_container.value() }, { 0, 0 } };
box_state.set_static_position_rect(static_position);
return;
}

View file

@ -2253,14 +2253,11 @@ StaticPositionRect FlexFormattingContext::calculate_static_position_rect(Box con
break;
}
auto absolute_position_of_flex_container = absolute_content_rect(flex_container()).location();
auto absolute_position_of_abspos_containing_block = absolute_content_rect(*box.containing_block()).location();
auto flex_container_width = is_row_layout() ? inner_main_size(m_flex_container_state) : inner_cross_size(m_flex_container_state);
auto flex_container_height = is_row_layout() ? inner_cross_size(m_flex_container_state) : inner_main_size(m_flex_container_state);
StaticPositionRect static_position_rect;
static_position_rect.rect = { absolute_position_of_flex_container - absolute_position_of_abspos_containing_block, { flex_container_width, flex_container_height } };
static_position_rect.rect = { { 0, 0 }, { flex_container_width, flex_container_height } };
static_position_rect.horizontal_alignment = is_row_layout() ? main_axis_alignment : cross_axis_alignment;
static_position_rect.vertical_alignment = is_row_layout() ? cross_axis_alignment : main_axis_alignment;
return static_position_rect;

View file

@ -1179,7 +1179,6 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el
box_state.margin_bottom = margin_bottom.to_px(box, width_of_containing_block);
}
// NOTE: This is different from content_box_rect_in_ancestor_coordinate_space() as this does *not* follow the containing block chain up, but rather the parent() chain.
CSSPixelRect FormattingContext::content_box_rect_in_static_position_ancestor_coordinate_space(Box const& box, Box const& ancestor_box) const
{
auto rect = content_box_rect(box);
@ -1261,6 +1260,8 @@ void FormattingContext::layout_absolutely_positioned_element(Box const& box, Ava
CSSPixelPoint used_offset;
auto static_position = m_state.get(box).static_position();
auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space(box, *box.containing_block());
static_position += offset_to_static_parent.location();
if (box.computed_values().inset().top().is_auto() && box.computed_values().inset().bottom().is_auto()) {
used_offset.set_y(static_position.y());

View file

@ -2632,8 +2632,7 @@ StaticPositionRect GridFormattingContext::calculate_static_position_rect(Box con
// layout_absolutely_positioned_element() defined for GFC knows how to handle this case.
StaticPositionRect static_position;
auto const& box_state = m_state.get(box);
auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space(box, *box.containing_block());
static_position.rect = { offset_to_static_parent.location().translated(0, 0), { box_state.content_width(), box_state.content_height() } };
static_position.rect = { { 0, 0 }, { box_state.content_width(), box_state.content_height() } };
return static_position;
}
}

View file

@ -411,6 +411,16 @@ void InlineFormattingContext::generate_line_boxes()
}
}
for (auto& line_box : line_boxes) {
for (auto& fragment : line_box.fragments()) {
if (fragment.layout_node().is_inline_block()) {
auto& box = as<Box>(fragment.layout_node());
auto& box_state = m_state.get_mutable(box);
box_state.set_content_offset(fragment.offset());
}
}
}
for (auto* box : absolute_boxes) {
auto& box_state = m_state.get_mutable(*box);
box_state.set_static_position_rect(calculate_static_position_rect(*box));
@ -494,9 +504,8 @@ StaticPositionRect InlineFormattingContext::calculate_static_position_rect(Box c
} else {
// Easy case: no previous sibling, we're at the top of the containing block.
}
auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space(box, *box.containing_block());
StaticPositionRect static_position_rect;
static_position_rect.rect = { offset_to_static_parent.location().translated(x, y), { 0, 0 } };
static_position_rect.rect = { { x, y }, { 0, 0 } };
return static_position_rect;
}

View file

@ -90,6 +90,7 @@ struct LayoutState {
void set_content_x(CSSPixels x) { offset.set_x(x); }
void set_content_y(CSSPixels y) { offset.set_y(y); }
// offset from top-left corner of content area of box's containing block to top-left corner of box's content area
CSSPixelPoint offset;
SizeConstraint width_constraint { SizeConstraint::None };

View file

@ -140,6 +140,7 @@ Box const* Node::containing_block() const
return nearest_ancestor_capable_of_forming_a_containing_block(*this);
}
// returns containing block this node would have had if its position was static
Box const* Node::static_position_containing_block() const
{
return nearest_ancestor_capable_of_forming_a_containing_block(*this);

View file

@ -1853,12 +1853,11 @@ CSSPixels TableFormattingContext::border_spacing_vertical() const
return computed_values.border_spacing_vertical().to_px(table_box());
}
StaticPositionRect TableFormattingContext::calculate_static_position_rect(Box const& box) const
StaticPositionRect TableFormattingContext::calculate_static_position_rect(Box const&) const
{
// FIXME: Implement static position calculation for table descendants instead of always returning a rectangle with zero position and size.
StaticPositionRect static_position;
auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space(box, *box.containing_block());
static_position.rect = { offset_to_static_parent.location(), { 0, 0 } };
static_position.rect = { { 0, 0 }, { 0, 0 } };
return static_position;
}

View file

@ -6,7 +6,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
Box <tbody> at (10,10) content-size 0x0 table-row-group children: not-inline
Box <tr> at (10,10) content-size 0x0 table-row children: not-inline
BlockContainer <(anonymous)> at (10,10) content-size 0x0 table-cell [BFC] children: not-inline
BlockContainer <td> at (9,9) content-size 0x0 positioned [BFC] children: not-inline
BlockContainer <td> at (11,11) content-size 0x0 positioned [BFC] children: not-inline
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x20]
@ -16,4 +16,4 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableBox (Box<TBODY>) [10,10 0x0]
PaintableBox (Box<TR>) [10,10 0x0]
PaintableWithLines (BlockContainer(anonymous)) [10,10 0x0]
PaintableWithLines (BlockContainer<TD>) [8,8 2x2]
PaintableWithLines (BlockContainer<TD>) [10,10 2x2]

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<title>Static position inside inline-block</title>
<link rel="author" title="Martin Robinson" href="mrobinson@igalia.com">
<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#abs-non-replaced-width" title="10.3.7 Absolutely positioned, non-replaced elements">
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div style="display: inline-block; width: 100px; height: 100px;"></div>
<div style="display: inline-block; width: 100px; height: 100px; background: red;">
<div style="width: 100px; height: 100px; background: green;"></div>
</div>
<div style="display: inline-block; width: 100px; height: 100px;"></div>

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<title>Static position inside inline-block</title>
<link rel="author" title="Martin Robinson" href="mrobinson@igalia.com">
<link rel="help" href="https://www.w3.org/TR/CSS22/visudet.html#abs-non-replaced-width" title="10.3.7 Absolutely positioned, non-replaced elements">
<link rel="match" href="../../../../../expected/wpt-import/css/CSS2/abspos/static-inside-inline-block-ref.html">
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div style="display: inline-block; width: 100px; height: 100px;"></div>
<div style="display: inline-block; width: 100px; height: 100px; background: red;">
<div style="position: absolute; width: 100px; height: 100px; background: green;"></div>
</div>
<div style="display: inline-block; width: 100px; height: 100px;"></div>