Skip to content

Commit 5fb0f5a

Browse files
committed
parseHtmlAsChildren handling for unexpected dom (custom element callback)
Removes an assertion that can break with custom element callbacks. lightpanda-io#2429 does not solve this issue since it isn't a result of when reactions are executed, but just that they happen. (I should note, that I'm not 100% sure the above statement is correct. It's possible that our CE reactions are (in some cases at least) too micro. Maybe some operations, like setInnerHtml should operate within a more atomic frameworks, vs an CE-reaction per internal step. But that's a pretty big change)
1 parent 037db69 commit 5fb0f5a

2 files changed

Lines changed: 45 additions & 3 deletions

File tree

src/browser/Frame.zig

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3472,10 +3472,16 @@ pub fn parseHtmlAsChildren(self: *Frame, node: *Node, html: []const u8) !void {
34723472
var parser = Parser.init(self.call_arena, node, self);
34733473
parser.parseFragment(html);
34743474

3475-
// https://github.com/servo/html5ever/issues/583
3475+
// html5ever wraps fragment output in an <html> element; unwrap so its
3476+
// children land directly on `node`. See https://github.com/servo/html5ever/issues/583.
3477+
// Because of custom element callbacks, the structure might not be what
3478+
// we expect, and nodes might be altogether removed. We deal with this in a
3479+
// few different places, but always the same way: leave it as-is.
34763480
const children = node._children orelse return;
3477-
const first = children.one;
3478-
lp.assert(first.is(Element.Html.Html) != null, "Frame.parseHtmlAsChildren root", .{ .type = first._type });
3481+
const first = children.first();
3482+
if (first.is(Element.Html.Html) == null) {
3483+
return;
3484+
}
34793485
node._children = first._children;
34803486

34813487
if (self.hasMutationObservers()) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!DOCTYPE html>
2+
<body>
3+
<script src="../testing.js"></script>
4+
5+
<!-- Frame.parseHtmlAsChildren extracts children from the temporary
6+
<html> element the fragment parser leaves at the top of the parse
7+
result. A custom element's connectedCallback fires synchronously
8+
during that parse, so it can reach two levels up (this.parentNode is
9+
the temporary <html>; this.parentNode.parentNode is the original
10+
element receiving innerHTML) and replace the temporary wrapper with
11+
a text node before the post-parse step inspects the result. -->
12+
<script id="connected_callback_replaces_wrapper">
13+
{
14+
class WrapperReplacer extends HTMLElement {
15+
connectedCallback() {
16+
const wrapper = this.parentNode;
17+
if (!wrapper) return;
18+
const host = wrapper.parentNode;
19+
if (!host) return;
20+
host.removeChild(wrapper);
21+
host.appendChild(document.createTextNode(''));
22+
}
23+
}
24+
customElements.define('wrapper-replacer-1', WrapperReplacer);
25+
26+
const host = document.createElement('div');
27+
document.body.appendChild(host);
28+
host.innerHTML = '<wrapper-replacer-1></wrapper-replacer-1>';
29+
30+
// The point of this test is to reach this line without aborting.
31+
testing.expectTrue(host.firstChild !== null);
32+
}
33+
</script>
34+
</body>
35+
</html>
36+

0 commit comments

Comments
 (0)