Skip to content

Commit 49dd2f9

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 6a1654c commit 49dd2f9

14 files changed

+422
-141
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

+23-14
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
*/
@@ -463,7 +464,7 @@ int HTMLElement::offset_top() const
463464
if (!paintable_box())
464465
return 0;
465466

466-
CSSPixels top_border_edge_of_element = paintable_box()->absolute_border_box_rect().y();
467+
CSSPixels top_border_edge_of_element = paintable_box()->absolute_united_border_box_rect().y();
467468

468469
// 2. If the offsetParent of the element is null
469470
// return the y-coordinate of the top border edge of the first CSS layout box associated with the element,
@@ -487,7 +488,7 @@ int HTMLElement::offset_top() const
487488
if (offset_parent->is_html_body_element() && !offset_parent->paintable_box()->is_positioned()) {
488489
top_padding_edge_of_offset_parent = 0;
489490
} else {
490-
top_padding_edge_of_offset_parent = offset_parent->paintable_box()->absolute_padding_box_rect().y();
491+
top_padding_edge_of_offset_parent = offset_parent->paintable_box()->absolute_united_padding_box_rect().y();
491492
}
492493
return (top_border_edge_of_element - top_padding_edge_of_offset_parent).to_int();
493494
}
@@ -505,7 +506,7 @@ int HTMLElement::offset_left() const
505506
if (!paintable_box())
506507
return 0;
507508

508-
CSSPixels left_border_edge_of_element = paintable_box()->absolute_border_box_rect().x();
509+
CSSPixels left_border_edge_of_element = paintable_box()->absolute_united_border_box_rect().x();
509510

510511
// 2. If the offsetParent of the element is null
511512
// return the x-coordinate of the left border edge of the first CSS layout box associated with the element,
@@ -529,7 +530,7 @@ int HTMLElement::offset_left() const
529530
if (offset_parent->is_html_body_element() && !offset_parent->paintable_box()->is_positioned()) {
530531
left_padding_edge_of_offset_parent = 0;
531532
} else {
532-
left_padding_edge_of_offset_parent = offset_parent->paintable_box()->absolute_padding_box_rect().x();
533+
left_padding_edge_of_offset_parent = offset_parent->paintable_box()->absolute_united_padding_box_rect().x();
533534
}
534535
return (left_border_edge_of_element - left_padding_edge_of_offset_parent).to_int();
535536
}
@@ -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 const* box = paintable_box();
546+
if (!box)
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 box->absolute_united_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 const* box = paintable_box();
565+
if (!box)
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 box->absolute_united_border_box_rect().height().to_int();
565574
}
566575

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

Libraries/LibWeb/Layout/Box.h

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
#pragma once
88

99
#include <AK/OwnPtr.h>
10-
#include <LibGfx/Rect.h>
1110
#include <LibJS/Heap/Cell.h>
1211
#include <LibWeb/Layout/Node.h>
1312

Libraries/LibWeb/Layout/InlineNode.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
#include <LibWeb/DOM/Document.h>
1010
#include <LibWeb/DOM/Element.h>
11-
#include <LibWeb/Layout/BlockContainer.h>
1211
#include <LibWeb/Layout/InlineFormattingContext.h>
1312
#include <LibWeb/Layout/InlineNode.h>
1413

Libraries/LibWeb/Layout/LayoutState.cpp

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

Libraries/LibWeb/Layout/Node.cpp

+7-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,10 @@ CSS::UserSelect Node::user_select_used_value() const
12751274
return computed_value;
12761275
}
12771276

1277+
void NodeWithStyleAndBoxModelMetrics::visit_edges(Cell::Visitor& visitor)
1278+
{
1279+
Base::visit_edges(visitor);
1280+
visitor.visit(m_continuation_of_node);
1281+
}
1282+
12781283
}

Libraries/LibWeb/Layout/Node.h

+6
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,11 @@ 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+
virtual void visit_edges(Cell::Visitor& visitor) override;
266+
262267
protected:
263268
NodeWithStyleAndBoxModelMetrics(DOM::Document& document, DOM::Node* node, GC::Ref<CSS::ComputedProperties> style)
264269
: NodeWithStyle(document, node, style)
@@ -274,6 +279,7 @@ class NodeWithStyleAndBoxModelMetrics : public NodeWithStyle {
274279
virtual bool is_node_with_style_and_box_model_metrics() const final { return true; }
275280

276281
BoxModelMetrics m_box_model;
282+
GC::Ptr<NodeWithStyleAndBoxModelMetrics> m_continuation_of_node;
277283
};
278284

279285
template<>

0 commit comments

Comments
 (0)