diff --git a/Tests/LibWeb/Layout/expected/block-and-inline/relpos-inline-elements.txt b/Tests/LibWeb/Layout/expected/block-and-inline/relpos-inline-elements.txt
new file mode 100644
index 0000000000..f775b22ad4
--- /dev/null
+++ b/Tests/LibWeb/Layout/expected/block-and-inline/relpos-inline-elements.txt
@@ -0,0 +1,31 @@
+Viewport <#document> at (0,0) content-size 800x600 children: not-inline
+ BlockContainer at (0,0) content-size 800x600 [BFC] children: not-inline
+ BlockContainer
at (8,8) content-size 784x17.46875 children: inline
+ line 0 width: 98, height: 17.46875, bottom: 17.46875, baseline: 13.53125
+ frag 0 from TextNode start: 0, length: 4, rect: [8,8 35.15625x17.46875]
+ "foo "
+ frag 1 from TextNode start: 0, length: 3, rect: [43,33 27.640625x17.46875]
+ "bar"
+ frag 2 from TextNode start: 0, length: 1, rect: [71,8 8x17.46875]
+ " "
+ frag 3 from TextNode start: 0, length: 3, rect: [54,58 27.203125x17.46875]
+ "baz"
+ TextNode <#text>
+ InlineNode
+ TextNode <#text>
+ TextNode <#text>
+ InlineNode
+ InlineNode
+ TextNode <#text>
+ TextNode <#text>
+
+PaintableWithLines (Viewport<#document>) [0,0 800x600]
+ PaintableWithLines (BlockContainer) [0,0 800x600]
+ PaintableWithLines (BlockContainer) [8,8 784x17.46875] overflow: [8,8 784x67.46875]
+ TextPaintable (TextNode<#text>)
+ InlinePaintable (InlineNode)
+ TextPaintable (TextNode<#text>)
+ TextPaintable (TextNode<#text>)
+ InlinePaintable (InlineNode)
+ InlinePaintable (InlineNode)
+ TextPaintable (TextNode<#text>)
diff --git a/Tests/LibWeb/Layout/input/block-and-inline/relpos-inline-elements.html b/Tests/LibWeb/Layout/input/block-and-inline/relpos-inline-elements.html
new file mode 100644
index 0000000000..dabe5600e3
--- /dev/null
+++ b/Tests/LibWeb/Layout/input/block-and-inline/relpos-inline-elements.html
@@ -0,0 +1,12 @@
+
+foo bar baz
diff --git a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp
index 51ddbeed77..956a947633 100644
--- a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp
+++ b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp
@@ -131,6 +131,71 @@ static CSSPixelRect measure_scrollable_overflow(Box const& box)
return scrollable_overflow_rect;
}
+void LayoutState::resolve_relative_positions(Vector const& paintables_with_lines)
+{
+ // This function resolves relative position offsets of all the boxes & fragments in the paint tree.
+ // It runs *after* the paint tree has been constructed, so it modifies paintable node & fragment offsets directly.
+
+ // Regular boxes (not line box fragments):
+ for (auto& it : used_values_per_layout_node) {
+ auto& used_values = *it.value;
+ auto& node = const_cast(used_values.node());
+
+ if (!node.is_box())
+ continue;
+
+ auto& paintable = static_cast(*node.paintable());
+ CSSPixelPoint offset;
+
+ if (used_values.containing_line_box_fragment.has_value()) {
+ // Atomic inline case:
+ // We know that `node` is an atomic inline because `containing_line_box_fragments` refers to the
+ // line box fragment in the parent block container that contains it.
+ auto const& containing_line_box_fragment = used_values.containing_line_box_fragment.value();
+ auto const& containing_block = *node.containing_block();
+ auto const& containing_block_paintable = verify_cast(*containing_block.paintable_box());
+ auto const& fragment = containing_block_paintable.line_boxes()[containing_line_box_fragment.line_box_index].fragments()[containing_line_box_fragment.fragment_index];
+
+ // The fragment has the final offset for the atomic inline, so we just need to copy it from there.
+ offset = fragment.offset();
+ } else {
+ // Not an atomic inline, much simpler case.
+ offset = used_values.offset;
+ }
+ // Apply relative position inset if appropriate.
+ if (node.computed_values().position() == CSS::Position::Relative) {
+ auto& inset = node.box_model().inset;
+ offset.translate_by(inset.left, inset.top);
+ }
+ paintable.set_offset(offset);
+ }
+
+ // Line box fragments:
+ for (auto const& paintable_with_lines : paintables_with_lines) {
+ for (auto const& line_box : paintable_with_lines.line_boxes()) {
+ for (auto& fragment : line_box.fragments()) {
+ auto const& fragment_node = fragment.layout_node();
+ if (!is(*fragment_node.parent()))
+ continue;
+ // Collect effective relative position offset from inline-flow parent chain.
+ CSSPixelPoint offset;
+ for (auto* ancestor = fragment_node.parent(); ancestor; ancestor = ancestor->parent()) {
+ if (!is(*ancestor))
+ break;
+ if (!ancestor->display().is_inline_outside() || !ancestor->display().is_flow_inside())
+ break;
+ if (ancestor->computed_values().position() == CSS::Position::Relative) {
+ auto const& ancestor_node = static_cast(*ancestor);
+ auto const& inset = ancestor_node.box_model().inset;
+ offset.translate_by(inset.left, inset.top);
+ }
+ }
+ const_cast(fragment).set_offset(fragment.offset().translated(offset));
+ }
+ }
+ }
+}
+
void LayoutState::commit()
{
// Only the top-level LayoutState should ever be committed.
@@ -158,7 +223,6 @@ void LayoutState::commit()
auto& paintable_box = const_cast(*box.paintable_box());
paintable_box.set_offset(used_values.offset);
paintable_box.set_content_size(used_values.content_width(), used_values.content_height());
- paintable_box.set_containing_line_box_fragment(used_values.containing_line_box_fragment);
if (used_values.override_borders_data().has_value()) {
paintable_box.set_override_borders_data(used_values.override_borders_data().value());
}
@@ -174,7 +238,11 @@ void LayoutState::commit()
}
}
- // Measure absolute rect of each line box. Also collect all text nodes.
+ resolve_relative_positions(paintables_with_lines);
+
+ // Make a pass over all the line boxes to:
+ // - Measure absolute rect of each line box.
+ // - Collect all text nodes, so we can create paintables for them later.
for (auto& paintable_with_lines : paintables_with_lines) {
for (auto& line_box : paintable_with_lines.line_boxes()) {
CSSPixelRect line_box_absolute_rect;
diff --git a/Userland/Libraries/LibWeb/Layout/LayoutState.h b/Userland/Libraries/LibWeb/Layout/LayoutState.h
index b599668d27..0715490181 100644
--- a/Userland/Libraries/LibWeb/Layout/LayoutState.h
+++ b/Userland/Libraries/LibWeb/Layout/LayoutState.h
@@ -172,6 +172,9 @@ struct LayoutState {
LayoutState const* m_parent { nullptr };
LayoutState const& m_root;
+
+private:
+ void resolve_relative_positions(Vector const&);
};
}
diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp
index e38c70dd3e..232aa498fd 100644
--- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp
+++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp
@@ -117,19 +117,7 @@ void PaintableBox::set_content_size(CSSPixelSize size)
CSSPixelPoint PaintableBox::effective_offset() const
{
- CSSPixelPoint offset;
- if (containing_block() && m_containing_line_box_fragment.has_value()) {
- auto& paintable_with_lines = *verify_cast(containing_block()->paintable_box());
- auto const& fragment = paintable_with_lines.line_boxes()[m_containing_line_box_fragment->line_box_index].fragments()[m_containing_line_box_fragment->fragment_index];
- offset = fragment.offset();
- } else {
- offset = m_offset;
- }
- if (layout_box().computed_values().position() == CSS::Position::Relative) {
- auto const& inset = layout_box().box_model().inset;
- offset.translate_by(inset.left, inset.top);
- }
- return offset;
+ return m_offset;
}
CSSPixelRect PaintableBox::compute_absolute_rect() const
@@ -176,11 +164,6 @@ CSSPixelRect PaintableBox::absolute_paint_rect() const
return *m_absolute_paint_rect;
}
-void PaintableBox::set_containing_line_box_fragment(Optional fragment_coordinate)
-{
- m_containing_line_box_fragment = move(fragment_coordinate);
-}
-
StackingContext* PaintableBox::enclosing_stacking_context()
{
for (auto* ancestor = layout_box().parent(); ancestor; ancestor = ancestor->parent()) {
diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h
index 3a788a4be9..58ad6ed342 100644
--- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h
+++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h
@@ -112,7 +112,6 @@ public:
Optional calculate_overflow_clipped_rect() const;
void set_overflow_data(OverflowData data) { m_overflow_data = move(data); }
- void set_containing_line_box_fragment(Optional);
StackingContext* stacking_context() { return m_stacking_context; }
StackingContext const* stacking_context() const { return m_stacking_context; }
@@ -201,9 +200,6 @@ private:
CSSPixelPoint m_offset;
CSSPixelSize m_content_size;
- // Some boxes hang off of line box fragments. (inline-block, inline-table, replaced, etc)
- Optional m_containing_line_box_fragment;
-
OwnPtr m_stacking_context;
Optional mutable m_absolute_rect;