From e51e6aa2b080b34377eba3c5d4affbc8a6aedecd Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 28 Jan 2026 14:44:05 +0800 Subject: [PATCH] Use ArenaPool when parsing HTML and for TextDecoder (with finalizer) Slowly more page.arena -> ArenaPool wherever possible. In some cases, an arena from the arenapool will be preferred over the call_arena also. --- src/browser/Page.zig | 11 +++++++++-- src/browser/webapi/DOMParser.zig | 7 +++++-- src/browser/webapi/Document.zig | 7 +++++-- src/browser/webapi/element/Html.zig | 5 ++++- src/browser/webapi/encoding/TextDecoder.zig | 21 +++++++++++++++++---- 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 830b29b1..90eae0f4 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -745,7 +745,10 @@ fn pageDoneCallback(ctx: *anyopaque) !void { switch (self._parse_state) { .html => |buf| { - var parser = Parser.init(self.arena, self.document.asNode(), self); + const parse_arena = try self.getArena(.{ .debug = "Page.parse" }); + defer self.releaseArena(parse_arena); + + var parser = Parser.init(parse_arena, self.document.asNode(), self); parser.parse(buf.items); self._script_manager.staticScriptsDone(); if (self._script_manager.isDone()) { @@ -757,7 +760,11 @@ fn pageDoneCallback(ctx: *anyopaque) !void { }, .text => |*buf| { try buf.appendSlice(self.arena, ""); - var parser = Parser.init(self.arena, self.document.asNode(), self); + + const parse_arena = try self.getArena(.{ .debug = "Page.parse" }); + defer self.releaseArena(parse_arena); + + var parser = Parser.init(parse_arena, self.document.asNode(), self); parser.parse(buf.items); self.documentIsComplete(); }, diff --git a/src/browser/webapi/DOMParser.zig b/src/browser/webapi/DOMParser.zig index c79f9956..30f8bf33 100644 --- a/src/browser/webapi/DOMParser.zig +++ b/src/browser/webapi/DOMParser.zig @@ -48,6 +48,9 @@ pub fn parseFromString( @"image/svg+xml", }, mime_type) orelse return error.NotSupported; + const arena = try page.getArena(.{ .debug = "DOMParser.parseFromString" }); + defer page.releaseArena(arena); + return switch (target_mime) { .@"text/html" => { // Create a new HTMLDocument @@ -61,7 +64,7 @@ pub fn parseFromString( } // Parse HTML into the document - var parser = Parser.init(page.arena, doc.asNode(), page); + var parser = Parser.init(arena, doc.asNode(), page); parser.parse(normalized); if (parser.err) |pe| { @@ -78,7 +81,7 @@ pub fn parseFromString( // Parse XML into XMLDocument. const doc_node = doc.asNode(); - var parser = Parser.init(page.arena, doc_node, page); + var parser = Parser.init(arena, doc_node, page); parser.parseXML(html); if (parser.err) |pe| { diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index e5c5f623..17b2639b 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -648,7 +648,10 @@ pub fn write(self: *Document, text: []const []const u8, page: *Page) !void { page._parse_mode = .document_write; defer page._parse_mode = previous_parse_mode; - var parser = Parser.init(page.call_arena, fragment_node, page); + const arena = try page.getArena(.{ .debug = "Document.write" }); + defer page.releaseArena(arena); + + var parser = Parser.init(arena, fragment_node, page); parser.parseFragment(html); // Extract children from wrapper HTML element (html5ever wraps fragments) @@ -661,7 +664,7 @@ pub fn write(self: *Document, text: []const []const u8, page: *Page) !void { var it = if (first.is(Element.Html.Html) == null) fragment_node.childrenIterator() else first.childrenIterator(); while (it.next()) |child| { - try children_to_insert.append(page.call_arena, child); + try children_to_insert.append(arena, child); } if (children_to_insert.items.len == 0) { diff --git a/src/browser/webapi/element/Html.zig b/src/browser/webapi/element/Html.zig index 9eb41edd..c96c25cb 100644 --- a/src/browser/webapi/element/Html.zig +++ b/src/browser/webapi/element/Html.zig @@ -281,8 +281,11 @@ pub fn insertAdjacentHTML( }); const doc_node = doc.asNode(); + const arena = try page.getArena(.{ .debug = "HTML.insertAdjacentHTML" }); + defer page.releaseArena(arena); + const Parser = @import("../../parser/Parser.zig"); - var parser = Parser.init(page.call_arena, doc_node, page); + var parser = Parser.init(arena, doc_node, page); parser.parse(html); // Check if there's parsing error. diff --git a/src/browser/webapi/encoding/TextDecoder.zig b/src/browser/webapi/encoding/TextDecoder.zig index 3148868b..ad4bf0f6 100644 --- a/src/browser/webapi/encoding/TextDecoder.zig +++ b/src/browser/webapi/encoding/TextDecoder.zig @@ -25,8 +25,9 @@ const Allocator = std.mem.Allocator; const TextDecoder = @This(); _fatal: bool, -_ignore_bom: bool, +_page: *Page, _arena: Allocator, +_ignore_bom: bool, _stream: std.ArrayListUnmanaged(u8), const Label = enum { @@ -45,13 +46,23 @@ pub fn init(label_: ?[]const u8, opts_: ?InitOpts, page: *Page) !*TextDecoder { _ = std.meta.stringToEnum(Label, label) orelse return error.RangeError; } + const arena = try page.getArena(.{ .debug = "TextDecoder" }); + errdefer page.releaseArena(arena); + const opts = opts_ orelse InitOpts{}; - return page._factory.create(TextDecoder{ - ._arena = page.arena, + const self = try arena.create(TextDecoder); + self.* = .{ + ._page = page, + ._arena = arena, ._stream = .empty, ._fatal = opts.fatal, ._ignore_bom = opts.ignoreBOM, - }); + }; + return self; +} + +pub fn deinit(self: *TextDecoder, _: bool) void { + self._page.releaseArena(self._arena); } pub fn getEncoding(_: *const TextDecoder) []const u8 { @@ -103,6 +114,8 @@ pub const JsApi = struct { pub const name = "TextDecoder"; pub const prototype_chain = bridge.prototypeChain(); pub var class_id: bridge.ClassId = undefined; + pub const weak = true; + pub const finalizer = bridge.finalizer(TextDecoder.deinit); }; pub const constructor = bridge.constructor(TextDecoder.init, .{});