LibWeb: Make TreeBuilder nicer to SVG foreignObject

This patch does two things:

1. Makes TreeBuilder never cross the foreignObject boundary when looking
   for an appropriate insertion parent. Before this change, we would
   sometimes make things inside the foreignObject DOM subtree have
   layout nodes outside the foreignObject.

2. Makes foreignObject boxes participate in the anonymous wrapping of
   inline-level boxes. This is particularly imporant for absolutely
   positioned elements inside foreignObject, which were previously
   getting incorrectly wrapped if there was any text (even empty)
   preceding the abspos element.
This commit is contained in:
Andreas Kling 2025-07-06 15:19:57 +02:00 committed by Andreas Kling
commit ddcb87fb40
Notes: github-actions[bot] 2025-07-09 12:38:08 +00:00
3 changed files with 47 additions and 1 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2018-2025, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
@ -77,6 +77,9 @@ static Layout::Node& insertion_parent_for_inline_node(Layout::NodeWithStyle& lay
if (is<FieldSetBox>(layout_parent))
return last_child_creating_anonymous_wrapper_if_needed(layout_parent);
if (layout_parent.is_svg_foreign_object_box())
return last_child_creating_anonymous_wrapper_if_needed(layout_parent);
if (layout_parent.display().is_inline_outside() && layout_parent.display().is_flow_inside())
return layout_parent;
@ -151,6 +154,9 @@ void TreeBuilder::insert_node_into_inline_or_block_ancestor(Layout::Node& node,
// Find the nearest ancestor that can host the node.
auto& nearest_insertion_ancestor = [&]() -> NodeWithStyle& {
for (auto& ancestor : m_ancestor_stack.in_reverse()) {
if (ancestor->is_svg_foreign_object_box())
return ancestor;
auto const& ancestor_display = ancestor->display();
// Out-of-flow nodes cannot be hosted in inline flow nodes.

View file

@ -0,0 +1,26 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x166 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x150 children: not-inline
SVGSVGBox <svg> at (8,8) content-size 300x150 [SVG] children: inline
TextNode <#text>
SVGForeignObjectBox <foreignObject> at (8,8) content-size 100x200 [BFC] children: not-inline
BlockContainer <(anonymous)> at (8,8) content-size 300x0 children: inline
TextNode <#text>
BlockContainer <div.el> at (8,8) content-size 50x60 positioned [BFC] children: not-inline
TextNode <#text>
TextNode <#text>
BlockContainer <(anonymous)> at (8,158) content-size 784x0 children: inline
TextNode <#text>
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x166]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x150]
SVGSVGPaintable (SVGSVGBox<svg>) [8,8 300x150] overflow: [8,8 300x200]
SVGForeignObjectPaintable (SVGForeignObjectBox<foreignObject>) [8,8 100x200]
PaintableWithLines (BlockContainer(anonymous)) [8,8 300x0]
PaintableWithLines (BlockContainer<DIV>.el) [8,8 50x60]
PaintableWithLines (BlockContainer(anonymous)) [8,158 784x0]
SC for Viewport<#document> [0,0 800x600] [children: 1] (z-index: auto)
SC for BlockContainer<HTML> [0,0 800x166] [children: 1] (z-index: auto)
SC for SVGForeignObjectBox<foreignObject> [8,8 100x200] [children: 0] (z-index: auto)

View file

@ -0,0 +1,14 @@
<!doctype HTML>
<style>
.el {
width: 50px;
height: 60px;
position: absolute;
}
</style>
<svg style="display: block">
<foreignObject width=100 height=200>
<div class="el"></div>
</foreignObject>
</svg>