From c0da6994dab1a8615e211168d56aa479b559631d Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 3 Dec 2025 08:52:51 +0800 Subject: [PATCH] Element.setInnerText --- src/browser/dump.zig | 34 +++++++++++++++++++++++++++- src/browser/tests/element/inner.html | 33 +++++++++++++++++++++++++++ src/browser/webapi/Element.zig | 22 +++++++++++++++++- 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/src/browser/dump.zig b/src/browser/dump.zig index 73ebe42b..e1feb57f 100644 --- a/src/browser/dump.zig +++ b/src/browser/dump.zig @@ -64,7 +64,7 @@ pub fn root(opts: RootOpts, writer: *std.Io.Writer, page: *Page) !void { pub fn deep(node: *Node, opts: Opts, writer: *std.Io.Writer, page: *Page) error{WriteFailed}!void { switch (node._type) { - .cdata => |cd| try writer.writeAll(cd.getData()), + .cdata => |cd| try writeEscapedText(cd.getData(), writer), .element => |el| { if (shouldStripElement(el, opts)) { return; @@ -211,3 +211,35 @@ fn shouldStripElement(el: *const Node.Element, opts: Opts) bool { return false; } + +fn writeEscapedText(text: []const u8, writer: *std.Io.Writer) !void { + // Fast path: if no special characters, write directly + const first_special = std.mem.indexOfAny(u8, text, "&<>") orelse { + return writer.writeAll(text); + }; + + try writer.writeAll(text[0..first_special]); + try writer.writeAll(switch (text[first_special]) { + '&' => "&", + '<' => "<", + '>' => ">", + else => unreachable, + }); + + // Process remaining text + var remaining = text[first_special + 1 ..]; + while (std.mem.indexOfAny(u8, remaining, "&<>")) |offset| { + try writer.writeAll(remaining[0..offset]); + try writer.writeAll(switch (remaining[offset]) { + '&' => "&", + '<' => "<", + '>' => ">", + else => unreachable, + }); + remaining = remaining[offset + 1 ..]; + } + + if (remaining.len > 0) { + try writer.writeAll(remaining); + } +} diff --git a/src/browser/tests/element/inner.html b/src/browser/tests/element/inner.html index da2aa5c6..b8023122 100644 --- a/src/browser/tests/element/inner.html +++ b/src/browser/tests/element/inner.html @@ -129,3 +129,36 @@ d1.innerHTML = '


'; testing.expectEqual('


', d1.innerHTML); + + diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 9bf36ab3..40240953 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -229,6 +229,26 @@ pub fn getInnerText(self: *Element, writer: *std.Io.Writer) !void { } } +pub fn setInnerText(self: *Element, text: []const u8, page: *Page) !void { + const parent = self.asNode(); + + // Remove all existing children + page.domChanged(); + var it = parent.childrenIterator(); + while (it.next()) |child| { + page.removeNode(parent, child, .{ .will_be_reconnected = false }); + } + + // Fast path: skip if text is empty + if (text.len == 0) { + return; + } + + // Create and append text node + const text_node = try page.createTextNode(text); + try page.appendNode(parent, text_node, .{ .child_already_connected = false }); +} + pub fn getOuterHTML(self: *Element, writer: *std.Io.Writer, page: *Page) !void { const dump = @import("../dump.zig"); return dump.deep(self.asNode(), .{ .shadow = .skip }, writer, page); @@ -913,7 +933,7 @@ pub const JsApi = struct { } pub const namespaceURI = bridge.accessor(Element.getNamespaceURI, null, .{}); - pub const innerText = bridge.accessor(_innerText, null, .{}); + pub const innerText = bridge.accessor(_innerText, Element.setInnerText, .{}); fn _innerText(self: *Element, page: *const Page) ![]const u8 { var buf = std.Io.Writer.Allocating.init(page.call_arena); try self.getInnerText(&buf.writer);