Skip to content

Commit e3732e8

Browse files
committed
LibWeb: Support inserting non-inline elements into inline elements
Our layout tree requires that all containers either have inline or non-inline children. In order to support the layout of non-inline elements inside inline elements, we need to do a bit of tree restructuring. It effectively simulates temporarily closing all inline nodes, appending the block element, and resumes appending to the last open inline node. The acid1.txt expectation needed to be updated to reflect the fact that we now hoist its <p> elements out of the inline <form> they were in. Visually, the before and after situations for acid1.html are identical.
1 parent 1302ce5 commit e3732e8

11 files changed

+375
-96
lines changed

Libraries/LibWeb/Dump.cpp

+6-3
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ void dump_tree(StringBuilder& builder, Layout::Node const& layout_node, bool sho
210210
nonbox_color_on,
211211
identifier,
212212
color_off);
213-
builder.append("\n"sv);
214213
} else {
215214
auto& box = verify_cast<Layout::Box>(layout_node);
216215
StringView color_on = is<Layout::SVGBox>(box) ? svg_box_color_on : box_color_on;
@@ -334,10 +333,14 @@ void dump_tree(StringBuilder& builder, Layout::Node const& layout_node, bool sho
334333
}
335334
}
336335
}
337-
338-
builder.append("\n"sv);
339336
}
340337

338+
if (is<Layout::NodeWithStyleAndBoxModelMetrics>(layout_node)
339+
&& static_cast<Layout::NodeWithStyleAndBoxModelMetrics const&>(layout_node).continuation_of_node())
340+
builder.append(" continuation"sv);
341+
342+
builder.append("\n"sv);
343+
341344
if (layout_node.dom_node() && is<HTML::HTMLImageElement>(*layout_node.dom_node())) {
342345
if (auto image_data = static_cast<HTML::HTMLImageElement const&>(*layout_node.dom_node()).current_request().image_data()) {
343346
if (is<SVG::SVGDecodedImageData>(*image_data)) {

Libraries/LibWeb/HTML/HTMLElement.cpp

+19-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Copyright (c) 2018-2024, Andreas Kling <[email protected]>
3+
* Copyright (c) 2025, Jelle Raaijmakers <[email protected]>
34
*
45
* SPDX-License-Identifier: BSD-2-Clause
56
*/
@@ -540,13 +541,17 @@ int HTMLElement::offset_width() const
540541
// NOTE: Ensure that layout is up-to-date before looking at metrics.
541542
const_cast<DOM::Document&>(document()).update_layout();
542543

543-
// 1. If the element does not have any associated CSS layout box return zero and terminate this algorithm.
544-
if (!paintable_box())
544+
// 1. If the element does not have any associated box return zero and terminate this algorithm.
545+
auto* current_node = layout_node().ptr();
546+
if (!is<Layout::NodeWithStyleAndBoxModelMetrics>(current_node))
545547
return 0;
546548

547-
// 2. Return the width of the axis-aligned bounding box of the border boxes of all fragments generated by the element’s principal box,
548-
// ignoring any transforms that apply to the element and its ancestors.
549-
return paintable_box()->border_box_width().to_int();
549+
// 2. Return the unscaled width of the axis-aligned bounding box of the border boxes of all fragments generated by
550+
// the element’s principal box, ignoring any transforms that apply to the element and its ancestors.
551+
//
552+
// If the element’s principal box is an inline-level box which was "split" by a block-level descendant, also
553+
// include fragments generated by the block-level descendants, unless they are zero width or height.
554+
return static_cast<Layout::NodeWithStyleAndBoxModelMetrics const&>(*current_node).absolute_border_box_rect().width().to_int();
550555
}
551556

552557
// https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetheight
@@ -555,13 +560,17 @@ int HTMLElement::offset_height() const
555560
// NOTE: Ensure that layout is up-to-date before looking at metrics.
556561
const_cast<DOM::Document&>(document()).update_layout();
557562

558-
// 1. If the element does not have any associated CSS layout box return zero and terminate this algorithm.
559-
if (!paintable_box())
563+
// 1. If the element does not have any associated box return zero and terminate this algorithm.
564+
auto* current_node = layout_node().ptr();
565+
if (!is<Layout::NodeWithStyleAndBoxModelMetrics>(current_node))
560566
return 0;
561567

562-
// 2. Return the height of the axis-aligned bounding box of the border boxes of all fragments generated by the element’s principal box,
563-
// ignoring any transforms that apply to the element and its ancestors.
564-
return paintable_box()->border_box_height().to_int();
568+
// 2. Return the unscaled height of the axis-aligned bounding box of the border boxes of all fragments generated by
569+
// the element’s principal box, ignoring any transforms that apply to the element and its ancestors.
570+
//
571+
// If the element’s principal box is an inline-level box which was "split" by a block-level descendant, also
572+
// include fragments generated by the block-level descendants, unless they are zero width or height.
573+
return static_cast<Layout::NodeWithStyleAndBoxModelMetrics const&>(*current_node).absolute_border_box_rect().height().to_int();
565574
}
566575

567576
// https://html.spec.whatwg.org/multipage/links.html#cannot-navigate

Libraries/LibWeb/Layout/LayoutState.cpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,12 @@ void LayoutState::commit(Box& root)
223223
root.document().for_each_shadow_including_inclusive_descendant([&](DOM::Node& node) {
224224
node.clear_paintable();
225225
if (node.layout_node() && is<InlineNode>(node.layout_node())) {
226-
inline_nodes.set(static_cast<InlineNode*>(node.layout_node()));
226+
// Inline nodes might have a continuation chain; add all inline nodes that are part of it.
227+
for (GC::Ptr inline_node = static_cast<NodeWithStyleAndBoxModelMetrics*>(node.layout_node());
228+
inline_node; inline_node = inline_node->continuation_of_node()) {
229+
if (is<InlineNode>(*inline_node))
230+
inline_nodes.set(static_cast<InlineNode*>(inline_node.ptr()));
231+
}
227232
}
228233
return TraversalDecision::Continue;
229234
});

Libraries/LibWeb/Layout/Node.cpp

+49-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/*
22
* Copyright (c) 2018-2023, Andreas Kling <[email protected]>
33
* Copyright (c) 2021-2023, Sam Atkins <[email protected]>
4+
* Copyright (c) 2025, Jelle Raaijmakers <[email protected]>
45
*
56
* SPDX-License-Identifier: BSD-2-Clause
67
*/
@@ -13,7 +14,6 @@
1314
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
1415
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
1516
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
16-
#include <LibWeb/CSS/StyleValues/MathDepthStyleValue.h>
1717
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
1818
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
1919
#include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
@@ -30,7 +30,6 @@
3030
#include <LibWeb/Layout/TableWrapper.h>
3131
#include <LibWeb/Layout/TextNode.h>
3232
#include <LibWeb/Layout/Viewport.h>
33-
#include <LibWeb/Platform/FontPlugin.h>
3433

3534
namespace Web::Layout {
3635

@@ -1275,4 +1274,52 @@ CSS::UserSelect Node::user_select_used_value() const
12751274
return computed_value;
12761275
}
12771276

1277+
template<typename Callable>
1278+
static CSSPixelRect united_rect_for_continuation_chain(NodeWithStyleAndBoxModelMetrics const& start, Callable get_rect)
1279+
{
1280+
// Combine the absolute border box rects of all paintables of all nodes in the continuation chain. Without this, we
1281+
// calculate the wrong border box rect for inline nodes that were split because of block elements.
1282+
Optional<CSSPixelRect> result;
1283+
for (auto* node = &start; node; node = node->continuation_of_node()) {
1284+
for (auto const& paintable : node->paintables()) {
1285+
if (!is<Painting::PaintableBox>(paintable))
1286+
continue;
1287+
auto const& paintable_box = static_cast<Painting::PaintableBox const&>(paintable);
1288+
auto paintable_border_box_rect = get_rect(paintable_box);
1289+
if (!result.has_value())
1290+
result = paintable_border_box_rect;
1291+
else if (!paintable_border_box_rect.is_empty())
1292+
result->unite(paintable_border_box_rect);
1293+
}
1294+
}
1295+
return result.value_or({});
1296+
}
1297+
1298+
CSSPixelRect NodeWithStyleAndBoxModelMetrics::absolute_border_box_rect() const
1299+
{
1300+
return united_rect_for_continuation_chain(*this, [](auto const& paintable_box) {
1301+
return paintable_box.absolute_border_box_rect();
1302+
});
1303+
}
1304+
1305+
CSSPixelRect NodeWithStyleAndBoxModelMetrics::absolute_content_rect() const
1306+
{
1307+
return united_rect_for_continuation_chain(*this, [](auto const& paintable_box) {
1308+
return paintable_box.absolute_rect();
1309+
});
1310+
}
1311+
1312+
CSSPixelRect NodeWithStyleAndBoxModelMetrics::absolute_padding_box_rect() const
1313+
{
1314+
return united_rect_for_continuation_chain(*this, [](auto const& paintable_box) {
1315+
return paintable_box.absolute_padding_box_rect();
1316+
});
1317+
}
1318+
1319+
void NodeWithStyleAndBoxModelMetrics::visit_edges(Cell::Visitor& visitor)
1320+
{
1321+
Base::visit_edges(visitor);
1322+
visitor.visit(m_continuation_of_node);
1323+
}
1324+
12781325
}

