Skip to content

Commit

Permalink
LibWeb: Support inserting non-inline elements into inline elements
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
gmta committed Jan 16, 2025
1 parent 1302ce5 commit 92e2698
Show file tree
Hide file tree
Showing 11 changed files with 375 additions and 96 deletions.
9 changes: 6 additions & 3 deletions Libraries/LibWeb/Dump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,6 @@ void dump_tree(StringBuilder& builder, Layout::Node const& layout_node, bool sho
nonbox_color_on,
identifier,
color_off);
builder.append("\n"sv);
} else {
auto& box = verify_cast<Layout::Box>(layout_node);
StringView color_on = is<Layout::SVGBox>(box) ? svg_box_color_on : box_color_on;
Expand Down Expand Up @@ -334,10 +333,14 @@ void dump_tree(StringBuilder& builder, Layout::Node const& layout_node, bool sho
}
}
}

builder.append("\n"sv);
}

if (is<Layout::NodeWithStyleAndBoxModelMetrics>(layout_node)
&& static_cast<Layout::NodeWithStyleAndBoxModelMetrics const&>(layout_node).continuation_of_node())
builder.append(" continuation"sv);

builder.append("\n"sv);

if (layout_node.dom_node() && is<HTML::HTMLImageElement>(*layout_node.dom_node())) {
if (auto image_data = static_cast<HTML::HTMLImageElement const&>(*layout_node.dom_node()).current_request().image_data()) {
if (is<SVG::SVGDecodedImageData>(*image_data)) {
Expand Down
29 changes: 19 additions & 10 deletions Libraries/LibWeb/HTML/HTMLElement.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2018-2024, Andreas Kling <[email protected]>
* Copyright (c) 2025, Jelle Raaijmakers <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
Expand Down Expand Up @@ -540,13 +541,17 @@ int HTMLElement::offset_width() const
// NOTE: Ensure that layout is up-to-date before looking at metrics.
const_cast<DOM::Document&>(document()).update_layout();

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

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

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

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

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

// https://html.spec.whatwg.org/multipage/links.html#cannot-navigate
Expand Down
7 changes: 6 additions & 1 deletion Libraries/LibWeb/Layout/LayoutState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,12 @@ void LayoutState::commit(Box& root)
root.document().for_each_shadow_including_inclusive_descendant([&](DOM::Node& node) {
node.clear_paintable();
if (node.layout_node() && is<InlineNode>(node.layout_node())) {
inline_nodes.set(static_cast<InlineNode*>(node.layout_node()));
// Inline nodes might have a continuation chain; add all inline nodes that are part of it.
for (GC::Ptr inline_node = static_cast<NodeWithStyleAndBoxModelMetrics*>(node.layout_node());
inline_node; inline_node = inline_node->continuation_of_node()) {
if (is<InlineNode>(*inline_node))
inline_nodes.set(static_cast<InlineNode*>(inline_node.ptr()));
}
}
return TraversalDecision::Continue;
});
Expand Down
51 changes: 49 additions & 2 deletions Libraries/LibWeb/Layout/Node.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2018-2023, Andreas Kling <[email protected]>
* Copyright (c) 2021-2023, Sam Atkins <[email protected]>
* Copyright (c) 2025, Jelle Raaijmakers <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
Expand All @@ -13,7 +14,6 @@
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
#include <LibWeb/CSS/StyleValues/MathDepthStyleValue.h>
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
#include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
Expand All @@ -30,7 +30,6 @@
#include <LibWeb/Layout/TableWrapper.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Platform/FontPlugin.h>

namespace Web::Layout {

Expand Down Expand Up @@ -1168,6 +1167,12 @@ bool NodeWithStyle::is_scroll_container() const
|| overflow_value_makes_box_a_scroll_container(computed_values().overflow_y());
}

void NodeWithStyleAndBoxModelMetrics::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_continuation_of_node);
}

void Node::add_paintable(GC::Ptr<Painting::Paintable> paintable)
{
if (!paintable)
Expand Down Expand Up @@ -1275,4 +1280,46 @@ CSS::UserSelect Node::user_select_used_value() const
return computed_value;
}

template<typename Callable>
static CSSPixelRect united_rect_for_continuation_chain(NodeWithStyleAndBoxModelMetrics const& start, Callable get_rect)
{
// Combine the absolute border box rects of all paintables of all nodes in the continuation chain. Without this, we
// calculate the wrong border box rect for inline nodes that were split because of block elements.
Optional<CSSPixelRect> result;
for (auto* node = &start; node; node = node->continuation_of_node()) {
for (auto const& paintable : node->paintables()) {
if (!is<Painting::PaintableBox>(paintable))
continue;
auto const& paintable_box = static_cast<Painting::PaintableBox const&>(paintable);
auto paintable_border_box_rect = get_rect(paintable_box);
if (!result.has_value())
result = paintable_border_box_rect;
else if (!paintable_border_box_rect.is_empty())
result->unite(paintable_border_box_rect);
}
}
return result.value_or({});
}

CSSPixelRect NodeWithStyleAndBoxModelMetrics::absolute_border_box_rect() const
{
return united_rect_for_continuation_chain(*this, [](auto const& paintable_box) {
return paintable_box.absolute_border_box_rect();
});
}

CSSPixelRect NodeWithStyleAndBoxModelMetrics::absolute_content_rect() const
{
return united_rect_for_continuation_chain(*this, [](auto const& paintable_box) {
return paintable_box.absolute_rect();
});
}

CSSPixelRect NodeWithStyleAndBoxModelMetrics::absolute_padding_box_rect() const
{
return united_rect_for_continuation_chain(*this, [](auto const& paintable_box) {
return paintable_box.absolute_padding_box_rect();
});
}

}
10 changes: 10 additions & 0 deletions Libraries/LibWeb/Layout/Node.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,15 @@ class NodeWithStyleAndBoxModelMetrics : public NodeWithStyle {
BoxModelMetrics& box_model() { return m_box_model; }
BoxModelMetrics const& box_model() const { return m_box_model; }

GC::Ptr<NodeWithStyleAndBoxModelMetrics> continuation_of_node() const { return m_continuation_of_node; }
void set_continuation_of_node(Badge<TreeBuilder>, GC::Ptr<NodeWithStyleAndBoxModelMetrics> node) { m_continuation_of_node = node; }

CSSPixelRect absolute_border_box_rect() const;
CSSPixelRect absolute_content_rect() const;
CSSPixelRect absolute_padding_box_rect() const;

virtual void visit_edges(Cell::Visitor& visitor) override;

protected:
NodeWithStyleAndBoxModelMetrics(DOM::Document& document, DOM::Node* node, GC::Ref<CSS::ComputedProperties> style)
: NodeWithStyle(document, node, style)
Expand All @@ -274,6 +283,7 @@ class NodeWithStyleAndBoxModelMetrics : public NodeWithStyle {
virtual bool is_node_with_style_and_box_model_metrics() const final { return true; }

BoxModelMetrics m_box_model;
GC::Ptr<NodeWithStyleAndBoxModelMetrics> m_continuation_of_node;
};

template<>
Expand Down
Loading

0 comments on commit 92e2698

Please sign in to comment.