From ca1e80a397ab24d5bac6458a508da7c2f4858c70 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Mon, 13 Jul 2026 23:23:00 +0800 Subject: [PATCH] fix: Custom-element constructor parser endless recursion In a custom element, when this.innerHTML = '....' is called, we need to be careful to prevent endless recursion. The html5ever callback used to determine the context element should not invoke the custom-element constructor, else we'll enter an endless loop. This also fixes an ungating problem added with the new HttpClient when a waitForImport can block forever. Both issues were see on a WooCommerce site - though the HttpClient is only due to an earlier HttpClient refactor. --- src/browser/Frame.zig | 6 ++ src/browser/frame/node_factory.zig | 6 ++ src/browser/parser/Parser.zig | 20 ++++++ src/browser/parser/html5ever.zig | 1 + .../context_element_not_reconstructed.html | 69 +++++++++++++++++++ src/html5ever/lib.rs | 27 ++++++-- src/network/HttpClient.zig | 14 +++- 7 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 src/browser/tests/custom_elements/context_element_not_reconstructed.html diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index 16a03f23f..4e3a6de81 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -211,6 +211,12 @@ _customized_builtin_disconnected_callback_invoked: std.AutoHashMapUnmanaged(*Ele // The constructor can access this to get the element being upgraded. _upgrading_element: ?*Node = null, +// Set when materializing the fragment parser's context element. The element +// is never inserted into the tree so if its a custom element ,we must not run +// its constructor (else we'll end up in an endless loop if the constructor +// sets this.innerHTML = '...', which happens). +_skip_custom_element_upgrade: bool = false, + // List of custom elements that were created before their definition was registered _undefined_custom_elements: std.ArrayList(*Element.Html.Custom) = .{}, diff --git a/src/browser/frame/node_factory.zig b/src/browser/frame/node_factory.zig index 95dd36ec9..b93d4a36a 100644 --- a/src/browser/frame/node_factory.zig +++ b/src/browser/frame/node_factory.zig @@ -829,6 +829,12 @@ pub fn createElementNS(frame: *Frame, namespace: Element.Namespace, name: []cons ._definition = definition, }); + // Fragment-parse context element. It will not be inserted and + // we should not run the custom element's constructor. + if (frame._skip_custom_element_upgrade) { + return node; + } + const def = definition orelse { const element = node.as(Element); const custom = element.is(Element.Html.Custom).?; diff --git a/src/browser/parser/Parser.zig b/src/browser/parser/Parser.zig index 700dc5377..159dbe372 100644 --- a/src/browser/parser/Parser.zig +++ b/src/browser/parser/Parser.zig @@ -272,6 +272,7 @@ pub fn parseFragment(self: *Parser, html: []const u8) void { &self.container, self, createElementCallback, + createContextElementCallback, getDataCallback, appendCallback, parseErrorCallback, @@ -439,6 +440,25 @@ fn createXMLElementCallback(ctx: *anyopaque, data: *anyopaque, qname: h5e.QualNa return _createElementCallbackWithDefaultnamespace(ctx, data, qname, attributes, .xml); } +// html5ever_parse_fragment materializes the fragment's context element through +// this dedicated callback, never through createElementCallback. The context +// element is a throwaway: html5ever only queries its name (and, for a +//