From 25d8b7d5c7a56dd67a675ff412526d1a4fb4c0ba Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 1 Jul 2026 18:12:03 +0800 Subject: [PATCH] mem: add local_arena The `call_arena` is currently our shortest-lived arena. Its promise is that it will be valid for at least 1 v8 -> zig -> v8 function call, which makes it ideal for getting values from v8 into zig, temporary work, and getting values from zig to v8. VBut `call_arena`'s lifetime is actually much longer. It's only reset when the call_depth reaches 0. And the reason for that are Zig functions that invoke v8 callbacks. If you just do it when a function ends, then if you allocate in ZigA and then call CallbackA which calls ZigB, then when ZigB ends, your ZigA allocation is cleared. This is something we could solve by reaching into Zig's ArenaAllocator to capture a position to rollback from. So, `call_arena` can end up living relatively long and accumulating quite a bit of memory. But most calls _don't_ invoke callbacks. Hence, `local_arena` IS reset at the end of every function. Using `local_arena` _is_ dangerous, mostly for the case where some of our APIs receive a *Frame (or *Execution) and thus might use a `local_arena` because they know they aren't invoking a JS callback. BUT, those APIs might not know that they're also invoked by some other Zig code that _could_ be invoking JS. Dangerous? Sure. But, github has code that looks like: ```js function onDelegatedClick(e) { for (const el of document.querySelectorAll('[data-action]')) { if (el.matches('.menu > .item:not(.disabled) a[href]')) { handle(el); } el.closest('.panel'); } } ``` Anything allocated in the `call_arena` will only be freed when the caller of `onDelegatedClick` ends. The `querySelectorAll` returns hundreds of elements and thus builds hundreds of parsed CSS and other scrap (x2 for `matches` and closest`). `call_arena` peaks at 15MB. With the local_arena? 1MB. If 10x more elements were returned, the peak would be 10x higher. With local_arena, it stays 1MB. --- src/browser/Frame.zig | 15 +++++++++++++-- src/browser/Runner.zig | 4 ++-- src/browser/ScriptManagerBase.zig | 2 +- src/browser/frame/node_factory.zig | 2 +- src/browser/js/Caller.zig | 11 +++++++++++ src/browser/js/Context.zig | 8 ++++++-- src/browser/js/Env.zig | 3 +++ src/browser/js/Execution.zig | 1 + src/browser/markdown.zig | 2 +- src/browser/webapi/CSS.zig | 2 +- src/browser/webapi/CryptoKey.zig | 2 +- src/browser/webapi/DOMMatrixReadOnly.zig | 8 ++++---- src/browser/webapi/DataTransfer.zig | 10 +++++----- src/browser/webapi/Document.zig | 10 +++++----- src/browser/webapi/DocumentFragment.zig | 2 +- src/browser/webapi/Element.zig | 12 ++++++++---- src/browser/webapi/HTMLDocument.zig | 4 ++-- src/browser/webapi/IntersectionObserver.zig | 2 +- src/browser/webapi/MutationObserver.zig | 2 +- src/browser/webapi/Node.zig | 6 ++++-- src/browser/webapi/Performance.zig | 4 ++-- src/browser/webapi/PerformanceObserver.zig | 4 ++-- src/browser/webapi/Range.zig | 2 +- src/browser/webapi/SubtleCrypto.zig | 8 ++++---- src/browser/webapi/URL.zig | 6 +++--- src/browser/webapi/Window.zig | 4 ++-- src/browser/webapi/WorkerGlobalScope.zig | 7 +++++++ src/browser/webapi/XMLSerializer.zig | 2 +- .../canvas/OffscreenCanvasRenderingContext2D.zig | 2 +- src/browser/webapi/collections/DOMTokenList.zig | 12 ++++++------ src/browser/webapi/crypto/AES.zig | 8 ++++---- src/browser/webapi/crypto/EC.zig | 2 +- src/browser/webapi/crypto/HMAC.zig | 3 +-- src/browser/webapi/crypto/KDF.zig | 4 ++-- src/browser/webapi/crypto/X25519.zig | 4 ++-- src/browser/webapi/css/CSSStyleDeclaration.zig | 4 ++-- src/browser/webapi/css/CSSStyleRule.zig | 2 +- src/browser/webapi/css/CSSStyleSheet.zig | 2 +- src/browser/webapi/element/DOMStringMap.zig | 6 +++--- src/browser/webapi/element/Html.zig | 4 ++-- src/browser/webapi/element/html/Anchor.zig | 2 +- src/browser/webapi/element/html/Area.zig | 2 +- src/browser/webapi/element/html/Base.zig | 2 +- src/browser/webapi/element/html/Canvas.zig | 4 ++-- src/browser/webapi/element/html/Input.zig | 2 +- src/cdp/CDP.zig | 7 +++++++ 46 files changed, 133 insertions(+), 84 deletions(-) diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index f903f3e1d..7cde1c6c8 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -251,10 +251,15 @@ js: *JS.Context, // An arena for the lifetime of the frame. arena: Allocator, -// An arena with a lifetime guaranteed to be for 1 invoking of a Zig function -// from JS. Best arena to use, when possible. +// An arena with a lifetime for at least the scope of one Zig invocation from +// JS. Prefer local_arena where possible. Use call_arena when allocations may +// need to call back into JS (event dispatch, forEach callback, ....) call_arena: Allocator, +// An arena with a lifetime guaranteed to be for exactly 1 invoking of a Zig +// function from JS. Best arena to use, when possible. +local_arena: Allocator, + parent: ?*Frame, window: *Window, document: *Document, @@ -306,6 +311,9 @@ pub fn init(self: *Frame, frame_id: u32, page: *Page, opts: InitOpts) !void { const call_arena = try session.getArena(.medium, "call_arena"); errdefer session.releaseArena(call_arena); + const local_arena = try session.getArena(.medium, "local_arena"); + errdefer session.releaseArena(local_arena); + const factory = &page.factory; const document = (try factory.document(Node.Document.HTMLDocument{ ._proto = undefined, @@ -320,6 +328,7 @@ pub fn init(self: *Frame, frame_id: u32, page: *Page, opts: InitOpts) !void { .document = document, .window = undefined, .call_arena = call_arena, + .local_arena = local_arena, ._frame_id = frame_id, ._page = page, ._session = session, @@ -382,6 +391,7 @@ pub fn init(self: *Frame, frame_id: u32, page: *Page, opts: InitOpts) !void { .identity = &page.identity, .identity_arena = arena, .call_arena = self.call_arena, + .local_arena = self.local_arena, }); errdefer browser.env.destroyContext(self.js); @@ -480,6 +490,7 @@ pub fn deinit(self: *Frame) void { self._style_manager.deinit(); page.releaseArena(self.call_arena); + page.releaseArena(self.local_arena); } pub fn trackWorker(self: *Frame, worker: *Worker) !void { diff --git a/src/browser/Runner.zig b/src/browser/Runner.zig index d07c1b31f..f00bdbd7c 100644 --- a/src/browser/Runner.zig +++ b/src/browser/Runner.zig @@ -368,7 +368,7 @@ pub fn waitForScript(self: *Runner, frame_id: u32, src: [:0]const u8, timeout_ms defer try_catch.deinit(); const s = ls.local.compile(src, "wait_script") catch |err| { - const caught = try_catch.caughtOrError(frame.call_arena, err); + const caught = try_catch.caughtOrError(frame.local_arena, err); log.err(.app, "wait script error", .{ .err = caught }); return error.ScriptError; }; @@ -396,7 +396,7 @@ pub fn waitForScript(self: *Runner, frame_id: u32, src: [:0]const u8, timeout_ms const script = compiled.get(ls.local.isolate).bindToCurrentContext(&ls.local); const value = script.run() catch |err| { - const caught = try_catch.caughtOrError(frame.call_arena, err); + const caught = try_catch.caughtOrError(frame.local_arena, err); log.err(.app, "wait script error", .{ .err = caught }); return error.ScriptError; }; diff --git a/src/browser/ScriptManagerBase.zig b/src/browser/ScriptManagerBase.zig index 07d9e16bb..509b4523f 100644 --- a/src/browser/ScriptManagerBase.zig +++ b/src/browser/ScriptManagerBase.zig @@ -935,7 +935,7 @@ pub const Script = struct { return; } - const caught = try_catch.caughtOrError(frame.call_arena, error.Unknown); + const caught = try_catch.caughtOrError(frame.local_arena, error.Unknown); log.warn(.js, "eval script", .{ .url = url, .caught = caught, diff --git a/src/browser/frame/node_factory.zig b/src/browser/frame/node_factory.zig index 43412acb8..5665efcff 100644 --- a/src/browser/frame/node_factory.zig +++ b/src/browser/frame/node_factory.zig @@ -356,7 +356,7 @@ pub fn createElementNS(frame: *Frame, namespace: Element.Namespace, name: []cons defer try_catch.deinit(); ls.local.eval(inject_script, "inject_script") catch |err| { - const caught = try_catch.caughtOrError(frame.call_arena, err); + const caught = try_catch.caughtOrError(frame.local_arena, err); log.err(.app, "inject script error", .{ .err = caught }); }; } diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index 55daf7c32..7cda1646e 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -33,6 +33,7 @@ const v8 = js.v8; const log = lp.log; const ArenaAllocator = std.heap.ArenaAllocator; const CALL_ARENA_RETAIN = 1024 * 16; +const LOCAL_ARENA_RETAIN = 1024 * 16; const IS_DEBUG = @import("builtin").mode == .Debug; const Caller = @This(); @@ -101,6 +102,16 @@ pub fn deinit(self: *Caller) void { _ = arena.reset(.{ .retain_with_limit = CALL_ARENA_RETAIN }); } + // Unlike call_arena, local_arena is reset on _every_ return, since its + // users promise not to hold data across a nested call. In debug, free + // back to the backing allocator so a stale pointer trips the + // DebugAllocator's use-after-free detection; in release, retain a buffer + // to avoid realloc churn. + { + const local_arena: *ArenaAllocator = @ptrCast(@alignCast(ctx.local_arena.ptr)); + _ = local_arena.reset(if (comptime IS_DEBUG) .free_all else .{ .retain_with_limit = LOCAL_ARENA_RETAIN }); + } + ctx.call_depth = call_depth; ctx.local = self.prev_local; ctx.global.setJs(self.prev_context); diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 7cc777f23..c0945742e 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -100,6 +100,10 @@ arena: Allocator, // owned by the IsolatedWorld. call_arena: Allocator, +// Like call_arena, but reset on _every_ Caller.deinit rather than only at +// call_depth 0. +local_arena: Allocator, + // Because calls can be nested (i.e.a function calling a callback), // we can only reset the call_arena when call_depth == 0. If we were // to reset it within a callback, it would invalidate the data of @@ -700,7 +704,7 @@ fn importMetaResolveCallback(callback_handle: ?*const v8.FunctionCallbackInfo) c return; }; - const resolved = ctx.script_manager.resolveSpecifier(ctx.call_arena, data.base, specifier) catch { + const resolved = ctx.script_manager.resolveSpecifier(ctx.local_arena, data.base, specifier) catch { _ = isolate.throwException(isolate.createTypeError("failed to resolve module specifier")); return; }; @@ -904,7 +908,7 @@ fn dynamicModuleSourceCallback(ctx: *anyopaque, module_source_: anyerror!ScriptM defer try_catch.deinit(); break :blk self.module(true, local, ms.src(), state.specifier, true) catch |err| { - const caught = try_catch.caughtOrError(self.call_arena, err); + const caught = try_catch.caughtOrError(self.local_arena, err); log.err(.js, "module compilation failed", .{ .caught = caught, .specifier = state.specifier, diff --git a/src/browser/js/Env.zig b/src/browser/js/Env.zig index 3a37e8d4a..299038ad5 100644 --- a/src/browser/js/Env.zig +++ b/src/browser/js/Env.zig @@ -229,6 +229,7 @@ pub const ContextParams = struct { identity: *js.Identity, identity_arena: Allocator, call_arena: Allocator, + local_arena: Allocator, debug_name: []const u8 = "Context", }; @@ -315,6 +316,7 @@ fn _createContext(self: *Env, global: anytype, params: ContextParams) !*Context .handle = context_global, .templates = self.templates, .call_arena = params.call_arena, + .local_arena = params.local_arena, .microtask_queue = microtask_queue, .script_manager = if (comptime is_frame) &global._script_manager.base else &global._script_manager, .scheduler = .init(context_arena), @@ -332,6 +334,7 @@ fn _createContext(self: *Env, global: anytype, params: ContextParams) !*Context .page = context.page, .session = page.session, .call_arena = params.call_arena, + .local_arena = params.local_arena, ._factory = global._factory, ._scheduler = &context.scheduler, }; diff --git a/src/browser/js/Execution.zig b/src/browser/js/Execution.zig index b4fbebd8a..ee3abadf6 100644 --- a/src/browser/js/Execution.zig +++ b/src/browser/js/Execution.zig @@ -51,6 +51,7 @@ js: *Context, buf: []u8, arena: Allocator, call_arena: Allocator, +local_arena: Allocator, page: *Page, session: *Session, diff --git a/src/browser/markdown.zig b/src/browser/markdown.zig index 50c0e26cc..b56d25bb3 100644 --- a/src/browser/markdown.zig +++ b/src/browser/markdown.zig @@ -381,7 +381,7 @@ const Context = struct { if (!info.has_visible and label == null and href_raw == null) return; - const href = if (href_raw) |h| URL.resolve(frame.call_arena, frame.base(), h, .{ .encoding = frame.charset }) catch h else null; + const href = if (href_raw) |h| URL.resolve(frame.local_arena, frame.base(), h, .{ .encoding = frame.charset }) catch h else null; if (info.has_block) { try self.renderChildren(el.asNode()); diff --git a/src/browser/webapi/CSS.zig b/src/browser/webapi/CSS.zig index 6b560a5a4..00bd1a952 100644 --- a/src/browser/webapi/CSS.zig +++ b/src/browser/webapi/CSS.zig @@ -65,7 +65,7 @@ pub fn escape(value: []const u8, frame: *Frame) ![]const u8 { return value; } - const result = try frame.call_arena.alloc(u8, out_len); + const result = try frame.local_arena.alloc(u8, out_len); var pos: usize = 0; if (needsEscape(true, first)) { diff --git a/src/browser/webapi/CryptoKey.zig b/src/browser/webapi/CryptoKey.zig index 3874b3fb9..f85c7ce20 100644 --- a/src/browser/webapi/CryptoKey.zig +++ b/src/browser/webapi/CryptoKey.zig @@ -190,7 +190,7 @@ pub fn getUsages(self: *const CryptoKey, exec: *const Execution) ![]const []cons n += 1; } } - return exec.call_arena.dupe([]const u8, buf[0..n]); + return exec.local_arena.dupe([]const u8, buf[0..n]); } pub const JsApi = struct { diff --git a/src/browser/webapi/DOMMatrixReadOnly.zig b/src/browser/webapi/DOMMatrixReadOnly.zig index ca39901a7..9da9975ca 100644 --- a/src/browser/webapi/DOMMatrixReadOnly.zig +++ b/src/browser/webapi/DOMMatrixReadOnly.zig @@ -627,7 +627,7 @@ pub fn inverse(self: *const DOMMatrixReadOnly, page: *Page) !*DOMMatrix { } pub fn toFloat32Array(self: *const DOMMatrixReadOnly, exec: *const js.Execution) !js.TypedArray(f32) { - const out = try exec.call_arena.alloc(f32, 16); + const out = try exec.local_arena.alloc(f32, 16); for (0..16) |i| { out[i] = @floatCast(self._m[i]); } @@ -635,7 +635,7 @@ pub fn toFloat32Array(self: *const DOMMatrixReadOnly, exec: *const js.Execution) } pub fn toFloat64Array(self: *const DOMMatrixReadOnly, exec: *const js.Execution) !js.TypedArray(f64) { - const out = try exec.call_arena.dupe(f64, &self._m); + const out = try exec.local_arena.dupe(f64, &self._m); return .{ .values = out }; } @@ -648,7 +648,7 @@ pub fn toString(self: *const DOMMatrixReadOnly, exec: *const js.Execution) ![]co return error.InvalidStateError; } } - return std.fmt.allocPrint(exec.call_arena, "matrix({d}, {d}, {d}, {d}, {d}, {d})", .{ + return std.fmt.allocPrint(exec.local_arena, "matrix({d}, {d}, {d}, {d}, {d}, {d})", .{ m[0], m[1], m[4], m[5], m[12], m[13], }); } @@ -657,7 +657,7 @@ pub fn toString(self: *const DOMMatrixReadOnly, exec: *const js.Execution) ![]co return error.InvalidStateError; } } - return std.fmt.allocPrint(exec.call_arena, "matrix3d({d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d})", .{ + return std.fmt.allocPrint(exec.local_arena, "matrix3d({d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d})", .{ m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10], m[11], diff --git a/src/browser/webapi/DataTransfer.zig b/src/browser/webapi/DataTransfer.zig index d53eb07d5..b8505d42c 100644 --- a/src/browser/webapi/DataTransfer.zig +++ b/src/browser/webapi/DataTransfer.zig @@ -102,7 +102,7 @@ fn normalizeFormat(arena: Allocator, format: []const u8) ![]const u8 { } pub fn getData(self: *const DataTransfer, format: []const u8, frame: *Frame) ![]const u8 { - const norm = try normalizeFormat(frame.call_arena, format); + const norm = try normalizeFormat(frame.local_arena, format); for (self._items.items) |it| { if (it._kind == .string and std.mem.eql(u8, it._type, norm)) { return it._payload.string; @@ -127,7 +127,7 @@ pub fn setData(self: *DataTransfer, format: []const u8, data: []const u8) !void pub fn clearData(self: *DataTransfer, format_: ?[]const u8, frame: *Frame) !void { if (format_) |format| { - const norm = try normalizeFormat(frame.call_arena, format); + const norm = try normalizeFormat(frame.local_arena, format); var i: usize = 0; while (i < self._items.items.len) { const it = self._items.items[i]; @@ -222,14 +222,14 @@ pub fn getTypes(self: *DataTransfer, frame: *Frame) ![][]const u8 { var has_files = false; for (self._items.items) |it| { switch (it._kind) { - .string => try out.append(frame.call_arena, it._type), + .string => try out.append(frame.local_arena, it._type), .file => has_files = true, } } if (has_files) { - try out.append(frame.call_arena, "Files"); + try out.append(frame.local_arena, "Files"); } - return out.toOwnedSlice(frame.call_arena); + return out.toOwnedSlice(frame.local_arena); } pub fn getDropEffect(self: *const DataTransfer) []const u8 { diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index 9f02ce08e..1913fd55f 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -171,7 +171,7 @@ pub fn setDomain(self: *Document, value: []const u8) !void { const doc_frame = self._frame orelse return error.SecurityError; const origin = doc_frame.origin orelse return error.SecurityError; - const arena = doc_frame.call_arena; + const arena = doc_frame.local_arena; const requested = if (idna.needsAscii(value)) try idna.toAscii(arena, value) else value; // Validate against the current effective domain. Once relaxed, @@ -193,7 +193,7 @@ pub fn setDomain(self: *Document, value: []const u8) !void { pub fn getCookie(_: *Document, frame: *Frame) ![]const u8 { var buf: std.ArrayList(u8) = .empty; - try frame._session.cookie_jar.forRequest(frame.url, buf.writer(frame.call_arena), .{ + try frame._session.cookie_jar.forRequest(frame.url, buf.writer(frame.local_arena), .{ .is_http = false, .is_navigation = true, }); @@ -738,7 +738,7 @@ pub fn elementFromPoint(self: *Document, x: f64, y: f64, frame: *Frame) !?*Eleme const root = self.asNode(); var stack: std.ArrayList(*Node) = .empty; - try stack.append(frame.call_arena, root); + try stack.append(frame.local_arena, root); var visibility_cache: Element.VisibilityCache = .{}; var preorder_index: f64 = 0; @@ -770,7 +770,7 @@ pub fn elementFromPoint(self: *Document, x: f64, y: f64, frame: *Frame) !?*Eleme // Add children to stack in reverse order so we process them in document order var child = node.lastChild(); while (child) |c| { - try stack.append(frame.call_arena, c); + try stack.append(frame.local_arena, c); child = c.previousSibling(); } } @@ -783,7 +783,7 @@ pub fn elementsFromPoint(self: *Document, x: f64, y: f64, frame: *Frame) ![]cons var current: ?*Element = (try self.elementFromPoint(x, y, frame)) orelse return &.{}; var result: std.ArrayList(*Element) = .empty; while (current) |el| { - try result.append(frame.call_arena, el); + try result.append(frame.local_arena, el); current = el.parentElement(); } return result.items; diff --git a/src/browser/webapi/DocumentFragment.zig b/src/browser/webapi/DocumentFragment.zig index da09e9bbc..ccb53f50a 100644 --- a/src/browser/webapi/DocumentFragment.zig +++ b/src/browser/webapi/DocumentFragment.zig @@ -222,7 +222,7 @@ pub const JsApi = struct { pub const innerHTML = bridge.accessor(_getInnerHTML, _setInnerHTML, .{ .ce_reactions = true }); fn _getInnerHTML(self: *DocumentFragment, frame: *Frame) ![]const u8 { - var buf = std.Io.Writer.Allocating.init(frame.call_arena); + var buf = std.Io.Writer.Allocating.init(frame.local_arena); try self.getInnerHTML(&buf.writer, frame); return buf.written(); } diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index f4c16641d..4530872a3 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -1280,7 +1280,7 @@ pub fn getClientRects(self: *Element, frame: *Frame) ![]DOMRect { if (!self.checkVisibilityCached(null, frame)) { return &.{}; } - const rects = try frame.call_arena.alloc(DOMRect, 1); + const rects = try frame.local_arena.alloc(DOMRect, 1); rects[0] = self.getBoundingClientRectForVisible(frame); return rects; } @@ -1924,14 +1924,16 @@ pub const JsApi = struct { pub const innerText = bridge.accessor(_innerText, Element.setInnerText, .{ .ce_reactions = true }); fn _innerText(self: *Element, frame: *Frame) ![]const u8 { - var buf = std.Io.Writer.Allocating.init(frame.call_arena); + var buf = std.Io.Writer.Allocating.init(frame.local_arena); try self.getInnerText(&buf.writer, frame); return buf.written(); } pub const outerHTML = bridge.accessor(_getOuterHTML, _setOuterHTML, .{ .ce_reactions = true }); fn _getOuterHTML(self: *Element, frame: *Frame) ![]const u8 { - var buf = std.Io.Writer.Allocating.init(frame.call_arena); + // local_arena: serialization is read-only and the returned string is + // converted to v8 before this call returns. No JS runs in between. + var buf = std.Io.Writer.Allocating.init(frame.local_arena); try self.getOuterHTML(&buf.writer, frame); return buf.written(); } @@ -1942,7 +1944,9 @@ pub const JsApi = struct { pub const innerHTML = bridge.accessor(_getInnerHTML, _setInnerHTML, .{ .ce_reactions = true }); fn _getInnerHTML(self: *Element, frame: *Frame) ![]const u8 { - var buf = std.Io.Writer.Allocating.init(frame.call_arena); + // local_arena: read-only serialization, result converted to v8 before + // returning; no JS runs in between. + var buf = std.Io.Writer.Allocating.init(frame.local_arena); try self.getInnerHTML(&buf.writer, frame); return buf.written(); } diff --git a/src/browser/webapi/HTMLDocument.zig b/src/browser/webapi/HTMLDocument.zig index 4d2ccb9c2..ed3f6282b 100644 --- a/src/browser/webapi/HTMLDocument.zig +++ b/src/browser/webapi/HTMLDocument.zig @@ -103,7 +103,7 @@ pub fn getTitle(self: *HTMLDocument, frame: *Frame) ![]const u8 { return ""; }; - var buf = std.Io.Writer.Allocating.init(frame.call_arena); + var buf = std.Io.Writer.Allocating.init(frame.local_arena); try title_element.asNode().getTextContent(&buf.writer); const text = buf.written(); @@ -114,7 +114,7 @@ pub fn getTitle(self: *HTMLDocument, frame: *Frame) ![]const u8 { var started = false; var in_whitespace = false; var result: std.ArrayList(u8) = .empty; - try result.ensureTotalCapacity(frame.call_arena, text.len); + try result.ensureTotalCapacity(frame.local_arena, text.len); for (text) |c| { const is_ascii_ws = c == ' ' or c == '\t' or c == '\n' or c == '\r' or c == '\x0C'; diff --git a/src/browser/webapi/IntersectionObserver.zig b/src/browser/webapi/IntersectionObserver.zig index 579a4192e..69bdfcd64 100644 --- a/src/browser/webapi/IntersectionObserver.zig +++ b/src/browser/webapi/IntersectionObserver.zig @@ -191,7 +191,7 @@ pub fn disconnect(self: *IntersectionObserver, frame: *Frame) void { } pub fn takeRecords(self: *IntersectionObserver, frame: *Frame) ![]*IntersectionObserverEntry { - const entries = try frame.call_arena.dupe(*IntersectionObserverEntry, self._pending_entries.items); + const entries = try frame.local_arena.dupe(*IntersectionObserverEntry, self._pending_entries.items); self._pending_entries.clearRetainingCapacity(); return entries; } diff --git a/src/browser/webapi/MutationObserver.zig b/src/browser/webapi/MutationObserver.zig index 5b696cc97..6d3dab74e 100644 --- a/src/browser/webapi/MutationObserver.zig +++ b/src/browser/webapi/MutationObserver.zig @@ -188,7 +188,7 @@ pub fn disconnect(self: *MutationObserver, frame: *Frame) void { } pub fn takeRecords(self: *MutationObserver, frame: *Frame) ![]*MutationRecord { - const records = try frame.call_arena.dupe(*MutationRecord, self._pending_records.items); + const records = try frame.local_arena.dupe(*MutationRecord, self._pending_records.items); self._pending_records.clearRetainingCapacity(); return records; } diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index e56209dd6..3cb562342 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -908,7 +908,7 @@ pub fn setData(self: *Node, data: []const u8, frame: *Frame) !void { pub fn normalize(self: *Node, frame: *Frame) !void { var buffer: std.ArrayList(u8) = .empty; - return self._normalize(frame.call_arena, &buffer, frame); + return self._normalize(frame.local_arena, &buffer, frame); } const CloneError = error{ @@ -1311,7 +1311,9 @@ pub const JsApi = struct { // cdata and attributes can return value directly, avoiding the copy switch (self._type) { .element, .document_fragment => { - var buf = std.Io.Writer.Allocating.init(frame.call_arena); + // local_arena: read-only text collection, result converted to + // v8 before returning; no JS runs in between. + var buf = std.Io.Writer.Allocating.init(frame.local_arena); try self.getTextContent(&buf.writer); return buf.written(); }, diff --git a/src/browser/webapi/Performance.zig b/src/browser/webapi/Performance.zig index 796376c04..b718f5aa5 100644 --- a/src/browser/webapi/Performance.zig +++ b/src/browser/webapi/Performance.zig @@ -215,11 +215,11 @@ pub fn getEntries(self: *const Performance) []*Entry { } pub fn getEntriesByType(self: *const Performance, entry_type: []const u8, exec: *const Execution) ![]const *Entry { - return filterEntriesByType(exec.call_arena, self._entries.items, entry_type); + return filterEntriesByType(exec.local_arena, self._entries.items, entry_type); } pub fn getEntriesByName(self: *const Performance, name: []const u8, entry_type: ?[]const u8, exec: *const Execution) ![]const *Entry { - return filterEntriesByName(exec.call_arena, self._entries.items, name, entry_type); + return filterEntriesByName(exec.local_arena, self._entries.items, name, entry_type); } // Also used by PerformanceObserver diff --git a/src/browser/webapi/PerformanceObserver.zig b/src/browser/webapi/PerformanceObserver.zig index 1c879e38a..c5f017e0b 100644 --- a/src/browser/webapi/PerformanceObserver.zig +++ b/src/browser/webapi/PerformanceObserver.zig @@ -216,11 +216,11 @@ pub const EntryList = struct { } pub fn getEntriesByType(self: *const EntryList, entry_type: []const u8, exec: *Execution) ![]const *Performance.Entry { - return Performance.filterEntriesByType(exec.call_arena, self._entries, entry_type); + return Performance.filterEntriesByType(exec.local_arena, self._entries, entry_type); } pub fn getEntriesByName(self: *const EntryList, name: []const u8, entry_type: ?[]const u8, exec: *Execution) ![]const *Performance.Entry { - return Performance.filterEntriesByName(exec.call_arena, self._entries, name, entry_type); + return Performance.filterEntriesByName(exec.local_arena, self._entries, name, entry_type); } pub const JsApi = struct { diff --git a/src/browser/webapi/Range.zig b/src/browser/webapi/Range.zig index a617125e3..03994dfc5 100644 --- a/src/browser/webapi/Range.zig +++ b/src/browser/webapi/Range.zig @@ -569,7 +569,7 @@ pub fn createContextualFragment(self: *const Range, html: []const u8, frame: *Fr pub fn toString(self: *const Range, frame: *Frame) ![]const u8 { // Simplified implementation: just extract text content - var buf = std.Io.Writer.Allocating.init(frame.call_arena); + var buf = std.Io.Writer.Allocating.init(frame.local_arena); try self.writeTextContent(&buf.writer); return buf.written(); } diff --git a/src/browser/webapi/SubtleCrypto.zig b/src/browser/webapi/SubtleCrypto.zig index 4e6f48ef6..662a525cd 100644 --- a/src/browser/webapi/SubtleCrypto.zig +++ b/src/browser/webapi/SubtleCrypto.zig @@ -185,7 +185,7 @@ pub fn importKey( const k = jwk.k orelse { return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } }); }; - break :blk common.base64Decode(exec.call_arena, k) catch |err| switch (err) { + break :blk common.base64Decode(exec.local_arena, k) catch |err| switch (err) { error.DataError => return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } }), else => |e| return e, }; @@ -309,13 +309,13 @@ fn exportJwk(key: *CryptoKey, exec: *const Execution) !js.Promise { // The `alg` registry value depends on the algorithm and key length. const alg: []const u8 = switch (key._type) { - .aes => try std.fmt.allocPrint(exec.call_arena, "A{d}{s}", .{ + .aes => try std.fmt.allocPrint(exec.local_arena, "A{d}{s}", .{ key._key.len * 8, key._algorithm.name[4..], // strip "AES-" }), .hmac => blk: { const hash: []const u8 = key._algorithm.hash orelse "SHA-"; - break :blk try std.fmt.allocPrint(exec.call_arena, "HS{s}", .{hash[4..]}); // strip "SHA-" + break :blk try std.fmt.allocPrint(exec.local_arena, "HS{s}", .{hash[4..]}); // strip "SHA-" }, else => { log.warn(.not_implemented, "SubtleCrypto.exportKey", .{ .format = "jwk", .type = key._type }); @@ -324,7 +324,7 @@ fn exportJwk(key: *CryptoKey, exec: *const Execution) !js.Promise { }; return local.resolvePromise(JwkSecret{ - .k = try common.base64Encode(exec.call_arena, key._key), + .k = try common.base64Encode(exec.local_arena, key._key), .alg = alg, .ext = key._extractable, .key_ops = try key.getUsages(exec), diff --git a/src/browser/webapi/URL.zig b/src/browser/webapi/URL.zig index dbffa97f6..ace7d6796 100644 --- a/src/browser/webapi/URL.zig +++ b/src/browser/webapi/URL.zig @@ -174,7 +174,7 @@ pub fn getSearch(self: *const URL, exec: *const Execution) ![]const u8 { return ""; } - var buf = std.Io.Writer.Allocating.init(exec.call_arena); + var buf = std.Io.Writer.Allocating.init(exec.local_arena); try buf.writer.writeByte('?'); try search_params.toString(&buf.writer); return buf.written(); @@ -262,7 +262,7 @@ pub fn getOrigin(self: *const URL, exec: *const Execution) ![]const u8 { const origin = U.url_get_origin(self._url); defer origin.deinit(); - return exec.call_arena.dupe(u8, origin.slice()); + return exec.local_arena.dupe(u8, origin.slice()); } pub fn setHref(self: *URL, value: []const u8, exec: *const Execution) !void { @@ -288,7 +288,7 @@ pub fn toString(self: *const URL, exec: *const Execution) ![]const u8 { if (search_params.getSize() == 0) { U.url_set_query_to_null(self._url); } else { - var buf = std.Io.Writer.Allocating.init(exec.call_arena); + var buf = std.Io.Writer.Allocating.init(exec.local_arena); defer buf.deinit(); try search_params.toString(&buf.writer); const query = buf.written(); diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index c1616f5b0..31f79c0c7 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -696,11 +696,11 @@ pub fn postMessage(self: *Window, message: js.Value, target_origin: ?[]const u8, const base64 = @import("encoding/base64.zig"); pub fn btoa(_: *const Window, input: base64.BinInput, frame: *Frame) ![]const u8 { - return base64.encode(frame.call_arena, input); + return base64.encode(frame.local_arena, input); } pub fn atob(_: *const Window, input: base64.BinInput, frame: *Frame) !js.String.OneByte { - const decoded = try base64.decode(frame.call_arena, input); + const decoded = try base64.decode(frame.local_arena, input); return .{ .bytes = decoded }; } diff --git a/src/browser/webapi/WorkerGlobalScope.zig b/src/browser/webapi/WorkerGlobalScope.zig index b860996b4..ab965648e 100644 --- a/src/browser/webapi/WorkerGlobalScope.zig +++ b/src/browser/webapi/WorkerGlobalScope.zig @@ -70,6 +70,7 @@ _http_owner: HttpClient.Owner = .{}, arena: Allocator, call_arena: Allocator, +local_arena: Allocator, url: [:0]const u8, // Same-origin constraint: a worker's origin is inherited from its parent frame. origin: ?[]const u8 = null, @@ -129,6 +130,9 @@ pub fn init( const call_arena = try session.getArena(.small, "WorkerGlobalScope.call_arena"); errdefer session.releaseArena(call_arena); + const local_arena = try session.getArena(.small, "WorkerGlobalScope.local_arena"); + errdefer session.releaseArena(local_arena); + const factory = frame._factory; const self = try factory.eventTargetWithAllocator(arena, WorkerGlobalScope{ .url = url, @@ -136,6 +140,7 @@ pub fn init( .origin = frame.origin, .js = undefined, .call_arena = call_arena, + .local_arena = local_arena, ._frame = frame, ._page = frame._page, ._session = session, @@ -162,6 +167,7 @@ pub fn init( self.js = try session.browser.env.createWorkerContext(self, .{ .call_arena = call_arena, + .local_arena = local_arena, .identity_arena = arena, .identity = &self._identity, }); @@ -191,6 +197,7 @@ pub fn deinit(self: *WorkerGlobalScope) void { } browser.env.destroyContext(self.js); session.releaseArena(self.call_arena); + session.releaseArena(self.local_arena); } pub fn base(self: *const WorkerGlobalScope) [:0]const u8 { diff --git a/src/browser/webapi/XMLSerializer.zig b/src/browser/webapi/XMLSerializer.zig index b7f955aec..683afa4ac 100644 --- a/src/browser/webapi/XMLSerializer.zig +++ b/src/browser/webapi/XMLSerializer.zig @@ -34,7 +34,7 @@ pub fn init() XMLSerializer { pub fn serializeToString(self: *const XMLSerializer, node: *Node, frame: *Frame) ![]const u8 { _ = self; - var buf = std.Io.Writer.Allocating.init(frame.call_arena); + var buf = std.Io.Writer.Allocating.init(frame.local_arena); if (node.is(Node.Document)) |doc| { try dump.root(doc, .{ .shadow = .skip }, &buf.writer, frame); } else { diff --git a/src/browser/webapi/canvas/OffscreenCanvasRenderingContext2D.zig b/src/browser/webapi/canvas/OffscreenCanvasRenderingContext2D.zig index de59a2e13..46ff54ebc 100644 --- a/src/browser/webapi/canvas/OffscreenCanvasRenderingContext2D.zig +++ b/src/browser/webapi/canvas/OffscreenCanvasRenderingContext2D.zig @@ -34,7 +34,7 @@ const OffscreenCanvasRenderingContext2D = @This(); _fill_style: color.RGBA = color.RGBA.Named.black, pub fn getFillStyle(self: *const OffscreenCanvasRenderingContext2D, exec: *Execution) ![]const u8 { - var w = std.Io.Writer.Allocating.init(exec.call_arena); + var w = std.Io.Writer.Allocating.init(exec.local_arena); try self._fill_style.format(&w.writer); return w.written(); } diff --git a/src/browser/webapi/collections/DOMTokenList.zig b/src/browser/webapi/collections/DOMTokenList.zig index 88acb1667..ad1114f6e 100644 --- a/src/browser/webapi/collections/DOMTokenList.zig +++ b/src/browser/webapi/collections/DOMTokenList.zig @@ -45,7 +45,7 @@ const Lookup = std.StringArrayHashMapUnmanaged(void); const WHITESPACE = " \t\n\r\x0C"; pub fn length(self: *const DOMTokenList, frame: *Frame) !u32 { - const tokens = try self.getTokens(frame.call_arena); + const tokens = try self.getTokens(frame.local_arena); return @intCast(tokens.count()); } @@ -53,7 +53,7 @@ pub fn length(self: *const DOMTokenList, frame: *Frame) !u32 { pub fn item(self: *const DOMTokenList, index: usize, frame: *Frame) !?[]const u8 { var i: usize = 0; - const allocator = frame.call_arena; + const allocator = frame.local_arena; var seen: std.StringArrayHashMapUnmanaged(void) = .empty; var it = std.mem.tokenizeAny(u8, self.getValue(), WHITESPACE); @@ -84,7 +84,7 @@ pub fn add(self: *DOMTokenList, tokens: []const []const u8, frame: *Frame) !void try validateToken(token); } - const allocator = frame.call_arena; + const allocator = frame.local_arena; var lookup = try self.getTokens(allocator); try lookup.ensureUnusedCapacity(allocator, tokens.len); @@ -100,7 +100,7 @@ pub fn remove(self: *DOMTokenList, tokens: []const []const u8, frame: *Frame) !v try validateToken(token); } - var lookup = try self.getTokens(frame.call_arena); + var lookup = try self.getTokens(frame.local_arena); for (tokens) |token| { _ = lookup.orderedRemove(token); } @@ -151,8 +151,8 @@ pub fn replace(self: *DOMTokenList, old_token: []const u8, new_token: []const u8 return error.InvalidCharacterError; } - const allocator = frame.call_arena; - var lookup = try self.getTokens(frame.call_arena); + const allocator = frame.local_arena; + var lookup = try self.getTokens(allocator); // Check if old_token exists if (!lookup.contains(old_token)) { diff --git a/src/browser/webapi/crypto/AES.zig b/src/browser/webapi/crypto/AES.zig index 0edc4c22c..3114671ef 100644 --- a/src/browser/webapi/crypto/AES.zig +++ b/src/browser/webapi/crypto/AES.zig @@ -260,7 +260,7 @@ fn ctr( std.mem.writeInt(u128, &wrapped, readBlock(counter) & ~mask, .big); const second = try cbcOrCtr(cipher, key, &wrapped, data[split..], encrypting, false, exec); - const out = try exec.call_arena.alloc(u8, data.len); + const out = try exec.local_arena.alloc(u8, data.len); @memcpy(out[0..split], first); @memcpy(out[split..], second); return out; @@ -308,7 +308,7 @@ fn cbcOrCtr( } // Block ciphers may emit up to one extra block on top of the input. - const out = try exec.call_arena.alloc(u8, data.len + 16); + const out = try exec.local_arena.alloc(u8, data.len + 16); var out_len: c_int = 0; if (cipherUpdate(ctx, out.ptr, &out_len, @ptrCast(data.ptr), @intCast(data.len), encrypting) != 1) { return error.OperationError; @@ -365,7 +365,7 @@ fn gcm( } if (encrypting) { - const out = try exec.call_arena.alloc(u8, data.len + tag_len); + const out = try exec.local_arena.alloc(u8, data.len + tag_len); var out_len: c_int = 0; if (data.len > 0 and cipherUpdate(ctx, out.ptr, &out_len, @ptrCast(data.ptr), @intCast(data.len), true) != 1) { return error.OperationError; @@ -391,7 +391,7 @@ fn gcm( return error.OperationError; } - const out = try exec.call_arena.alloc(u8, ct.len + 16); + const out = try exec.local_arena.alloc(u8, ct.len + 16); var out_len: c_int = 0; if (ct.len > 0 and cipherUpdate(ctx, out.ptr, &out_len, @ptrCast(ct.ptr), @intCast(ct.len), false) != 1) { return error.OperationError; diff --git a/src/browser/webapi/crypto/EC.zig b/src/browser/webapi/crypto/EC.zig index afa38259b..ab1f753ba 100644 --- a/src/browser/webapi/crypto/EC.zig +++ b/src/browser/webapi/crypto/EC.zig @@ -236,7 +236,7 @@ pub fn deriveBits( if (crypto.EVP_PKEY_derive(ctx, null, &secret_len) != 1 or secret_len == 0) { return error.OperationError; } - const secret = try exec.call_arena.alloc(u8, secret_len); + const secret = try exec.local_arena.alloc(u8, secret_len); if (crypto.EVP_PKEY_derive(ctx, secret.ptr, &secret_len) != 1) { return error.OperationError; } diff --git a/src/browser/webapi/crypto/HMAC.zig b/src/browser/webapi/crypto/HMAC.zig index a897a02db..625ff7cbf 100644 --- a/src/browser/webapi/crypto/HMAC.zig +++ b/src/browser/webapi/crypto/HMAC.zig @@ -151,7 +151,7 @@ pub fn sign( return resolver.promise(); } - const buffer = try exec.call_arena.alloc(u8, crypto.EVP_MD_size(crypto_key.getDigest())); + const buffer = try exec.local_arena.alloc(u8, crypto.EVP_MD_size(crypto_key.getDigest())); var out_len: u32 = 0; // Try to sign. _ = crypto.HMAC( @@ -163,7 +163,6 @@ pub fn sign( buffer.ptr, &out_len, ) orelse { - exec.call_arena.free(buffer); // Failure. resolver.rejectError("HMAC.sign", .{ .dom_exception = .{ .err = error.InvalidAccessError } }); return resolver.promise(); diff --git a/src/browser/webapi/crypto/KDF.zig b/src/browser/webapi/crypto/KDF.zig index 98aa87b63..5f312f1df 100644 --- a/src/browser/webapi/crypto/KDF.zig +++ b/src/browser/webapi/crypto/KDF.zig @@ -64,7 +64,7 @@ pub fn pbkdf2( ) Error![]const u8 { try checkBaseKey(base_key, "PBKDF2", usage_ok); const digest = crypto.findDigest(params.hash.name()) catch return error.NotSupported; - const out = try exec.call_arena.alloc(u8, try outputLen(length)); + const out = try exec.local_arena.alloc(u8, try outputLen(length)); // A zero-length derivation is valid and yields an empty buffer; the C // routines reject a zero output length, so short-circuit here. if (out.len == 0) { @@ -97,7 +97,7 @@ pub fn hkdf( ) Error![]const u8 { try checkBaseKey(base_key, "HKDF", usage_ok); const digest = crypto.findDigest(params.hash.name()) catch return error.NotSupported; - const out = try exec.call_arena.alloc(u8, try outputLen(length)); + const out = try exec.local_arena.alloc(u8, try outputLen(length)); // A zero-length derivation is valid and yields an empty buffer; the C // routines reject a zero output length, so short-circuit here. if (out.len == 0) { diff --git a/src/browser/webapi/crypto/X25519.zig b/src/browser/webapi/crypto/X25519.zig index 4b8b74e17..631b6687c 100644 --- a/src/browser/webapi/crypto/X25519.zig +++ b/src/browser/webapi/crypto/X25519.zig @@ -138,8 +138,8 @@ pub fn deriveBits( return error.Internal; } - const derived_key = try exec.call_arena.alloc(u8, 32); - errdefer exec.call_arena.free(derived_key); + const derived_key = try exec.local_arena.alloc(u8, 32); + errdefer exec.local_arena.free(derived_key); var out_key_len: usize = derived_key.len; const result = crypto.EVP_PKEY_derive(ctx, derived_key.ptr, &out_key_len); diff --git a/src/browser/webapi/css/CSSStyleDeclaration.zig b/src/browser/webapi/css/CSSStyleDeclaration.zig index 393243187..c0f2f7f49 100644 --- a/src/browser/webapi/css/CSSStyleDeclaration.zig +++ b/src/browser/webapi/css/CSSStyleDeclaration.zig @@ -151,7 +151,7 @@ fn setPropertyImpl(self: *CSSStyleDeclaration, property_name: []const u8, value: const normalized = normalizePropertyName(property_name, &frame.buf); // Normalize the value for canonical serialization - const normalized_value = try normalizePropertyValue(frame.call_arena, normalized, value); + const normalized_value = try normalizePropertyValue(frame.local_arena, normalized, value); // Find existing property if (self.findProperty(.wrap(normalized))) |existing| { @@ -206,7 +206,7 @@ pub fn setFloat(self: *CSSStyleDeclaration, value_: ?[]const u8, frame: *Frame) } pub fn getCssText(self: *const CSSStyleDeclaration, frame: *Frame) ![]const u8 { - var buf = std.Io.Writer.Allocating.init(frame.call_arena); + var buf = std.Io.Writer.Allocating.init(frame.local_arena); try self.format(&buf.writer); return buf.written(); } diff --git a/src/browser/webapi/css/CSSStyleRule.zig b/src/browser/webapi/css/CSSStyleRule.zig index e3450b2b4..f28f0d011 100644 --- a/src/browser/webapi/css/CSSStyleRule.zig +++ b/src/browser/webapi/css/CSSStyleRule.zig @@ -38,7 +38,7 @@ pub fn getStyle(self: *CSSStyleRule, frame: *Frame) !*CSSStyleProperties { pub fn getCssText(self: *CSSStyleRule, frame: *Frame) ![]const u8 { const style_props = try self.getStyle(frame); const style = style_props.asCSSStyleDeclaration(); - var buf = std.Io.Writer.Allocating.init(frame.call_arena); + var buf = std.Io.Writer.Allocating.init(frame.local_arena); try buf.writer.print("{s} {{ ", .{self._selector_text}); try style.format(&buf.writer); try buf.writer.writeAll(" }"); diff --git a/src/browser/webapi/css/CSSStyleSheet.zig b/src/browser/webapi/css/CSSStyleSheet.zig index c21c5d000..dd08118ef 100644 --- a/src/browser/webapi/css/CSSStyleSheet.zig +++ b/src/browser/webapi/css/CSSStyleSheet.zig @@ -66,7 +66,7 @@ pub fn getCssRules(self: *CSSStyleSheet, frame: *Frame) !*CSSRuleList { if (self.getOwnerNode()) |owner| { if (owner.is(Element.Html.Style)) |style| { - const text = try style.asNode().getTextContentAlloc(frame.call_arena); + const text = try style.asNode().getTextContentAlloc(frame.local_arena); try self.replaceSync(text, frame); } } diff --git a/src/browser/webapi/element/DOMStringMap.zig b/src/browser/webapi/element/DOMStringMap.zig index 7fd23f34d..a7fff6551 100644 --- a/src/browser/webapi/element/DOMStringMap.zig +++ b/src/browser/webapi/element/DOMStringMap.zig @@ -32,17 +32,17 @@ const DOMStringMap = @This(); _element: *Element, fn getProperty(self: *DOMStringMap, name: String, frame: *Frame) !?String { - const attr_name = try camelToKebab(frame.call_arena, name); + const attr_name = try camelToKebab(frame.local_arena, name); return try self._element.getAttribute(attr_name, frame); } fn setProperty(self: *DOMStringMap, name: String, value: String, frame: *Frame) !void { - const attr_name = try camelToKebab(frame.call_arena, name); + const attr_name = try camelToKebab(frame.local_arena, name); return self._element.setAttributeSafe(attr_name, value, frame); } fn deleteProperty(self: *DOMStringMap, name: String, frame: *Frame) !void { - const attr_name = try camelToKebab(frame.call_arena, name); + const attr_name = try camelToKebab(frame.local_arena, name); try self._element.removeAttribute(attr_name, frame); } diff --git a/src/browser/webapi/element/Html.zig b/src/browser/webapi/element/Html.zig index eb55b4bc6..d643a43c4 100644 --- a/src/browser/webapi/element/Html.zig +++ b/src/browser/webapi/element/Html.zig @@ -1598,7 +1598,7 @@ fn mergeTextNodes(left_node: *Node, right_node: *Node, frame: *Frame) !bool { // both nodes are Text nodes - const merged = try std.mem.concat(frame.call_arena, u8, &.{ left.getData().str(), right.getData().str() }); + const merged = try std.mem.concat(frame.local_arena, u8, &.{ left.getData().str(), right.getData().str() }); // set the left node to the merged value try left.setData(merged, frame); @@ -1610,7 +1610,7 @@ fn mergeTextNodes(left_node: *Node, right_node: *Node, frame: *Frame) !bool { } fn renderedTextFragment(value: []const u8, frame: *Frame) ![]Node.NodeOrText { - const arena = frame.call_arena; + const arena = frame.local_arena; var nodes: std.ArrayList(Node.NodeOrText) = .empty; var rest = value; diff --git a/src/browser/webapi/element/html/Anchor.zig b/src/browser/webapi/element/html/Anchor.zig index 0a55c457f..8d36299d5 100644 --- a/src/browser/webapi/element/html/Anchor.zig +++ b/src/browser/webapi/element/html/Anchor.zig @@ -64,7 +64,7 @@ pub fn setTarget(self: *Anchor, value: []const u8, frame: *Frame) !void { pub fn getOrigin(self: *Anchor, frame: *Frame) ![]const u8 { const href = try getResolvedHref(self, frame) orelse return ""; - return (try URL.getOrigin(frame.call_arena, href)) orelse "null"; + return (try URL.getOrigin(frame.local_arena, href)) orelse "null"; } pub fn getHost(self: *Anchor, frame: *Frame) ![]const u8 { diff --git a/src/browser/webapi/element/html/Area.zig b/src/browser/webapi/element/html/Area.zig index b58f5fe44..0deaa70ad 100644 --- a/src/browser/webapi/element/html/Area.zig +++ b/src/browser/webapi/element/html/Area.zig @@ -51,7 +51,7 @@ pub fn setHref(self: *Area, value: []const u8, frame: *Frame) !void { pub fn getOrigin(self: *Area, frame: *Frame) ![]const u8 { const href = try getResolvedHref(self, frame) orelse return ""; - return (try URL.getOrigin(frame.call_arena, href)) orelse "null"; + return (try URL.getOrigin(frame.local_arena, href)) orelse "null"; } pub fn getHost(self: *Area, frame: *Frame) ![]const u8 { diff --git a/src/browser/webapi/element/html/Base.zig b/src/browser/webapi/element/html/Base.zig index ee47c0453..3e25dc8f4 100644 --- a/src/browser/webapi/element/html/Base.zig +++ b/src/browser/webapi/element/html/Base.zig @@ -32,7 +32,7 @@ pub fn getHref(self: *Base, frame: *Frame) ![]const u8 { return ""; } const owner = element.asConstNode().ownerFrame(frame); - return URL.resolve(frame.call_arena, owner.url, href, .{}); + return URL.resolve(frame.local_arena, owner.url, href, .{}); } pub fn setHref(self: *Base, value: []const u8, frame: *Frame) !void { diff --git a/src/browser/webapi/element/html/Canvas.zig b/src/browser/webapi/element/html/Canvas.zig index 05acdee2f..775afa65f 100644 --- a/src/browser/webapi/element/html/Canvas.zig +++ b/src/browser/webapi/element/html/Canvas.zig @@ -49,7 +49,7 @@ pub fn getWidth(self: *const Canvas) u32 { } pub fn setWidth(self: *Canvas, value: u32, frame: *Frame) !void { - const str = try std.fmt.allocPrint(frame.call_arena, "{d}", .{value}); + const str = try std.fmt.allocPrint(frame.local_arena, "{d}", .{value}); try self.asElement().setAttributeSafe(comptime .wrap("width"), .wrap(str), frame); } @@ -59,7 +59,7 @@ pub fn getHeight(self: *const Canvas) u32 { } pub fn setHeight(self: *Canvas, value: u32, frame: *Frame) !void { - const str = try std.fmt.allocPrint(frame.call_arena, "{d}", .{value}); + const str = try std.fmt.allocPrint(frame.local_arena, "{d}", .{value}); try self.asElement().setAttributeSafe(comptime .wrap("height"), .wrap(str), frame); } diff --git a/src/browser/webapi/element/html/Input.zig b/src/browser/webapi/element/html/Input.zig index c665daa96..3518f0d2a 100644 --- a/src/browser/webapi/element/html/Input.zig +++ b/src/browser/webapi/element/html/Input.zig @@ -302,7 +302,7 @@ pub fn getValueForJS(self: *const Input, frame: *Frame) ![]const u8 { if (fl._files.len == 0) { return ""; } - return try std.fmt.allocPrint(frame.call_arena, "C:\\fakepath\\{s}", .{fl._files[0]._name}); + return try std.fmt.allocPrint(frame.local_arena, "C:\\fakepath\\{s}", .{fl._files[0]._name}); } pub fn getValidationMessage(self: *Input, frame: *Frame) []const u8 { diff --git a/src/cdp/CDP.zig b/src/cdp/CDP.zig index 0c818caa8..1f76c9ab6 100644 --- a/src/cdp/CDP.zig +++ b/src/cdp/CDP.zig @@ -691,10 +691,14 @@ pub const BrowserContext = struct { const call_arena = try browser.arena_pool.acquire(.tiny, "IsolatedWorld.call_arena"); errdefer browser.arena_pool.release(call_arena); + const local_arena = try browser.arena_pool.acquire(.tiny, "IsolatedWorld.local_arena"); + errdefer browser.arena_pool.release(local_arena); + const world = try arena.create(IsolatedWorld); world.* = .{ .arena = arena, .call_arena = call_arena, + .local_arena = local_arena, .context = null, .browser = browser, .name = try arena.dupe(u8, world_name), @@ -1118,6 +1122,7 @@ const ScriptOnNewDocument = struct { const IsolatedWorld = struct { arena: Allocator, call_arena: Allocator, + local_arena: Allocator, browser: *Browser, name: []const u8, context: ?*js.Context = null, @@ -1130,6 +1135,7 @@ const IsolatedWorld = struct { pub fn deinit(self: *IsolatedWorld) void { self.removeContext(); self.browser.arena_pool.release(self.call_arena); + self.browser.arena_pool.release(self.local_arena); self.browser.arena_pool.release(self.arena); } @@ -1155,6 +1161,7 @@ const IsolatedWorld = struct { .identity = &self.identity, .identity_arena = self.arena, .call_arena = self.call_arena, + .local_arena = self.local_arena, .debug_name = "IsolatedContext", }); self.context = ctx;