From 1cf025bcc87757dcbb2424898950753233d014c8 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Mon, 28 Sep 2026 18:12:08 +0800 Subject: [PATCH 1/2] mem: remove allocation for default sanitizer https://github.com/lightpanda-io/browser/pull/3619 added proper support for Sanitizer. A consequence of that is that every setHTML/parseHTML without an explicit creates the default Sanitizer (with its ~300 entries). This commit creates 1 app-level default sanitizer and uses it, internally, when none is explicitly given. The sanitizer is immutable so can safely be used across threads. --- src/App.zig | 6 +++++ src/browser/webapi/Sanitizer.zig | 40 +++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/App.zig b/src/App.zig index 3d573294b..124e8e6bd 100644 --- a/src/App.zig +++ b/src/App.zig @@ -27,6 +27,7 @@ const Telemetry = @import("telemetry/telemetry.zig").Telemetry; const Network = @import("network/Network.zig"); const Watchdog = @import("Watchdog.zig"); +const Sanitizer = @import("browser/webapi/Sanitizer.zig"); pub const ArenaPool = @import("ArenaPool.zig"); const log = lp.log; @@ -45,6 +46,7 @@ arena_pool: ArenaPool, app_dir_path: ?[]const u8, regex_context: *Regex.Context, +default_sanitizer: *Sanitizer, pub fn init(allocator: Allocator, config: *const Config) !*App { const platform = try Platform.init(.{ @@ -73,6 +75,7 @@ pub fn init(allocator: Allocator, config: *const Config) !*App { .app_dir_path = undefined, .telemetry = undefined, .arena_pool = undefined, + .default_sanitizer = undefined, .watchdog = .init(config.watchdogMs()), }; try app.watchdog.start(); @@ -89,6 +92,8 @@ pub fn init(allocator: Allocator, config: *const Config) !*App { app.arena_pool = ArenaPool.init(allocator, .{}); errdefer app.arena_pool.deinit(); + app.default_sanitizer = try .initDefault(&app.arena_pool); + return app; } @@ -107,6 +112,7 @@ pub fn deinit(self: *App) void { self.regex_context.deinit(); self.snapshot.deinit(); self.platform.deinit(); + self.default_sanitizer.deinitDefault(); self.arena_pool.deinit(); allocator.destroy(self); diff --git a/src/browser/webapi/Sanitizer.zig b/src/browser/webapi/Sanitizer.zig index 6cf57a724..8a7e5113d 100644 --- a/src/browser/webapi/Sanitizer.zig +++ b/src/browser/webapi/Sanitizer.zig @@ -268,6 +268,24 @@ pub fn deinit(self: *Sanitizer, _: *Page) void { self._owned_arena.release(); } +// Every setHTML/parseHTML that doesn't pass an explicit Sanitizer gets a default +// one. This has ~300 entries, so rather than creating it every time, we have +// one global default Sanitizer on the app. This value is only ever used internally +// in sanitize where it is immutable. +pub fn initDefault(arena_pool: *lp.ArenaPool) !*Sanitizer { + const arena = try arena_pool.acquire(.small, "Sanitizer.default"); + errdefer arena.release(); + + const self = try arena.create(Sanitizer); + self.* = .{ ._owned_arena = arena, ._arena = arena.allocator() }; + try self.setFromDefault(); + return self; +} + +pub fn deinitDefault(self: *Sanitizer) void { + self._owned_arena.release(); +} + pub fn acquireRef(self: *Sanitizer) void { self._rc.acquire(); } @@ -476,8 +494,6 @@ fn own(self: *Sanitizer, value: []const u8) ![]const u8 { return String.intern(value) orelse self._arena.dupe(u8, value); } -// - - const JsName = struct { name: String, namespace: ?[]const u8, @@ -1023,7 +1039,7 @@ const FromOptions = struct { } }; -fn fromOptions(options: ?Options, safe: bool, exec: *const Execution) !?FromOptions { +fn fromOptions(options: ?Options, safe: bool, frame: *Frame) !?FromOptions { const spec = blk: { const o = options orelse break :blk null; const spec = o.sanitizer orelse break :blk null; @@ -1036,11 +1052,14 @@ fn fromOptions(options: ?Options, safe: bool, exec: *const Execution) !?FromOpti break :blk spec; }; - if (spec == null and safe == false) { - return null; + if (spec == null) { + if (safe == false) { + return null; + } + // A missing spec takes the "default" preset + return .{ .sanitizer = frame._session.browser.app.default_sanitizer, .owned = false }; } - // A missing spec takes the "default" preset - return .{ .sanitizer = try create(spec, safe == false, exec), .owned = true }; + return .{ .sanitizer = try create(spec, safe == false, &frame.js.execution), .owned = true }; } pub fn setAndFilterHTML(target: *Node, context: *Element, html: []const u8, options: ?Options, safe: bool, frame: *Frame) !void { @@ -1051,7 +1070,7 @@ pub fn setAndFilterHTML(target: *Node, context: *Element, html: []const u8, opti } } - const resolved = try fromOptions(options, safe, &frame.js.execution); + const resolved = try fromOptions(options, safe, frame); defer if (resolved) |r| r.release(frame.page); // Parsed into a detached fragment, so that nothing is connected (no fetch, @@ -1067,7 +1086,7 @@ pub fn setAndFilterHTML(target: *Node, context: *Element, html: []const u8, opti } pub fn parseHTML(html: []const u8, options: ?Options, safe: bool, frame: *Frame) !*Node.Document { - const resolved = try fromOptions(options, safe, &frame.js.execution); + const resolved = try fromOptions(options, safe, frame); defer if (resolved) |r| r.release(frame.page); const document = (try Frame.parse.htmlDocument(frame, html, .{ .allow_declarative_shadow = true })).asDocument(); @@ -1079,6 +1098,9 @@ pub fn parseHTML(html: []const u8, options: ?Options, safe: bool, frame: *Frame) } fn sanitize(self: *const Sanitizer, root: *Node, safe: bool, frame: *Frame) !void { + if (@TypeOf(self) != *const Sanitizer) { + @compileError("self *must* remain const since it can reference a globally shared defaulf sanitizer that cannot be mutated"); + } const arena = frame.call_arena; // A template's contents and a shadow root are trees of their own From 3ca791be4d70b274ba13d206e3419c0291674f9d Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Mon, 28 Sep 2026 18:16:46 +0800 Subject: [PATCH 2/2] fix typo --- src/browser/webapi/Sanitizer.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/webapi/Sanitizer.zig b/src/browser/webapi/Sanitizer.zig index 8a7e5113d..bff530f86 100644 --- a/src/browser/webapi/Sanitizer.zig +++ b/src/browser/webapi/Sanitizer.zig @@ -1099,7 +1099,7 @@ pub fn parseHTML(html: []const u8, options: ?Options, safe: bool, frame: *Frame) fn sanitize(self: *const Sanitizer, root: *Node, safe: bool, frame: *Frame) !void { if (@TypeOf(self) != *const Sanitizer) { - @compileError("self *must* remain const since it can reference a globally shared defaulf sanitizer that cannot be mutated"); + @compileError("self *must* remain const since it can reference a globally shared default sanitizer that cannot be mutated"); } const arena = frame.call_arena;