LibWeb: Retain display: contents in ancestor stack for continuations

When restructuring inline nodes because of a block element insertion
during the layout tree build, we might end up with a `display: contents`
element in the ancestor stack that is not part of the actual layout
tree, since it's never actually used as a parent for any node. Because
we were only rewinding the ancestor stack with actual new layout nodes,
it became corrupted and layout nodes were added to the wrong parent.

This new logic leaves the ancestor stack intact, only replacing layout
nodes whenever a new one is created.

Fixes the sidebar on https://reddit.com.
Fixes #3590.
This commit is contained in:
Jelle Raaijmakers 2025-02-18 16:51:38 +01:00 committed by Jelle Raaijmakers
parent dd8cca180f
commit de7ca7b157
Notes: github-actions[bot] 2025-02-18 22:32:41 +00:00
3 changed files with 13 additions and 12 deletions

View file

@ -305,13 +305,11 @@ void TreeBuilder::restructure_block_node_in_inline_parent(NodeWithStyleAndBoxMod
}();
nearest_block_ancestor.set_children_are_inline(false);
// Unwind the ancestor stack to find the topmost inline ancestor.
// Find the topmost inline ancestor.
GC::Ptr<NodeWithStyleAndBoxModelMetrics> topmost_inline_ancestor;
for (auto* ancestor = &parent; ancestor; ancestor = ancestor->parent()) {
if (ancestor == &nearest_block_ancestor)
break;
if (ancestor == m_ancestor_stack.last())
m_ancestor_stack.take_last();
if (ancestor->is_inline())
topmost_inline_ancestor = static_cast<NodeWithStyleAndBoxModelMetrics*>(ancestor);
}
@ -320,7 +318,7 @@ void TreeBuilder::restructure_block_node_in_inline_parent(NodeWithStyleAndBoxMod
// We need to host the topmost inline ancestor and its previous siblings in an anonymous "before" wrapper. If an
// inline wrapper does not already exist, we create a new one and add it to the nearest block ancestor.
GC::Ptr<Node> before_wrapper;
if (auto last_child = nearest_block_ancestor.last_child(); last_child->is_anonymous() && last_child->children_are_inline()) {
if (auto* last_child = nearest_block_ancestor.last_child(); last_child->is_anonymous() && last_child->children_are_inline()) {
before_wrapper = last_child;
} else {
before_wrapper = nearest_block_ancestor.create_anonymous_wrapper();
@ -388,7 +386,12 @@ void TreeBuilder::restructure_block_node_in_inline_parent(NodeWithStyleAndBoxMod
current_parent->append_child(new_inline_node);
current_parent = new_inline_node;
// Stop recreating nodes when we've reached node's parent
// Replace the node in the ancestor stack with the new node.
auto& node_with_style = static_cast<NodeWithStyle&>(*inline_node);
if (auto stack_index = m_ancestor_stack.find_first_index(node_with_style); stack_index.has_value())
m_ancestor_stack[stack_index.release_value()] = new_inline_node;
// Stop recreating nodes when we've reached node's parent.
if (inline_node == &parent)
break;
}
@ -396,13 +399,6 @@ void TreeBuilder::restructure_block_node_in_inline_parent(NodeWithStyleAndBoxMod
after_wrapper->set_children_are_inline(true);
nearest_block_ancestor.append_child(after_wrapper);
}
// Rewind the ancestor stack
for (GC::Ptr<Node> inline_node = topmost_inline_ancestor; inline_node; inline_node = inline_node->last_child()) {
if (!is<NodeWithStyle>(*inline_node))
break;
m_ancestor_stack.append(static_cast<NodeWithStyle&>(*inline_node));
}
}
static bool is_ignorable_whitespace(Layout::Node const& node)

View file

@ -18,5 +18,7 @@
<b>foo</b><div><b>bar</b></div><b>baz</b>
<hr>
<span>foo</span><div>bar</div>
<hr>
<b>foo</b><div><b>bar</b></div><b>baz</b>
</body>
</html>

View file

@ -40,5 +40,8 @@
target2.setAttribute('style', null);
});
</script>
<!-- Block inside `display: contents` element -->
<hr>
<b>foo<div style="display: contents"><div>bar</div></div>baz</b>
</body>
</html>