Add arena buckets to ArenaPool

ArenaPool previously maintained up to 512 16KB buckets. The 16KB retention is
small for things like XHR and scripts, but increasing it to something more
reasonably, like 128KB, would use up to 8x more memory.

This commit adds 4 buckets: 1KB, 4KB, 16KB and 128KB. Callers can request a
tiny, small, medium or large bucket. We end up using less memory peak memory
and less allocations.

Furthermore, callers can request a specific size. This is particularly useful
for WebSocket or Blob where the size could vary greatly (so we'd likely default
to a large bucket), but that could needlessly use up a large arena.

The bucket sizes were derived from analyzing allocations. A significant number
of allocations were very small. Things like ScheduleCallback and
FinalizerCallback are always less than 1K and can be generated in the thousands.
The 16KB retention was wasteful in these cases...better to have a large number
of 1K pools, so that we can have a handful of very large buffers.
This commit is contained in:
Karl Seguin
2026-04-10 19:09:18 +08:00
parent 18176b554c
commit ddf614a9d5
54 changed files with 314 additions and 204 deletions

View File

@@ -69,7 +69,7 @@ pub fn init(allocator: Allocator, config: *const Config) !*App {
app.telemetry = try Telemetry.init(app, config.mode);
errdefer app.telemetry.deinit(allocator);
app.arena_pool = ArenaPool.init(allocator, 512, 1024 * 16);
app.arena_pool = ArenaPool.init(allocator, .{});
errdefer app.arena_pool.deinit();
return app;

View File

@@ -27,33 +27,52 @@ const ArenaPool = @This();
const IS_DEBUG = builtin.mode == .Debug;
allocator: Allocator,
retain_bytes: usize,
free_list_len: u16 = 0,
free_list: ?*Entry = null,
free_list_max: u16,
entry_pool: std.heap.MemoryPool(Entry),
mutex: std.Thread.Mutex = .{},
// Debug mode: track acquire/release counts per debug name to detect leaks and double-frees
_leak_track: if (IS_DEBUG) std.StringHashMapUnmanaged(isize) else void = if (IS_DEBUG) .empty else {},
pub const BucketSize = enum { tiny, small, medium, large };
const Bucket = struct {
free_list: ?*Entry = null,
free_list_len: u16 = 0,
free_list_max: u16,
retain_bytes: usize,
};
const Entry = struct {
next: ?*Entry,
arena: ArenaAllocator,
bucket: *Bucket,
debug: if (IS_DEBUG) []const u8 else void = if (IS_DEBUG) "" else {},
};
pub const DebugInfo = struct {
debug: []const u8 = "",
pub const Config = struct {
tiny: Config.Bucket = .{ .max = 512, .retain = 1024 },
small: Config.Bucket = .{ .max = 128, .retain = 4 * 1024 },
medium: Config.Bucket = .{ .max = 64, .retain = 16 * 1024 },
large: Config.Bucket = .{ .max = 32, .retain = 128 * 1024 },
const Bucket = struct {
max: u16,
retain: usize,
};
};
pub fn init(allocator: Allocator, free_list_max: u16, retain_bytes: usize) ArenaPool {
tiny: Bucket,
small: Bucket,
medium: Bucket,
large: Bucket,
allocator: Allocator,
mutex: std.Thread.Mutex = .{},
entry_pool: std.heap.MemoryPool(Entry),
_leak_track: if (IS_DEBUG) std.StringHashMapUnmanaged(isize) else void = if (IS_DEBUG) .empty else {},
pub fn init(allocator: Allocator, config: Config) ArenaPool {
return .{
.allocator = allocator,
.free_list_max = free_list_max,
.retain_bytes = retain_bytes,
.entry_pool = .init(allocator),
._leak_track = if (IS_DEBUG) .empty else {},
.tiny = .{ .free_list_max = config.tiny.max, .retain_bytes = config.tiny.retain },
.small = .{ .free_list_max = config.small.max, .retain_bytes = config.small.retain },
.medium = .{ .free_list_max = config.medium.max, .retain_bytes = config.medium.retain },
.large = .{ .free_list_max = config.large.max, .retain_bytes = config.large.retain },
};
}
@@ -73,24 +92,49 @@ pub fn deinit(self: *ArenaPool) void {
self._leak_track.deinit(self.allocator);
}
var entry = self.free_list;
while (entry) |e| {
entry = e.next;
e.arena.deinit();
// Free all arenas in all buckets
inline for (&[_]*Bucket{ &self.tiny, &self.small, &self.medium, &self.large }) |bucket| {
var entry = bucket.free_list;
while (entry) |e| {
entry = e.next;
e.arena.deinit();
}
}
self.entry_pool.deinit();
}
pub fn acquire(self: *ArenaPool, dbg: DebugInfo) !Allocator {
// Acquire an arena from the pool.
// - Pass a BucketSize (.tiny, .small, .medium, .large) for explicit bucket selection
// - Pass a usize for automatic bucket selection based on expected size
pub fn acquire(self: *ArenaPool, size_or_bucket: anytype, debug: []const u8) !Allocator {
const bucket = blk: {
const T = @TypeOf(size_or_bucket);
if (T == BucketSize or T == @TypeOf(.enum_literal)) {
break :blk switch (@as(BucketSize, size_or_bucket)) {
.tiny => &self.tiny,
.small => &self.small,
.medium => &self.medium,
.large => &self.large,
};
}
if (T == usize or T == comptime_int) {
if (size_or_bucket <= self.tiny.retain_bytes) break :blk &self.tiny;
if (size_or_bucket <= self.small.retain_bytes) break :blk &self.small;
if (size_or_bucket <= self.medium.retain_bytes) break :blk &self.medium;
break :blk &self.large;
}
@compileError("acquire expects BucketSize or usize, got " ++ @typeName(T));
};
self.mutex.lock();
defer self.mutex.unlock();
if (self.free_list) |entry| {
self.free_list = entry.next;
self.free_list_len -= 1;
if (bucket.free_list) |entry| {
bucket.free_list = entry.next;
bucket.free_list_len -= 1;
if (IS_DEBUG) {
entry.debug = dbg.debug;
const gop = try self._leak_track.getOrPut(self.allocator, dbg.debug);
entry.debug = debug;
const gop = try self._leak_track.getOrPut(self.allocator, debug);
if (!gop.found_existing) {
gop.value_ptr.* = 0;
}
@@ -102,12 +146,13 @@ pub fn acquire(self: *ArenaPool, dbg: DebugInfo) !Allocator {
const entry = try self.entry_pool.create();
entry.* = .{
.next = null,
.bucket = bucket,
.debug = if (IS_DEBUG) debug else {},
.arena = ArenaAllocator.init(self.allocator),
.debug = if (IS_DEBUG) dbg.debug else {},
};
if (IS_DEBUG) {
const gop = try self._leak_track.getOrPut(self.allocator, dbg.debug);
const gop = try self._leak_track.getOrPut(self.allocator, debug);
if (!gop.found_existing) {
gop.value_ptr.* = 0;
}
@@ -116,12 +161,14 @@ pub fn acquire(self: *ArenaPool, dbg: DebugInfo) !Allocator {
return entry.arena.allocator();
}
// Universal release - determines bucket from the Entry automatically
pub fn release(self: *ArenaPool, allocator: Allocator) void {
const arena: *std.heap.ArenaAllocator = @ptrCast(@alignCast(allocator.ptr));
const arena: *ArenaAllocator = @ptrCast(@alignCast(allocator.ptr));
const entry: *Entry = @fieldParentPtr("arena", arena);
const bucket = entry.bucket;
// Reset the arena before acquiring the lock to minimize lock hold time
_ = arena.reset(.{ .retain_with_limit = self.retain_bytes });
_ = arena.reset(.{ .retain_with_limit = bucket.retain_bytes });
self.mutex.lock();
defer self.mutex.unlock();
@@ -139,105 +186,113 @@ pub fn release(self: *ArenaPool, allocator: Allocator) void {
}
}
const free_list_len = self.free_list_len;
if (free_list_len == self.free_list_max) {
if (bucket.free_list_len >= bucket.free_list_max) {
arena.deinit();
self.entry_pool.destroy(entry);
return;
}
entry.next = self.free_list;
self.free_list_len = free_list_len + 1;
self.free_list = entry;
entry.next = bucket.free_list;
bucket.free_list = entry;
bucket.free_list_len += 1;
}
pub fn reset(_: *const ArenaPool, allocator: Allocator, retain: usize) void {
const arena: *std.heap.ArenaAllocator = @ptrCast(@alignCast(allocator.ptr));
const arena: *ArenaAllocator = @ptrCast(@alignCast(allocator.ptr));
_ = arena.reset(.{ .retain_with_limit = retain });
}
pub fn resetRetain(_: *const ArenaPool, allocator: Allocator) void {
const arena: *std.heap.ArenaAllocator = @ptrCast(@alignCast(allocator.ptr));
const arena: *ArenaAllocator = @ptrCast(@alignCast(allocator.ptr));
_ = arena.reset(.retain_capacity);
}
const testing = std.testing;
test "arena pool - basic acquire and use" {
var pool = ArenaPool.init(testing.allocator, 512, 1024 * 16);
test "ArenaPool: basic acquire and release" {
var pool = ArenaPool.init(testing.allocator, .{});
defer pool.deinit();
const alloc = try pool.acquire(.{ .debug = "test" });
const buf = try alloc.alloc(u8, 64);
@memset(buf, 0xAB);
try testing.expectEqual(@as(u8, 0xAB), buf[0]);
const tiny = try pool.acquire(.tiny, "test-tiny");
const medium = try pool.acquire(.medium, "test-medium");
const large = try pool.acquire(.large, "test-large");
pool.release(alloc);
// All three must be distinct arenas
try testing.expect(tiny.ptr != medium.ptr);
try testing.expect(medium.ptr != large.ptr);
_ = try tiny.alloc(u8, 64);
_ = try medium.alloc(u8, 1024);
_ = try large.alloc(u8, 4096);
// Universal release works for all buckets
pool.release(tiny);
pool.release(medium);
pool.release(large);
try testing.expectEqual(1, pool.tiny.free_list_len);
try testing.expectEqual(1, pool.medium.free_list_len);
try testing.expectEqual(1, pool.large.free_list_len);
}
test "arena pool - reuse entry after release" {
var pool = ArenaPool.init(testing.allocator, 512, 1024 * 16);
test "ArenaPool: reuse from correct bucket" {
var pool = ArenaPool.init(testing.allocator, .{});
defer pool.deinit();
const alloc1 = try pool.acquire(.{ .debug = "test" });
try testing.expectEqual(@as(u16, 0), pool.free_list_len);
const tiny1 = try pool.acquire(.tiny, "test");
pool.release(tiny1);
try testing.expectEqual(1, pool.tiny.free_list_len);
pool.release(alloc1);
try testing.expectEqual(@as(u16, 1), pool.free_list_len);
// Next acquire with .tiny should reuse from tiny bucket
const tiny2 = try pool.acquire(.tiny, "test");
try testing.expectEqual(0, pool.tiny.free_list_len);
try testing.expectEqual(tiny1.ptr, tiny2.ptr);
// The same entry should be returned from the free list.
const alloc2 = try pool.acquire(.{ .debug = "test" });
try testing.expectEqual(@as(u16, 0), pool.free_list_len);
try testing.expectEqual(alloc1.ptr, alloc2.ptr);
// acquire with .medium should NOT get the tiny arena
const medium = try pool.acquire(.medium, "test-medium");
try testing.expect(medium.ptr != tiny2.ptr);
pool.release(alloc2);
pool.release(tiny2);
pool.release(medium);
}
test "arena pool - multiple concurrent arenas" {
var pool = ArenaPool.init(testing.allocator, 512, 1024 * 16);
test "ArenaPool: respects per-bucket max limits" {
var pool = ArenaPool.init(testing.allocator, .{
.tiny = .{ .max = 1, .retain = 1024 },
.medium = .{ .max = 2, .retain = 1024 },
.large = .{ .max = 1, .retain = 1024 },
});
defer pool.deinit();
const a1 = try pool.acquire(.{ .debug = "test1" });
const a2 = try pool.acquire(.{ .debug = "test2" });
const a3 = try pool.acquire(.{ .debug = "test3" });
// Acquire 3 tiny arenas
const t1 = try pool.acquire(.tiny, "t1");
const t2 = try pool.acquire(.tiny, "t2");
const t3 = try pool.acquire(.tiny, "t3");
// All three must be distinct arenas.
try testing.expect(a1.ptr != a2.ptr);
try testing.expect(a2.ptr != a3.ptr);
try testing.expect(a1.ptr != a3.ptr);
// Release all 3, but only 1 should be kept (tiny_max = 1)
pool.release(t1);
try testing.expectEqual(1, pool.tiny.free_list_len);
pool.release(t2);
try testing.expectEqual(1, pool.tiny.free_list_len); // still 1, t2 discarded
pool.release(t3);
try testing.expectEqual(1, pool.tiny.free_list_len); // still 1, t3 discarded
_ = try a1.alloc(u8, 16);
_ = try a2.alloc(u8, 32);
_ = try a3.alloc(u8, 48);
// Acquire 3 medium arenas
const m1 = try pool.acquire(.medium, "m1");
const m2 = try pool.acquire(.medium, "m2");
const m3 = try pool.acquire(.medium, "m3");
pool.release(a1);
pool.release(a2);
pool.release(a3);
try testing.expectEqual(@as(u16, 3), pool.free_list_len);
// Release all 3, but only 2 should be kept (medium_max = 2)
pool.release(m1);
pool.release(m2);
pool.release(m3);
try testing.expectEqual(2, pool.medium.free_list_len);
}
test "arena pool - free list respects max limit" {
// Cap the free list at 1 so the second release discards its arena.
var pool = ArenaPool.init(testing.allocator, 1, 1024 * 16);
test "ArenaPool: reset clears memory without releasing" {
var pool = ArenaPool.init(testing.allocator, .{});
defer pool.deinit();
const a1 = try pool.acquire(.{ .debug = "test1" });
const a2 = try pool.acquire(.{ .debug = "test2" });
pool.release(a1);
try testing.expectEqual(@as(u16, 1), pool.free_list_len);
// The free list is full; a2's arena should be destroyed, not queued.
pool.release(a2);
try testing.expectEqual(@as(u16, 1), pool.free_list_len);
}
test "arena pool - reset clears memory without releasing" {
var pool = ArenaPool.init(testing.allocator, 512, 1024 * 16);
defer pool.deinit();
const alloc = try pool.acquire(.{ .debug = "test" });
const alloc = try pool.acquire(.medium, "test");
const buf = try alloc.alloc(u8, 128);
@memset(buf, 0xFF);
@@ -246,7 +301,7 @@ test "arena pool - reset clears memory without releasing" {
pool.reset(alloc, 0);
// The free list must stay empty; the allocator was not released.
try testing.expectEqual(@as(u16, 0), pool.free_list_len);
try testing.expectEqual(0, pool.medium.free_list_len);
// Allocating again through the same arena must still work.
const buf2 = try alloc.alloc(u8, 64);
@@ -256,18 +311,60 @@ test "arena pool - reset clears memory without releasing" {
pool.release(alloc);
}
test "arena pool - deinit with entries in free list" {
test "ArenaPool: deinit with entries in free list" {
// Verifies that deinit properly cleans up free-listed arenas (no leaks
// detected by the test allocator).
var pool = ArenaPool.init(testing.allocator, 512, 1024 * 16);
var pool = ArenaPool.init(testing.allocator, .{});
const a1 = try pool.acquire(.{ .debug = "test1" });
const a2 = try pool.acquire(.{ .debug = "test2" });
const a1 = try pool.acquire(.tiny, "test1");
const a2 = try pool.acquire(.medium, "test2");
_ = try a1.alloc(u8, 256);
_ = try a2.alloc(u8, 512);
pool.release(a1);
pool.release(a2);
try testing.expectEqual(@as(u16, 2), pool.free_list_len);
try testing.expectEqual(1, pool.tiny.free_list_len);
try testing.expectEqual(1, pool.medium.free_list_len);
pool.deinit();
}
test "ArenaPool: small bucket" {
var pool = ArenaPool.init(testing.allocator, .{
.small = .{ .max = 2, .retain = 4 * 1024 },
});
defer pool.deinit();
const s1 = try pool.acquire(.small, "s1");
const s2 = try pool.acquire(.small, "s2");
const s3 = try pool.acquire(.small, "s3");
pool.release(s1);
pool.release(s2);
pool.release(s3);
try testing.expectEqual(2, pool.small.free_list_len);
}
test "ArenaPool: size-based acquire" {
var pool = ArenaPool.init(testing.allocator, .{});
defer pool.deinit();
// <= 1KB -> tiny
const a = try pool.acquire(500, "fits-tiny");
// <= 4KB -> small
const b = try pool.acquire(2000, "fits-small");
// <= 16KB -> medium
const c = try pool.acquire(8000, "fits-medium");
// > 16KB -> large
const d = try pool.acquire(20000, "fits-large");
pool.release(a);
pool.release(b);
pool.release(c);
pool.release(d);
try testing.expectEqual(1, pool.tiny.free_list_len);
try testing.expectEqual(1, pool.small.free_list_len);
try testing.expectEqual(1, pool.medium.free_list_len);
try testing.expectEqual(1, pool.large.free_list_len);
}

View File

@@ -374,7 +374,8 @@ fn serveFromCache(req: Request, cached: *const CachedResponse) !void {
fn processRequest(self: *Client, req: Request) !void {
if (self.network.cache) |*cache| {
if (req.method == .GET) {
const arena = try self.network.app.arena_pool.acquire(.{ .debug = "HttpClient.processRequest.cache" });
// cache is only used to read the meta data
const arena = try self.network.app.arena_pool.acquire(.small, "HttpClient.cache");
defer self.network.app.arena_pool.release(arena);
var iter = req.headers.iterator();

View File

@@ -248,7 +248,7 @@ pub fn init(self: *Page, frame_id: u32, session: *Session, parent: ?*Page) !void
log.debug(.page, "page.init", .{});
}
const call_arena = try session.getArena(.{ .debug = "call_arena" });
const call_arena = try session.getArena(.medium, "call_arena");
errdefer session.releaseArena(call_arena);
const factory = &session.factory;
@@ -429,8 +429,8 @@ pub fn headersForRequest(self: *Page, headers: *HttpClient.Headers) !void {
}
}
pub fn getArena(self: *Page, comptime opts: Session.GetArenaOpts) !Allocator {
return self._session.getArena(opts);
pub fn getArena(self: *Page, size_or_bucket: anytype, debug: []const u8) !Allocator {
return self._session.getArena(size_or_bucket, debug);
}
pub fn releaseArena(self: *Page, allocator: Allocator) void {
@@ -510,7 +510,7 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
log.warn(.js, "invalid blob", .{ .url = request_url });
return error.BlobNotFound;
};
const parse_arena = try self.getArena(.{ .debug = "Page.parseBlob" });
const parse_arena = try self.getArena(.medium, "Page.parseBlob");
defer self.releaseArena(parse_arena);
var parser = Parser.init(parse_arena, self.document.asNode(), self);
parser.parse(blob._slice);
@@ -619,7 +619,7 @@ pub fn scheduleNavigation(self: *Page, request_url: []const u8, opts: NavigateOp
if (self.canScheduleNavigation(std.meta.activeTag(nt)) == false) {
return;
}
const arena = try self._session.getArena(.{ .debug = "scheduleNavigation" });
const arena = try self._session.getArena(.small, "scheduleNavigation");
errdefer self._session.releaseArena(arena);
return self.scheduleNavigationWithArena(arena, request_url, opts, nt);
}
@@ -1022,7 +1022,7 @@ fn pageDoneCallback(ctx: *anyopaque) !void {
});
};
const parse_arena = try self.getArena(.{ .debug = "Page.parse" });
const parse_arena = try self.getArena(.medium, "Page.parse");
defer self.releaseArena(parse_arena);
var parser = Parser.init(parse_arena, self.document.asNode(), self);
@@ -3568,7 +3568,7 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form
// I don't think this is technically correct, but FormData handles it ok
const form_data = try FormData.init(form, submitter_, self);
const arena = try self._session.getArena(.{ .debug = "submitForm" });
const arena = try self._session.getArena(.medium, "submitForm");
errdefer self._session.releaseArena(arena);
const encoding = form_element.getAttributeSafe(comptime .wrap("enctype"));

View File

@@ -249,7 +249,7 @@ fn _tick(self: *Runner, comptime is_cdp: bool, opts: TickOpts) !CDPTickResult {
}
pub fn waitForSelector(self: *Runner, selector: [:0]const u8, timeout_ms: u32) !*Node.Element {
const arena = try self.session.getArena(.{ .debug = "Runner.waitForSelector" });
const arena = try self.session.getArena(.small, "Runner.waitForSelector");
defer self.session.releaseArena(arena);
var timer = try std.time.Timer.start();

View File

@@ -188,7 +188,7 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
var handover = false;
const page = self.page;
const arena = try page.getArena(.{ .debug = "addFromElement" });
const arena = try page.getArena(.large, "SM.addFromElement");
errdefer if (!handover) {
page.releaseArena(arena);
};
@@ -369,7 +369,7 @@ pub fn preloadImport(self: *ScriptManager, url: [:0]const u8, referrer: []const
errdefer _ = self.imported_modules.remove(url);
const page = self.page;
const arena = try page.getArena(.{ .debug = "preloadImport" });
const arena = try page.getArena(.large, "SM.preloadImport");
errdefer page.releaseArena(arena);
const script = try arena.create(Script);
@@ -469,7 +469,7 @@ pub fn waitForImport(self: *ScriptManager, url: [:0]const u8) !ModuleSource {
pub fn getAsyncImport(self: *ScriptManager, url: [:0]const u8, cb: ImportAsync.Callback, cb_data: *anyopaque, referrer: []const u8) !void {
const page = self.page;
const arena = try page.getArena(.{ .debug = "getAsyncImport" });
const arena = try page.getArena(.large, "SM.getAsyncImport");
errdefer page.releaseArena(arena);
const script = try arena.create(Script);

View File

@@ -110,10 +110,10 @@ pub fn init(self: *Session, browser: *Browser, notification: *Notification) !voi
const allocator = browser.app.allocator;
const arena_pool = browser.arena_pool;
const arena = try arena_pool.acquire(.{ .debug = "Session" });
const arena = try arena_pool.acquire(.small, "Session");
errdefer arena_pool.release(arena);
const page_arena = try arena_pool.acquire(.{ .debug = "Session.page_arena" });
const page_arena = try arena_pool.acquire(.large, "Session.page_arena");
errdefer arena_pool.release(page_arena);
self.* = .{
@@ -186,12 +186,8 @@ pub fn removePage(self: *Session) void {
}
}
pub const GetArenaOpts = struct {
debug: []const u8,
};
pub fn getArena(self: *Session, opts: GetArenaOpts) !Allocator {
return self.arena_pool.acquire(.{ .debug = opts.debug });
pub fn getArena(self: *Session, size_or_bucket: anytype, debug: []const u8) !Allocator {
return self.arena_pool.acquire(size_or_bucket, debug);
}
pub fn releaseArena(self: *Session, allocator: Allocator) void {

View File

@@ -66,7 +66,7 @@ dirty: bool = false,
pub fn init(page: *Page) !StyleManager {
return .{
.page = page,
.arena = try page.getArena(.{ .debug = "StyleManager" }),
.arena = try page.getArena(.medium, "StyleManager"),
};
}

View File

@@ -261,7 +261,7 @@ pub const ContextParams = struct {
};
pub fn createContext(self: *Env, page: *Page, params: ContextParams) !*Context {
const context_arena = try self.app.arena_pool.acquire(.{ .debug = params.debug_name });
const context_arena = try self.app.arena_pool.acquire(.large, params.debug_name);
errdefer self.app.arena_pool.release(context_arena);
const isolate = self.isolate;

View File

@@ -1479,7 +1479,7 @@ fn createFinalizerCallback(
) !*Session.FinalizerCallback {
const session = self.ctx.session;
const arena = try session.getArena(.{ .debug = "FinalizerCallback" });
const arena = try session.getArena(.tiny, "FinalizerCallback");
errdefer session.releaseArena(arena);
const fc = try arena.create(Session.FinalizerCallback);

View File

@@ -45,7 +45,7 @@ key: []const u8,
security_token: v8.Global,
pub fn init(app: *App, isolate: js.Isolate, key: []const u8) !*Origin {
const arena = try app.arena_pool.acquire(.{ .debug = "Origin" });
const arena = try app.arena_pool.acquire(.tiny, "Origin");
errdefer app.arena_pool.release(arena);
var hs: js.HandleScope = undefined;

View File

@@ -44,11 +44,11 @@ fn _toSlice(self: String, comptime null_terminate: bool, allocator: Allocator) !
const handle = self.handle;
const isolate = local.isolate.handle;
const len = v8.v8__String__Utf8Length(handle, isolate);
const buf = try (if (comptime null_terminate) allocator.allocSentinel(u8, @intCast(len), 0) else allocator.alloc(u8, @intCast(len)));
const l = v8.v8__String__Utf8Length(handle, isolate);
const buf = try (if (comptime null_terminate) allocator.allocSentinel(u8, @intCast(l), 0) else allocator.alloc(u8, @intCast(l)));
const n = v8.v8__String__WriteUtf8(handle, isolate, buf.ptr, buf.len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8);
if (comptime IS_DEBUG) {
std.debug.assert(n == len);
std.debug.assert(n == l);
}
return buf;
@@ -64,32 +64,32 @@ pub fn toSSOWithAlloc(self: String, allocator: Allocator) !SSO {
const handle = self.handle;
const isolate = self.local.isolate.handle;
const len: usize = @intCast(v8.v8__String__Utf8Length(handle, isolate));
const l: usize = @intCast(v8.v8__String__Utf8Length(handle, isolate));
if (len <= 12) {
if (l <= 12) {
var content: [12]u8 = undefined;
const n = v8.v8__String__WriteUtf8(handle, isolate, &content[0], content.len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8);
if (comptime IS_DEBUG) {
std.debug.assert(n == len);
std.debug.assert(n == l);
}
// Weird that we do this _after_, but we have to..I've seen weird issues
// in ReleaseMode where v8 won't write to content if it starts off zero
// initiated
@memset(content[len..], 0);
return .{ .len = @intCast(len), .payload = .{ .content = content } };
@memset(content[l..], 0);
return .{ .len = @intCast(l), .payload = .{ .content = content } };
}
const buf = try allocator.alloc(u8, len);
const buf = try allocator.alloc(u8, l);
const n = v8.v8__String__WriteUtf8(handle, isolate, buf.ptr, buf.len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8);
if (comptime IS_DEBUG) {
std.debug.assert(n == len);
std.debug.assert(n == l);
}
var prefix: [4]u8 = @splat(0);
@memcpy(&prefix, buf[0..4]);
return .{
.len = @intCast(len),
.len = @intCast(l),
.payload = .{ .heap = .{
.prefix = prefix,
.ptr = buf.ptr,
@@ -103,9 +103,13 @@ pub fn format(self: String, writer: *std.Io.Writer) !void {
const isolate = local.isolate.handle;
var small: [1024]u8 = undefined;
const len = v8.v8__String__Utf8Length(handle, isolate);
var buf = if (len < 1024) &small else local.call_arena.alloc(u8, @intCast(len)) catch return error.WriteFailed;
const l = v8.v8__String__Utf8Length(handle, isolate);
var buf = if (l < 1024) &small else local.call_arena.alloc(u8, @intCast(l)) catch return error.WriteFailed;
const n = v8.v8__String__WriteUtf8(handle, isolate, buf.ptr, buf.len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8);
return writer.writeAll(buf[0..n]);
}
pub fn len(self: String) usize {
return @intCast(v8.v8__String__Utf8Length(self.handle, self.local.isolate.handle));
}

View File

@@ -77,7 +77,15 @@ pub fn initWithMimeValidation(
validate_mime: bool,
page: *Page,
) !*Blob {
const arena = try page.getArena(.{ .debug = "Blob" });
const data_len = blk: {
const parts = maybe_blob_parts orelse break :blk 0;
var size: usize = 0;
for (parts) |p| {
size += p.len;
}
break :blk size;
};
const arena = try page.getArena(256 + data_len, "Blob");
errdefer page.releaseArena(arena);
const options: InitOptions = maybe_options orelse .{};

View File

@@ -50,7 +50,7 @@ pub fn parseFromString(
@"image/svg+xml",
}, mime_type) orelse return error.NotSupported;
const arena = try page.getArena(.{ .debug = "DOMParser.parseFromString" });
const arena = try page.getArena(.medium, "DOMParser.parseFromString");
defer page.releaseArena(arena);
return switch (target_mime) {

View File

@@ -666,7 +666,7 @@ pub fn write(self: *Document, text: []const []const u8, page: *Page) !void {
page._parse_mode = .document_write;
defer page._parse_mode = previous_parse_mode;
const arena = try page.getArena(.{ .debug = "Document.write" });
const arena = try page.getArena(.medium, "Document.write");
defer page.releaseArena(arena);
var parser = Parser.init(arena, fragment_node, page);

View File

@@ -90,14 +90,14 @@ pub const Options = struct {
};
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*Event {
const arena = try page.getArena(.{ .debug = "Event" });
const arena = try page.getArena(.tiny, "Event");
errdefer page.releaseArena(arena);
const str = try String.init(arena, typ, .{});
return initWithTrusted(arena, str, opts_, false);
}
pub fn initTrusted(typ: String, opts_: ?Options, page: *Page) !*Event {
const arena = try page.getArena(.{ .debug = "Event.trusted" });
const arena = try page.getArena(.tiny, "Event.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, opts_, true);
}

View File

@@ -31,7 +31,7 @@ _proto: *Blob,
// TODO: Implement File API.
pub fn init(page: *Page) !*File {
const arena = try page.getArena(.{ .debug = "File" });
const arena = try page.getArena(.tiny, "File");
errdefer page.releaseArena(arena);
return page._factory.blob(arena, File{ ._proto = undefined });
}

View File

@@ -63,7 +63,7 @@ const Result = union(enum) {
};
pub fn init(page: *Page) !*FileReader {
const arena = try page.getArena(.{ .debug = "FileReader" });
const arena = try page.getArena(.tiny, "FileReader");
errdefer page.releaseArena(arena);
return page._factory.eventTargetWithAllocator(arena, FileReader{

View File

@@ -71,7 +71,7 @@ pub const ObserverInit = struct {
};
pub fn init(callback: js.Function.Temp, options: ?ObserverInit, page: *Page) !*IntersectionObserver {
const arena = try page.getArena(.{ .debug = "IntersectionObserver" });
const arena = try page.getArena(.medium, "IntersectionObserver");
errdefer page.releaseArena(arena);
const opts = options orelse ObserverInit{};
@@ -266,7 +266,7 @@ fn checkIntersection(self: *IntersectionObserver, target: *Element, page: *Page)
(was_intersecting_opt != null and was_intersecting_opt.? != is_now_intersecting);
if (should_report) {
const arena = try page.getArena(.{ .debug = "IntersectionObserverEntry" });
const arena = try page.getArena(.tiny, "IntersectionObserverEntry");
errdefer page.releaseArena(arena);
const entry = try arena.create(IntersectionObserverEntry);

View File

@@ -76,7 +76,7 @@ pub const ObserveOptions = struct {
};
pub fn init(callback: js.Function.Temp, page: *Page) !*MutationObserver {
const arena = try page.getArena(.{ .debug = "MutationObserver" });
const arena = try page.getArena(.medium, "MutationObserver");
errdefer page.releaseArena(arena);
const self = try arena.create(MutationObserver);
@@ -227,7 +227,7 @@ pub fn notifyAttributeChange(
}
}
const arena = try page.getArena(.{ .debug = "MutationRecord" });
const arena = try page.getArena(.tiny, "MutationRecord");
const record = try arena.create(MutationRecord);
record.* = .{
._arena = arena,
@@ -271,7 +271,7 @@ pub fn notifyCharacterDataChange(
continue;
}
const arena = try page.getArena(.{ .debug = "MutationRecord" });
const arena = try page.getArena(.tiny, "MutationRecord");
const record = try arena.create(MutationRecord);
record.* = .{
._arena = arena,
@@ -318,7 +318,7 @@ pub fn notifyChildListChange(
continue;
}
const arena = try page.getArena(.{ .debug = "MutationRecord" });
const arena = try page.getArena(.tiny, "MutationRecord");
const record = try arena.create(MutationRecord);
record.* = .{
._arena = arena,

View File

@@ -38,7 +38,7 @@ const QueryDescriptor = struct {
};
// We always report 'prompt' (the default safe value — neither granted nor denied).
pub fn query(_: *const Permissions, qd: QueryDescriptor, page: *Page) !js.Promise {
const arena = try page.getArena(.{ .debug = "PermissionStatus" });
const arena = try page.getArena(.tiny, "PermissionStatus");
errdefer page.releaseArena(arena);
const status = try arena.create(PermissionStatus);

View File

@@ -33,7 +33,7 @@ const Range = @This();
_proto: *AbstractRange,
pub fn init(page: *Page) !*Range {
const arena = try page.getArena(.{ .debug = "Range" });
const arena = try page.getArena(.medium, "Range");
errdefer page.releaseArena(arena);
return page._factory.abstractRange(arena, Range{ ._proto = undefined }, page);
}
@@ -312,7 +312,7 @@ pub fn intersectsNode(self: *const Range, node: *Node) bool {
}
pub fn cloneRange(self: *const Range, page: *Page) !*Range {
const arena = try page.getArena(.{ .debug = "Range.clone" });
const arena = try page.getArena(.medium, "Range.clone");
errdefer page.releaseArena(arena);
const clone = try page._factory.abstractRange(arena, Range{ ._proto = undefined }, page);

View File

@@ -407,7 +407,7 @@ pub fn postMessage(self: *Window, message: js.Value.Temp, target_origin: ?[]cons
const target_page = self._page;
const source_window = target_page.js.getIncumbent().window;
const arena = try target_page.getArena(.{ .debug = "Window.postMessage" });
const arena = try target_page.getArena(.medium, "Window.postMessage");
errdefer target_page.releaseArena(arena);
// Origin should be the source window's origin (where the message came from)
@@ -645,7 +645,7 @@ fn scheduleCallback(self: *Window, cb: js.Function.Temp, delay_ms: u32, opts: Sc
return error.TooManyTimeout;
}
const arena = try page.getArena(.{ .debug = "Window.schedule" });
const arena = try page.getArena(.tiny, "Window.schedule");
errdefer page.releaseArena(arena);
const timer_id = self._timer_id +% 1;

View File

@@ -52,7 +52,7 @@ _playState: PlayState = .idle,
//
// TODO add support for effect and timeline
pub fn init(page: *Page) !*Animation {
const arena = try page.getArena(.{ .debug = "Animation" });
const arena = try page.getArena(.tiny, "Animation");
errdefer page.releaseArena(arena);
const self = try arena.create(Animation);

View File

@@ -39,7 +39,7 @@ pub const ValueIterator = GenericIterator(Iterator, "1");
pub const EntryIterator = GenericIterator(Iterator, null);
pub fn init(node: *Node, page: *Page) !*ChildNodes {
const arena = try page.getArena(.{ .debug = "ChildNodes" });
const arena = try page.getArena(.small, "ChildNodes");
errdefer page.releaseArena(arena);
const self = try arena.create(ChildNodes);

View File

@@ -33,7 +33,7 @@ _family: []const u8,
pub fn init(family: []const u8, source: []const u8, page: *Page) !*FontFace {
_ = source;
const arena = try page.getArena(.{ .debug = "FontFace" });
const arena = try page.getArena(.tiny, "FontFace");
errdefer page.releaseArena(arena);
const self = try arena.create(FontFace);

View File

@@ -34,7 +34,7 @@ _proto: *EventTarget,
_arena: Allocator,
pub fn init(page: *Page) !*FontFaceSet {
const arena = try page.getArena(.{ .debug = "FontFaceSet" });
const arena = try page.getArena(.tiny, "FontFaceSet");
errdefer page.releaseArena(arena);
return page._factory.eventTargetWithAllocator(arena, FontFaceSet{

View File

@@ -292,7 +292,7 @@ pub fn insertAdjacentHTML(
});
const doc_node = doc.asNode();
const arena = try page.getArena(.{ .debug = "HTML.insertAdjacentHTML" });
const arena = try page.getArena(.medium, "HTML.insertAdjacentHTML");
defer page.releaseArena(arena);
const Parser = @import("../../parser/Parser.zig");

View File

@@ -48,7 +48,7 @@ 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" });
const arena = try page.getArena(.large, "TextDecoder");
errdefer page.releaseArena(arena);
const opts = opts_ orelse InitOpts{};

View File

@@ -39,14 +39,14 @@ const CloseEventOptions = struct {
const Options = Event.inheritOptions(CloseEvent, CloseEventOptions);
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*CloseEvent {
const arena = try page.getArena(.{ .debug = "CloseEvent" });
const arena = try page.getArena(.tiny, "CloseEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, _opts, false, page);
}
pub fn initTrusted(typ: String, _opts: ?Options, page: *Page) !*CloseEvent {
const arena = try page.getArena(.{ .debug = "CloseEvent.trusted" });
const arena = try page.getArena(.tiny, "CloseEvent.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, _opts, true, page);
}

View File

@@ -35,7 +35,7 @@ const CompositionEventOptions = struct {
const Options = Event.inheritOptions(CompositionEvent, CompositionEventOptions);
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*CompositionEvent {
const arena = try page.getArena(.{ .debug = "CompositionEvent" });
const arena = try page.getArena(.tiny, "CompositionEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});

View File

@@ -38,7 +38,7 @@ const CustomEventOptions = struct {
const Options = Event.inheritOptions(CustomEvent, CustomEventOptions);
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*CustomEvent {
const arena = try page.getArena(.{ .debug = "CustomEvent" });
const arena = try page.getArena(.tiny, "CustomEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});

View File

@@ -47,14 +47,14 @@ pub const ErrorEventOptions = struct {
const Options = Event.inheritOptions(ErrorEvent, ErrorEventOptions);
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*ErrorEvent {
const arena = try page.getArena(.{ .debug = "ErrorEvent" });
const arena = try page.getArena(.small, "ErrorEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, opts_, false, page);
}
pub fn initTrusted(typ: String, opts_: ?Options, page: *Page) !*ErrorEvent {
const arena = try page.getArena(.{ .debug = "ErrorEvent.trusted" });
const arena = try page.getArena(.small, "ErrorEvent.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, opts_, true, page);
}

View File

@@ -42,13 +42,13 @@ pub const Options = Event.inheritOptions(
);
pub fn initTrusted(typ: String, _opts: ?Options, page: *Page) !*FocusEvent {
const arena = try page.getArena(.{ .debug = "FocusEvent.trusted" });
const arena = try page.getArena(.tiny, "FocusEvent.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, _opts, true, page);
}
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*FocusEvent {
const arena = try page.getArena(.{ .debug = "FocusEvent" });
const arena = try page.getArena(.tiny, "FocusEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, _opts, false, page);

View File

@@ -38,14 +38,14 @@ const Options = Event.inheritOptions(FormDataEvent, struct {
});
pub fn init(typ: []const u8, maybe_options: Options, page: *Page) !*FormDataEvent {
const arena = try page.getArena(.{ .debug = "FormDataEvent" });
const arena = try page.getArena(.tiny, "FormDataEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, maybe_options, false, page);
}
pub fn initTrusted(typ: String, _opts: ?Options, page: *Page) !*FormDataEvent {
const arena = try page.getArena(.{ .debug = "FormDataEvent.trusted" });
const arena = try page.getArena(.tiny, "FormDataEvent.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, _opts, true, page);
}

View File

@@ -46,13 +46,13 @@ const Options = Event.inheritOptions(
);
pub fn initTrusted(typ: String, _opts: ?Options, page: *Page) !*InputEvent {
const arena = try page.getArena(.{ .debug = "InputEvent.trusted" });
const arena = try page.getArena(.tiny, "InputEvent.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, _opts, true, page);
}
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*InputEvent {
const arena = try page.getArena(.{ .debug = "InputEvent" });
const arena = try page.getArena(.tiny, "InputEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, _opts, false, page);

View File

@@ -186,13 +186,13 @@ const Options = Event.inheritOptions(
);
pub fn initTrusted(typ: String, _opts: ?Options, page: *Page) !*KeyboardEvent {
const arena = try page.getArena(.{ .debug = "KeyboardEvent.trusted" });
const arena = try page.getArena(.tiny, "KeyboardEvent.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, _opts, true, page);
}
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*KeyboardEvent {
const arena = try page.getArena(.{ .debug = "KeyboardEvent" });
const arena = try page.getArena(.tiny, "KeyboardEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, _opts, false, page);

View File

@@ -50,14 +50,14 @@ pub const Data = union(enum) {
const Options = Event.inheritOptions(MessageEvent, MessageEventOptions);
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*MessageEvent {
const arena = try page.getArena(.{ .debug = "MessageEvent" });
const arena = try page.getArena(.small, "MessageEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, opts_, false, page);
}
pub fn initTrusted(typ: String, opts_: ?Options, page: *Page) !*MessageEvent {
const arena = try page.getArena(.{ .debug = "MessageEvent.trusted" });
const arena = try page.getArena(.small, "MessageEvent.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, opts_, true, page);
}

View File

@@ -82,14 +82,14 @@ pub const Options = Event.inheritOptions(
);
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*MouseEvent {
const arena = try page.getArena(.{ .debug = "MouseEvent" });
const arena = try page.getArena(.tiny, "MouseEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, _opts, false, page);
}
pub fn initTrusted(typ: String, _opts: ?Options, page: *Page) !*MouseEvent {
const arena = try page.getArena(.{ .debug = "MouseEvent.trusted" });
const arena = try page.getArena(.tiny, "MouseEvent.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, _opts, true, page);
}

View File

@@ -45,14 +45,14 @@ const Options = Event.inheritOptions(
);
pub fn init(typ: []const u8, opts: Options, page: *Page) !*NavigationCurrentEntryChangeEvent {
const arena = try page.getArena(.{ .debug = "NavigationCurrentEntryChangeEvent" });
const arena = try page.getArena(.tiny, "NavigationCurrentEntryChangeEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, opts, false, page);
}
pub fn initTrusted(typ: String, opts: Options, page: *Page) !*NavigationCurrentEntryChangeEvent {
const arena = try page.getArena(.{ .debug = "NavigationCurrentEntryChangeEvent.trusted" });
const arena = try page.getArena(.tiny, "NavigationCurrentEntryChangeEvent.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, opts, true, page);
}

View File

@@ -38,14 +38,14 @@ const PageTransitionEventOptions = struct {
const Options = Event.inheritOptions(PageTransitionEvent, PageTransitionEventOptions);
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*PageTransitionEvent {
const arena = try page.getArena(.{ .debug = "PageTransitionEvent" });
const arena = try page.getArena(.tiny, "PageTransitionEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, _opts, false, page);
}
pub fn initTrusted(typ: String, _opts: ?Options, page: *Page) !*PageTransitionEvent {
const arena = try page.getArena(.{ .debug = "PageTransitionEvent.trusted" });
const arena = try page.getArena(.tiny, "PageTransitionEvent.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, _opts, true, page);
}

View File

@@ -84,7 +84,7 @@ const Options = Event.inheritOptions(
);
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*PointerEvent {
const arena = try page.getArena(.{ .debug = "UIEvent" });
const arena = try page.getArena(.tiny, "PointerEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});

View File

@@ -39,14 +39,14 @@ const PopStateEventOptions = struct {
const Options = Event.inheritOptions(PopStateEvent, PopStateEventOptions);
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*PopStateEvent {
const arena = try page.getArena(.{ .debug = "PopStateEvent" });
const arena = try page.getArena(.tiny, "PopStateEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, _opts, false, page);
}
pub fn initTrusted(typ: String, _opts: ?Options, page: *Page) !*PopStateEvent {
const arena = try page.getArena(.{ .debug = "PopStateEvent.trusted" });
const arena = try page.getArena(.tiny, "PopStateEvent.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, _opts, true, page);
}

View File

@@ -39,14 +39,14 @@ const ProgressEventOptions = struct {
const Options = Event.inheritOptions(ProgressEvent, ProgressEventOptions);
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*ProgressEvent {
const arena = try page.getArena(.{ .debug = "ProgressEvent" });
const arena = try page.getArena(.tiny, "ProgressEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, _opts, false, page);
}
pub fn initTrusted(typ: String, _opts: ?Options, page: *Page) !*ProgressEvent {
const arena = try page.getArena(.{ .debug = "ProgressEvent.trusted" });
const arena = try page.getArena(.tiny, "ProgressEvent.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, _opts, true, page);
}

View File

@@ -37,7 +37,7 @@ const PromiseRejectionEventOptions = struct {
const Options = Event.inheritOptions(PromiseRejectionEvent, PromiseRejectionEventOptions);
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*PromiseRejectionEvent {
const arena = try page.getArena(.{ .debug = "PromiseRejectionEvent" });
const arena = try page.getArena(.tiny, "PromiseRejectionEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});

View File

@@ -39,14 +39,14 @@ const SubmitEventOptions = struct {
const Options = Event.inheritOptions(SubmitEvent, SubmitEventOptions);
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*SubmitEvent {
const arena = try page.getArena(.{ .debug = "SubmitEvent" });
const arena = try page.getArena(.tiny, "SubmitEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, opts_, false, page);
}
pub fn initTrusted(typ: String, _opts: ?Options, page: *Page) !*SubmitEvent {
const arena = try page.getArena(.{ .debug = "SubmitEvent.trusted" });
const arena = try page.getArena(.tiny, "SubmitEvent.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, _opts, true, page);
}

View File

@@ -40,7 +40,7 @@ pub const Options = Event.inheritOptions(
);
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*TextEvent {
const arena = try page.getArena(.{ .debug = "TextEvent" });
const arena = try page.getArena(.tiny, "TextEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});

View File

@@ -51,7 +51,7 @@ pub const Options = Event.inheritOptions(
);
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*UIEvent {
const arena = try page.getArena(.{ .debug = "UIEvent" });
const arena = try page.getArena(.tiny, "UIEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});

View File

@@ -50,7 +50,7 @@ pub const Options = Event.inheritOptions(
);
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*WheelEvent {
const arena = try page.getArena(.{ .debug = "WheelEvent" });
const arena = try page.getArena(.medium, "WheelEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});

View File

@@ -57,7 +57,7 @@ const InitOpts = struct {
};
pub fn init(body_: ?[]const u8, opts_: ?InitOpts, page: *Page) !*Response {
const arena = try page.getArena(.{ .debug = "Response" });
const arena = try page.getArena(.large, "Response");
errdefer page.releaseArena(arena);
const opts = opts_ orelse InitOpts{};
@@ -174,7 +174,7 @@ pub fn bytes(self: *const Response, page: *Page) !js.Promise {
}
pub fn clone(self: *const Response, page: *Page) !*Response {
const arena = try page.getArena(.{ .debug = "Response.clone" });
const arena = try page.getArena((self._body orelse "").len + self._url.len + 256, "Response.clone");
errdefer page.releaseArena(arena);
const body = if (self._body) |b| try arena.dupe(u8, b) else null;

View File

@@ -105,7 +105,7 @@ pub fn init(url: []const u8, protocols_: ?[]const u8, page: *Page) !*WebSocket {
}
}
const arena = try page.getArena(.{ .debug = "WebSocket" });
const arena = try page.getArena(.medium, "WebSocket");
errdefer page.releaseArena(arena);
const resolved_url = try URL.resolve(arena, page.base(), url, .{ .always_dupe = true, .encode = true });
@@ -272,12 +272,10 @@ pub fn send(self: *WebSocket, data: SendData) !void {
return error.InvalidStateError;
}
// Get a dedicated arena for this message
const arena = try self._page._session.getArena(.{ .debug = "WebSocket message" });
errdefer self._page._session.releaseArena(arena);
switch (data) {
.blob => |blob| {
const arena = try self._page._session.getArena(blob._slice.len, "WebSocket.message");
errdefer self._page._session.releaseArena(arena);
try self.queueMessage(.{ .binary = .{
.arena = arena,
.data = try arena.dupe(u8, blob._slice),
@@ -285,15 +283,21 @@ pub fn send(self: *WebSocket, data: SendData) !void {
},
.js_val => |js_val| {
if (js_val.isString()) |str| {
const arena = try self._page._session.getArena(str.len(), "WebSocket.message");
errdefer self._page._session.releaseArena(arena);
try self.queueMessage(.{ .text = .{
.arena = arena,
.data = try str.toSliceWithAlloc(arena),
} });
} else {
const binary = try js_val.toZig(BinaryData);
const buffer = binary.asBuffer();
const arena = try self._page._session.getArena(buffer.len, "WebSocket.message");
errdefer self._page._session.releaseArena(arena);
try self.queueMessage(.{ .binary = .{
.arena = arena,
.data = try arena.dupe(u8, binary.asBuffer()),
.data = try arena.dupe(u8, buffer),
} });
}
},

View File

@@ -89,7 +89,7 @@ const ResponseType = enum {
};
pub fn init(page: *Page) !*XMLHttpRequest {
const arena = try page.getArena(.{ .debug = "XMLHttpRequest" });
const arena = try page.getArena(.large, "XMLHttpRequest");
errdefer page.releaseArena(arena);
const self = try page._factory.xhrEventTarget(arena, XMLHttpRequest{
._page = page,

View File

@@ -45,7 +45,7 @@ pub fn querySelectorAll(root: *Node, input: []const u8, page: *Page) !*List {
return error.SyntaxError;
}
const arena = try page.getArena(.{ .debug = "querySelectorAll" });
const arena = try page.getArena(.small, "querySelectorAll");
errdefer page.releaseArena(arena);
var nodes: std.AutoArrayHashMapUnmanaged(*Node, void) = .empty;

View File

@@ -487,10 +487,10 @@ pub const BrowserContext = struct {
pub fn createIsolatedWorld(self: *BrowserContext, world_name: []const u8, grant_universal_access: bool) !*IsolatedWorld {
const browser = &self.cdp.browser;
const arena = try browser.arena_pool.acquire(.{ .debug = "IsolatedWorld" });
const arena = try browser.arena_pool.acquire(.small, "IsolatedWorld");
errdefer browser.arena_pool.release(arena);
const call_arena = try browser.arena_pool.acquire(.{ .debug = "IsolatedWorld.call_arena" });
const call_arena = try browser.arena_pool.acquire(.tiny, "IsolatedWorld.call_arena");
errdefer browser.arena_pool.release(call_arena);
const world = try arena.create(IsolatedWorld);