From 2d3234b54db503b841feb839d79c61e6cd6db58b Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sat, 24 Jan 2026 10:04:19 +0800 Subject: [PATCH] Fix insertAdjacentHtml Handle a wider range of HTML, including invalid html, empty string, head only comment only, etc.. --- .../tests/document/insert_adjacent_html.html | 49 +++++++++++++++++++ src/browser/webapi/element/Html.zig | 27 +++++----- 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/browser/tests/document/insert_adjacent_html.html b/src/browser/tests/document/insert_adjacent_html.html index cd8d1b19..1d8bffbd 100644 --- a/src/browser/tests/document/insert_adjacent_html.html +++ b/src/browser/tests/document/insert_adjacent_html.html @@ -41,4 +41,53 @@ testing.expectEqual("DIV", newElement.tagName); testing.expectEqual("after begin", newElement.innerText); testing.expectEqual("afterbegin", newElement.className); + + const fuzzWrapper = document.createElement("div"); + fuzzWrapper.id = "fuzz-wrapper"; + document.body.appendChild(fuzzWrapper); + + const fuzzCases = [ + // These cases have no element (or empty body), so nothing is inserted + { name: "empty string", html: "", expectElements: 0 }, + { name: "comment only", html: "", expectElements: 0 }, + { name: "doctype only", html: "", expectElements: 0 }, + { name: "full empty doc", html: "", expectElements: 0 }, + + { name: "whitespace only", html: " ", expectElements: 0 }, + { name: "newlines only", html: "\n\n\n", expectElements: 0 }, + { name: "just text", html: "plain text", expectElements: 0 }, + // Head-only elements: Extracted from container + { name: "empty meta", html: "", expectElements: 1 }, + { name: "empty title", html: "", expectElements: 1 }, + { name: "empty head", html: "", expectElements: 0 }, // Container with no children + { name: "empty body", html: "", expectElements: 0 }, // Container with no children + { name: "empty html", html: "", expectElements: 0 }, // Container with no children + { name: "meta only", html: "", expectElements: 1 }, + { name: "title only", html: "Test", expectElements: 1 }, + { name: "link only", html: "", expectElements: 1 }, + { name: "meta and title", html: "Test", expectElements: 2 }, + { name: "script only", html: " diff --git a/src/browser/webapi/element/Html.zig b/src/browser/webapi/element/Html.zig index f6354355..2411e711 100644 --- a/src/browser/webapi/element/Html.zig +++ b/src/browser/webapi/element/Html.zig @@ -290,22 +290,23 @@ pub fn insertAdjacentHTML( return error.Invalid; } - // We always get it wrapped like so: - // { ... } - // None of the following can be null. - const maybe_html_node = doc_node.firstChild(); - lp.assert(maybe_html_node != null, "Html.insertAdjacentHTML null html", .{}); - const html_node = maybe_html_node orelse return; - - const maybe_body_node = html_node.lastChild(); - lp.assert(maybe_body_node != null, "Html.insertAdjacentHTML null bodys", .{}); - const body = maybe_body_node orelse return; + // The parser wraps content in a document structure: + // - Typical: ...... + // - Head-only: (no body) + // - Empty/comments: May have no element at all + const html_node = doc_node.firstChild() orelse return; const target_node, const prev_node = try self.asElement().asNode().findAdjacentNodes(position); - var iter = body.childrenIterator(); - while (iter.next()) |child_node| { - _ = try target_node.insertBefore(child_node, prev_node, page); + // Iterate through all children of (typically and/or ) + // and insert their children (not the containers themselves) into the target. + // This handles both body content AND head-only elements like , , etc. + var html_children = html_node.childrenIterator(); + while (html_children.next()) |container| { + var iter = container.childrenIterator(); + while (iter.next()) |child_node| { + _ = try target_node.insertBefore(child_node, prev_node, page); + } } }