Merge pull request #2500 from lightpanda-io/parseHtmlAsChildren_assertion

parseHtmlAsChildren handling for unexpected dom (custom element callb…
This commit is contained in:
Pierre Tachoire
2026-05-20 14:00:58 +02:00
committed by GitHub
2 changed files with 45 additions and 3 deletions

View File

@@ -3644,10 +3644,16 @@ pub fn parseHtmlAsChildren(self: *Frame, node: *Node, html: []const u8) !void {
var parser = Parser.init(self.call_arena, node, self);
parser.parseFragment(html);
// https://github.com/servo/html5ever/issues/583
// html5ever wraps fragment output in an <html> element; unwrap so its
// children land directly on `node`. See https://github.com/servo/html5ever/issues/583.
// Because of custom element callbacks, the structure might not be what
// we expect, and nodes might be altogether removed. We deal with this in a
// few different places, but always the same way: leave it as-is.
const children = node._children orelse return;
const first = children.one;
lp.assert(first.is(Element.Html.Html) != null, "Frame.parseHtmlAsChildren root", .{ .type = first._type });
const first = children.first();
if (first.is(Element.Html.Html) == null) {
return;
}
node._children = first._children;
if (self.hasMutationObservers()) {

View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<body>
<script src="../testing.js"></script>
<!-- Frame.parseHtmlAsChildren extracts children from the temporary
<html> element the fragment parser leaves at the top of the parse
result. A custom element's connectedCallback fires synchronously
during that parse, so it can reach two levels up (this.parentNode is
the temporary <html>; this.parentNode.parentNode is the original
element receiving innerHTML) and replace the temporary wrapper with
a text node before the post-parse step inspects the result. -->
<script id="connected_callback_replaces_wrapper">
{
class WrapperReplacer extends HTMLElement {
connectedCallback() {
const wrapper = this.parentNode;
if (!wrapper) return;
const host = wrapper.parentNode;
if (!host) return;
host.removeChild(wrapper);
host.appendChild(document.createTextNode(''));
}
}
customElements.define('wrapper-replacer-1', WrapperReplacer);
const host = document.createElement('div');
document.body.appendChild(host);
host.innerHTML = '<wrapper-replacer-1></wrapper-replacer-1>';
// The point of this test is to reach this line without aborting.
testing.expectTrue(host.firstChild !== null);
}
</script>
</body>
</html>