LibWeb: Fix crash from SVG resource boxes outliving removed element

Problem: Crash when removing a <mask>, <clipPath>, or <pattern> element
that’s referenced via url(#id), and then GC’ing it.

Cause: <mask>, <clipPath>, and <pattern> are laid out as resource boxes
attached to the referencing element's layout subtree, rather than their
own. So those survive cleanup of their DOM ancestor. Once a removed
<mask>/<clipPath>/<pattern> is collected, its resource box’s weak DOM
node pointer is null. And dereferencing that trips an assert — both
while painting and while tearing down the layout tree.

Fix: When a <mask>, <clipPath>, or <pattern> is removed, request a full
layout-tree update while the element’s still alive. That drops the stale
resource boxes (whose url(#id) references no longer resolve) before the
node gets collected.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/10127
This commit is contained in:
sideshowbarker 2026-06-18 18:06:43 +09:00 committed by Jelle Raaijmakers
parent 66f313fe48
commit 4bbc5e0950
4 changed files with 48 additions and 0 deletions

View file

@ -269,6 +269,12 @@ void SVGElement::removed_from(IsSubtreeRoot is_subtree_root, Node* old_ancestor,
return;
remove_from_use_element_that_reference_this();
// A <mask>, <clipPath>, or <pattern> referenced via url(#id) is laid out as a resource box attached to the
// referencing element's layout subtree. So it outlives this element's own DOM node. Rebuild the layout tree
// while this element is still alive — so those stale resource boxes are dropped before this node is collected.
if (id().has_value())
document().set_needs_full_layout_tree_update(true);
}
void SVGElement::remove_from_use_element_that_reference_this()

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<svg width="200" height="200">
<rect width="100" height="100" clip-path="url(#c)"/>
</svg>
<svg width="0" height="0">
<clipPath id="c"><rect width="50" height="50"/></clipPath>
</svg>
<script>
internals.dumpDisplayList();
document.getElementById("c").remove();
internals.dumpDisplayList();
internals.gc();
internals.dumpDisplayList();
</script>

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<svg width="200" height="200">
<rect width="100" height="100" mask="url(#m)"/>
</svg>
<svg width="0" height="0">
<mask id="m"><rect width="100" height="100"/></mask>
</svg>
<script>
internals.dumpDisplayList();
document.getElementById("m").remove();
internals.dumpDisplayList();
internals.gc();
internals.dumpDisplayList();
</script>

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<svg width="200" height="200">
<rect width="100" height="100" fill="url(#p)"/>
</svg>
<svg width="0" height="0">
<pattern id="p" width="10" height="10"><rect width="5" height="5"/></pattern>
</svg>
<script>
internals.dumpDisplayList();
document.getElementById("p").remove();
internals.dumpDisplayList();
internals.gc();
internals.dumpDisplayList();
</script>