From dc040dfc3767c9c23b9ad607a62bba9379626527 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Mon, 1 Dec 2025 15:37:08 +0300 Subject: [PATCH] add `insertAdjacentElement` and `insertAdjacentText` --- src/browser/webapi/Element.zig | 72 ++++++++++++---------------------- src/browser/webapi/Node.zig | 48 +++++++++++++++++++++++ 2 files changed, 73 insertions(+), 47 deletions(-) diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index c9c9c31f..4eb08af7 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -375,7 +375,7 @@ pub fn insertAdjacentHTML( const Parser = @import("../parser/Parser.zig"); var parser = Parser.init(page.call_arena, doc_node, page); - parser.parseFragment(html_or_xml); + parser.parse(html_or_xml); // Check if there's parsing error. if (parser.err) |_| return error.Invalid; @@ -390,52 +390,7 @@ pub fn insertAdjacentHTML( std.debug.assert(maybe_body_node != null); const body = maybe_body_node orelse return; - const self_node = self.asNode(); - // * `target_node` is `*Node` (where we actually insert), - // * `prev_node` is `?*Node`. - const target_node, const prev_node = blk: { - // Prefer case-sensitive match. - // "beforeend" was the most common case in my tests; we might adjust the order - // depending on which ones websites prefer most. - if (std.mem.eql(u8, position, "beforeend")) { - break :blk .{ self_node, null }; - } - - if (std.mem.eql(u8, position, "afterbegin")) { - // Get the first child; null indicates there are no children. - break :blk .{ self_node, self_node.firstChild() }; - } - - if (std.mem.eql(u8, position, "beforebegin")) { - // The node must have a parent node in order to use this variant. - const parent_node = self_node.parentNode() orelse return error.NoModificationAllowed; - // Parent cannot be Document. - switch (parent_node._type) { - .document, .document_fragment => return error.NoModificationAllowed, - else => {}, - } - - break :blk .{ parent_node, self_node }; - } - - if (std.mem.eql(u8, position, "afterend")) { - // The node must have a parent node in order to use this variant. - const parent_node = self_node.parentNode() orelse return error.NoModificationAllowed; - // Parent cannot be Document. - switch (parent_node._type) { - .document, .document_fragment => return error.NoModificationAllowed, - else => {}, - } - - // Get the next sibling or null; null indicates our node is the only one. - break :blk .{ parent_node, self_node.nextSibling() }; - } - - // Returned if: - // * position is not one of the four listed values. - // * The input is XML that is not well-formed. - return error.Syntax; - }; + const target_node, const prev_node = try self.asNode().findAdjacentNodes(position); var iter = body.childrenIterator(); while (iter.next()) |child_node| { @@ -443,6 +398,27 @@ pub fn insertAdjacentHTML( } } +pub fn insertAdjacentElement( + self: *Element, + position: []const u8, + element: *Element, + page: *Page, +) !void { + const target_node, const prev_node = try self.asNode().findAdjacentNodes(position); + _ = try target_node.insertBefore(element.asNode(), prev_node, page); +} + +pub fn insertAdjacentText( + self: *Element, + where: []const u8, + data: []const u8, + page: *Page, +) !void { + const text_node = try page.createTextNode(data); + const target_node, const prev_node = try self.asNode().findAdjacentNodes(where); + _ = try target_node.insertBefore(text_node, prev_node, page); +} + pub fn setAttributeNode(self: *Element, attr: *Attribute, page: *Page) !?*Attribute { if (attr._element) |el| { if (el == self) { @@ -1076,6 +1052,8 @@ pub const JsApi = struct { pub const shadowRoot = bridge.accessor(Element.getShadowRoot, null, .{}); pub const attachShadow = bridge.function(_attachShadow, .{ .dom_exception = true }); pub const insertAdjacentHTML = bridge.function(Element.insertAdjacentHTML, .{ .dom_exception = true }); + pub const insertAdjacentElement = bridge.function(Element.insertAdjacentElement, .{ .dom_exception = true }); + pub const insertAdjacentText = bridge.function(Element.insertAdjacentText, .{ .dom_exception = true }); const ShadowRootInit = struct { mode: []const u8, diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index ab0c28ec..1b686c86 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -115,6 +115,54 @@ pub fn is(self: *Node, comptime T: type) ?*T { return null; } +/// Given a position, returns target and previous nodes required for +/// insertAdjacentHTML, insertAdjacentElement and insertAdjacentText. +/// * `target_node` is `*Node` (where we actually insert), +/// * `previous_node` is `?*Node`. +pub fn findAdjacentNodes(self: *Node, position: []const u8) !struct { *Node, ?*Node } { + // Prefer case-sensitive match. + // "beforeend" was the most common case in my tests; we might adjust the order + // depending on which ones websites prefer most. + if (std.mem.eql(u8, position, "beforeend")) { + return .{ self, null }; + } + + if (std.mem.eql(u8, position, "afterbegin")) { + // Get the first child; null indicates there are no children. + return .{ self, self.firstChild() }; + } + + if (std.mem.eql(u8, position, "beforebegin")) { + // The node must have a parent node in order to use this variant. + const parent_node = self.parentNode() orelse return error.NoModificationAllowed; + // Parent cannot be Document. + switch (parent_node._type) { + .document, .document_fragment => return error.NoModificationAllowed, + else => {}, + } + + return .{ parent_node, self }; + } + + if (std.mem.eql(u8, position, "afterend")) { + // The node must have a parent node in order to use this variant. + const parent_node = self.parentNode() orelse return error.NoModificationAllowed; + // Parent cannot be Document. + switch (parent_node._type) { + .document, .document_fragment => return error.NoModificationAllowed, + else => {}, + } + + // Get the next sibling or null; null indicates our node is the only one. + return .{ parent_node, self.nextSibling() }; + } + + // Returned if: + // * position is not one of the four listed values. + // * The input is XML that is not well-formed. + return error.Syntax; +} + pub fn firstChild(self: *const Node) ?*Node { const children = self._children orelse return null; return children.first();