Libraries/LibWeb/Layout/Node.h

+10
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,15 @@ class NodeWithStyleAndBoxModelMetrics : public NodeWithStyle {
259259
BoxModelMetrics& box_model() { return m_box_model; }
260260
BoxModelMetrics const& box_model() const { return m_box_model; }
261261

262+
GC::Ptr<NodeWithStyleAndBoxModelMetrics> continuation_of_node() const { return m_continuation_of_node; }
263+
void set_continuation_of_node(Badge<TreeBuilder>, GC::Ptr<NodeWithStyleAndBoxModelMetrics> node) { m_continuation_of_node = node; }
264+
265+
CSSPixelRect absolute_border_box_rect() const;
266+
CSSPixelRect absolute_content_rect() const;
267+
CSSPixelRect absolute_padding_box_rect() const;
268+
269+
virtual void visit_edges(Cell::Visitor& visitor) override;
270+
262271
protected:
263272
NodeWithStyleAndBoxModelMetrics(DOM::Document& document, DOM::Node* node, GC::Ref<CSS::ComputedProperties> style)
264273
: NodeWithStyle(document, node, style)
@@ -274,6 +283,7 @@ class NodeWithStyleAndBoxModelMetrics : public NodeWithStyle {
274283
virtual bool is_node_with_style_and_box_model_metrics() const final { return true; }
275284

276285
BoxModelMetrics m_box_model;
286+
GC::Ptr<NodeWithStyleAndBoxModelMetrics> m_continuation_of_node;
277287
};
278288

279289
template<>

0 commit comments

Comments
 (0)