From 61cc7bb949de2ce8307afa5bf07b98b67c475fc3 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sat, 4 Jul 2026 22:14:17 +0800 Subject: [PATCH] refactor: Rework how v8::Globals are managed We typically persist a v8::Value by calling persist() or temp() on our v8 wrappers. Both persist() and temp() create a v8::Global, but persist() tracks it in the page's globals ArrayList while temp() tracks it in the page's temps HashMap. Globals are only freed when the page is torn down, hence a simple ArrayList is all we need. Temps can be freed at any point, hence HashMap is used. (Temps are also freed on teardown, but they can be freed before that too). This commit removes Temp (js.Value.Temp, js.Promise.Temp, js.Function.Temp) and all the mechinery around them (e.g. pseudo Global generic). There are now only Globals and any global can be freed at any time (while still being tracked in the page in order to be able to free any un-freed on page teardown). Many files were touched because .Temp -> .Global and .temp() -> .persist(). This commit is based off the indexeddb branch because indexeddb introduced "bare" globals (globals which aren't tracked by the Page). Building this new model to include "Bare" globals avoids a messy merge later. --- src/browser/EventManager.zig | 2 +- src/browser/EventManagerBase.zig | 3 +- src/browser/Page.zig | 26 +--- src/browser/js/Context.zig | 10 +- src/browser/js/Function.zig | 78 +++--------- src/browser/js/Local.zig | 20 ++- src/browser/js/Object.zig | 26 ++-- src/browser/js/Promise.zig | 60 ++------- src/browser/js/PromiseResolver.zig | 18 ++- src/browser/js/Value.zig | 118 +++++++++--------- src/browser/js/js.zig | 97 ++++++++++++-- src/browser/webapi/BroadcastChannel.zig | 6 +- .../webapi/DedicatedWorkerGlobalScope.zig | 12 +- src/browser/webapi/FileReader.zig | 36 +++--- src/browser/webapi/IntersectionObserver.zig | 4 +- src/browser/webapi/MessagePort.zig | 4 +- src/browser/webapi/MutationObserver.zig | 4 +- src/browser/webapi/Timers.zig | 18 +-- src/browser/webapi/WebDriver.zig | 4 +- src/browser/webapi/Window.zig | 20 +-- src/browser/webapi/Worker.zig | 8 +- src/browser/webapi/WorkerGlobalScope.zig | 10 +- src/browser/webapi/animation/Animation.zig | 6 +- src/browser/webapi/event/CustomEvent.zig | 8 +- src/browser/webapi/event/ErrorEvent.zig | 6 +- src/browser/webapi/event/MessageEvent.zig | 2 +- .../webapi/event/PromiseRejectionEvent.zig | 12 +- src/browser/webapi/net/WebSocket.zig | 24 ++-- src/browser/webapi/net/XMLHttpRequest.zig | 6 +- .../webapi/net/XMLHttpRequestEventTarget.zig | 42 +++---- src/browser/webapi/storage/idb/IDBCursor.zig | 6 +- src/browser/webapi/storage/idb/IDBIndex.zig | 2 +- .../webapi/storage/idb/IDBObjectStore.zig | 8 +- src/browser/webapi/storage/idb/IDBRequest.zig | 10 +- .../webapi/storage/idb/IDBTransaction.zig | 12 +- src/browser/webapi/storage/idb/idb.zig | 2 +- 36 files changed, 350 insertions(+), 380 deletions(-) diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig index 863923d66..dada38b5c 100644 --- a/src/browser/EventManager.zig +++ b/src/browser/EventManager.zig @@ -124,7 +124,7 @@ pub const DispatchDirectOptions = EventManagerBase.DispatchDirectOptions; // Direct dispatch for non-DOM targets (Window, XHR, AbortSignal) or DOM nodes with // property handlers. No propagation - just calls the handler and registered listeners. -// Handler can be: null, ?js.Function.Global, ?js.Function.Temp, or js.Function +// Handler can be: null, ?js.Function.Global or js.Function pub fn dispatchDirect(self: *EventManager, target: *EventTarget, event: *Event, handler: anytype, comptime opts: DispatchDirectOptions) !void { const frame = self.frame; diff --git a/src/browser/EventManagerBase.zig b/src/browser/EventManagerBase.zig index c18253426..335a98dc0 100644 --- a/src/browser/EventManagerBase.zig +++ b/src/browser/EventManagerBase.zig @@ -223,7 +223,7 @@ pub const DispatchDirectOptions = struct { /// Direct dispatch for non-DOM targets. No propagation - just calls the property /// handler and registered listeners. Caller is responsible for event ref counting. -/// Handler can be: null, ?js.Function.Global, ?js.Function.Temp, or js.Function +/// Handler can be: null, ?js.Function.Global or js.Function pub fn dispatchDirect( self: *EventManagerBase, arena: Allocator, @@ -359,7 +359,6 @@ fn getFunction(handler: anytype, local: *const js.Local) ?js.Function { } return switch (T) { js.Function => handler, - js.Function.Temp => local.toLocal(handler), js.Function.Global => local.toLocal(handler), else => @compileError("handler must be null or \\??js.Function(\\.(Temp|Global))?"), }; diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 3336e4111..793090f83 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -20,7 +20,6 @@ const std = @import("std"); const builtin = @import("builtin"); const js = @import("js/js.zig"); -const v8 = js.v8; const Frame = @import("Frame.zig"); const Session = @import("Session.zig"); @@ -81,11 +80,10 @@ identity: js.Identity = .{}, // weak-callback safety. finalizer_callbacks: std.AutoHashMapUnmanaged(usize, *js.FinalizerCallback) = .empty, -// Tracked global v8 objects that need to be released when the Page tears down. -globals: std.ArrayList(v8.Global) = .empty, - -// Temporary v8 globals that can be released early. Key is global.data_ptr. -temps: std.AutoHashMapUnmanaged(usize, v8.Global) = .empty, +// Persisted v8 handles owned by this Page. Handles that outlive the Page are +// reset on teardown; handles that can be released early are dropped +// individually. See js.GlobalTracker. +globals: js.GlobalTracker, // Double buffered so that, as we process one list of queued navigations, new // entries are added to the separate buffer. Prevents endless navigation loops @@ -144,6 +142,7 @@ pub fn init(self: *Page, session: *Session, frame_id: u32) !void { .frame = undefined, .frame_arena = frame_arena, .factory = Factory.init(frame_arena), + .globals = .init(session.browser.app.allocator), }; self.queued_navigation = &self.queued_navigation_1; @@ -180,20 +179,7 @@ pub fn deinit(self: *Page) void { self.finalizer_callbacks = .empty; } - { - for (self.globals.items) |*global| { - v8.v8__Global__Reset(global); - } - self.globals = .empty; - } - - { - var it = self.temps.valueIterator(); - while (it.next()) |global| { - v8.v8__Global__Reset(global); - } - self.temps = .empty; - } + self.globals.deinit(); if (comptime IS_DEBUG) { std.debug.assert(self.origins.count() == 0); diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 6fcbc16f7..04747c606 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -259,14 +259,6 @@ pub fn setOrigin(self: *Context, key: ?[]const u8) !void { } } -pub fn trackGlobal(self: *Context, global: v8.Global) !void { - return self.page.globals.append(self.page.frame_arena, global); -} - -pub fn trackTemp(self: *Context, global: v8.Global) !void { - return self.page.temps.put(self.page.frame_arena, global.data_ptr, global); -} - pub const IdentityResult = struct { value_ptr: *v8.Global, found_existing: bool, @@ -1102,7 +1094,7 @@ fn enqueueMicrotask(self: *Context, callback: anytype) void { // this should be safe (I think). In whatever HandleScope a microtask is enqueued, // PerformCheckpoint should be run. So the v8::Local should remain // valid. If we have problems with this, a simple solution is to provide a Zig -// wrapper for these callbacks which references a js.Function.Temp, on callback +// wrapper for these callbacks which references a js.Function, on callback // it executes the function and then releases the global. pub fn queueMicrotaskFunc(self: *Context, cb: js.Function) void { // Use context-specific microtask queue instead of isolate queue diff --git a/src/browser/js/Function.zig b/src/browser/js/Function.zig index 7ab174835..318e45f0f 100644 --- a/src/browser/js/Function.zig +++ b/src/browser/js/Function.zig @@ -227,29 +227,7 @@ pub fn getPropertyValue(self: *const Function, name: []const u8) !?js.Value { } pub fn persist(self: *const Function) !Global { - return self._persist(true); -} - -pub fn temp(self: *const Function) !Temp { - return self._persist(false); -} - -fn _persist(self: *const Function, comptime is_global: bool) !(if (is_global) Global else Temp) { - var ctx = self.local.ctx; - - var global: v8.Global = undefined; - v8.v8__Global__New(ctx.isolate.handle, self.handle, &global); - if (comptime is_global) { - try ctx.trackGlobal(global); - return .{ .handle = global, .temps = {} }; - } - try ctx.trackTemp(global); - return .{ .handle = global, .temps = &ctx.page.temps }; -} - -pub fn tempWithThis(self: *const Function, value: anytype) !Temp { - const with_this = try self.withThis(value); - return with_this.temp(); + return .{ .slot = try js.newTrackedSlot(self.local.ctx, self.handle) }; } pub fn persistWithThis(self: *const Function, value: anytype) !Global { @@ -257,41 +235,23 @@ pub fn persistWithThis(self: *const Function, value: anytype) !Global { return with_this.persist(); } -pub const Temp = G(.temp); -pub const Global = G(.global); +// A cheap, copyable handle to a persisted function. See js.GlobalSlot. +pub const Global = struct { + slot: *js.GlobalSlot, -const GlobalType = enum(u8) { - temp, - global, + pub fn deinit(self: Global) void { + self.slot.release(); + } + pub const release = deinit; + + pub fn local(self: Global, l: *const js.Local) Function { + return .{ + .local = l, + .handle = @ptrCast(v8.v8__Global__Get(&self.slot.handle, l.isolate.handle)), + }; + } + + pub fn isEqual(self: Global, other: Function) bool { + return v8.v8__Global__IsEqual(&self.slot.handle, other.handle); + } }; - -fn G(comptime global_type: GlobalType) type { - return struct { - handle: v8.Global, - temps: if (global_type == .temp) *std.AutoHashMapUnmanaged(usize, v8.Global) else void, - - const Self = @This(); - - pub fn deinit(self: *Self) void { - v8.v8__Global__Reset(&self.handle); - } - - pub fn local(self: *const Self, l: *const js.Local) Function { - return .{ - .local = l, - .handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)), - }; - } - - pub fn isEqual(self: *const Self, other: Function) bool { - return v8.v8__Global__IsEqual(&self.handle, other.handle); - } - - pub fn release(self: *const Self) void { - if (self.temps.fetchRemove(self.handle.data_ptr)) |kv| { - var g = kv.value; - v8.v8__Global__Reset(&g); - } - } - }; -} diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig index ad4ab25db..b24340c9b 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -275,7 +275,10 @@ pub fn mapZigInstanceToJs(self: *const Local, js_obj_handle: ?*const v8.Object, const gop = try ctx.addIdentity(resolved_ptr_id); if (gop.found_existing) { // we've seen this instance before, return the same object - return (js.Object.Global{ .handle = gop.value_ptr.* }).local(self); + return .{ + .local = self, + .handle = @ptrCast(v8.v8__Global__Get(gop.value_ptr, self.isolate.handle)), + }; } const isolate = self.isolate; @@ -468,12 +471,9 @@ pub fn zigValueToJs(self: *const Local, value: anytype, comptime opts: CallOpts) inline js.Function.Global, - js.Function.Temp, js.Value.Global, - js.Value.Temp, js.Object.Global, js.Promise.Global, - js.Promise.Temp, js.PromiseResolver.Global, js.Module.Global => return .{ .local = self, .handle = @ptrCast(value.local(self).handle) }, @@ -743,14 +743,13 @@ pub fn jsValueToZig(self: *const Local, comptime T: type, js_val: js.Value) !T { // probeJsValueToZig. Avoids having to duplicate this logic when probing. fn jsValueToStruct(self: *const Local, comptime T: type, js_val: js.Value) !?T { return switch (T) { - js.Function, js.Function.Global, js.Function.Temp => { + js.Function, js.Function.Global => { if (!js_val.isFunction()) { return null; } const js_func = js.Function{ .local = self, .handle = @ptrCast(js_val.handle) }; return switch (T) { js.Function => js_func, - js.Function.Temp => try js_func.temp(), js.Function.Global => try js_func.persist(), else => unreachable, }; @@ -767,7 +766,6 @@ fn jsValueToStruct(self: *const Local, comptime T: type, js_val: js.Value) !?T { }, js.Value => js_val, js.Value.Global => return try js_val.persist(), - js.Value.Temp => return try js_val.temp(), js.Object => { if (!js_val.isObject()) { return null; @@ -788,7 +786,7 @@ fn jsValueToStruct(self: *const Local, comptime T: type, js_val: js.Value) !?T { return try obj.persist(); }, - js.Promise.Global, js.Promise.Temp => { + js.Promise.Global => { if (!js_val.isPromise()) { return null; } @@ -796,11 +794,7 @@ fn jsValueToStruct(self: *const Local, comptime T: type, js_val: js.Value) !?T { .local = self, .handle = @ptrCast(js_val.handle), }; - return switch (T) { - js.Promise.Temp => try js_promise.temp(), - js.Promise.Global => try js_promise.persist(), - else => unreachable, - }; + return try js_promise.persist(); }, js.String => return js_val.isString(), js.String.OneByte => { diff --git a/src/browser/js/Object.zig b/src/browser/js/Object.zig index 138997820..8869a9060 100644 --- a/src/browser/js/Object.zig +++ b/src/browser/js/Object.zig @@ -92,14 +92,7 @@ pub fn format(self: Object, writer: *std.Io.Writer) !void { } pub fn persist(self: Object) !Global { - var ctx = self.local.ctx; - - var global: v8.Global = undefined; - v8.v8__Global__New(ctx.isolate.handle, self.handle, &global); - - try ctx.trackGlobal(global); - - return .{ .handle = global }; + return .{ .slot = try js.newTrackedSlot(self.local.ctx, self.handle) }; } pub fn getFunction(self: Object, name: []const u8) !?js.Function { @@ -171,21 +164,24 @@ pub fn toZig(self: Object, comptime T: type) !T { } pub const Global = struct { - handle: v8.Global, + slot: *js.GlobalSlot, - pub fn deinit(self: *Global) void { - v8.v8__Global__Reset(&self.handle); + pub fn deinit(self: Global) void { + self.slot.release(); } - pub fn local(self: *const Global, l: *const js.Local) Object { + // TODO: deprecated. @GlobalSlot + pub const release = deinit; + + pub fn local(self: Global, l: *const js.Local) Object { return .{ .local = l, - .handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)), + .handle = @ptrCast(v8.v8__Global__Get(&self.slot.handle, l.isolate.handle)), }; } - pub fn isEqual(self: *const Global, other: Object) bool { - return v8.v8__Global__IsEqual(&self.handle, other.handle); + pub fn isEqual(self: Global, other: Object) bool { + return v8.v8__Global__IsEqual(&self.slot.handle, other.handle); } }; diff --git a/src/browser/js/Promise.zig b/src/browser/js/Promise.zig index 20edc6516..78a509f9b 100644 --- a/src/browser/js/Promise.zig +++ b/src/browser/js/Promise.zig @@ -74,57 +74,21 @@ pub fn markAsHandled(self: Promise) void { } pub fn persist(self: Promise) !Global { - return self._persist(true); + return .{ .slot = try js.newTrackedSlot(self.local.ctx, self.handle) }; } -pub fn temp(self: Promise) !Temp { - return self._persist(false); -} +pub const Global = struct { + slot: *js.GlobalSlot, -fn _persist(self: *const Promise, comptime is_global: bool) !(if (is_global) Global else Temp) { - var ctx = self.local.ctx; - - var global: v8.Global = undefined; - v8.v8__Global__New(ctx.isolate.handle, self.handle, &global); - if (comptime is_global) { - try ctx.trackGlobal(global); - return .{ .handle = global, .temps = {} }; + pub fn deinit(self: Global) void { + self.slot.release(); } - try ctx.trackTemp(global); - return .{ .handle = global, .temps = &ctx.page.temps }; -} + pub const release = deinit; -pub const Temp = G(.temp); -pub const Global = G(.global); - -const GlobalType = enum(u8) { - temp, - global, + pub fn local(self: Global, l: *const js.Local) Promise { + return .{ + .local = l, + .handle = @ptrCast(v8.v8__Global__Get(&self.slot.handle, l.isolate.handle)), + }; + } }; - -fn G(comptime global_type: GlobalType) type { - return struct { - handle: v8.Global, - temps: if (global_type == .temp) *std.AutoHashMapUnmanaged(usize, v8.Global) else void, - - const Self = @This(); - - pub fn deinit(self: *Self) void { - v8.v8__Global__Reset(&self.handle); - } - - pub fn local(self: *const Self, l: *const js.Local) Promise { - return .{ - .local = l, - .handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)), - }; - } - - pub fn release(self: *const Self) void { - if (self.temps.fetchRemove(self.handle.data_ptr)) |kv| { - var g = kv.value; - v8.v8__Global__Reset(&g); - } - } - }; -} diff --git a/src/browser/js/PromiseResolver.zig b/src/browser/js/PromiseResolver.zig index e99a3d0b7..287ddd221 100644 --- a/src/browser/js/PromiseResolver.zig +++ b/src/browser/js/PromiseResolver.zig @@ -118,24 +118,22 @@ fn _reject(self: PromiseResolver, value: anytype) !void { } pub fn persist(self: PromiseResolver) !Global { - var ctx = self.local.ctx; - var global: v8.Global = undefined; - v8.v8__Global__New(ctx.isolate.handle, self.handle, &global); - try ctx.trackGlobal(global); - return .{ .handle = global }; + return .{ .slot = try js.newTrackedSlot(self.local.ctx, self.handle) }; } pub const Global = struct { - handle: v8.Global, + slot: *js.GlobalSlot, - pub fn deinit(self: *Global) void { - v8.v8__Global__Reset(&self.handle); + pub fn deinit(self: Global) void { + self.slot.release(); } - pub fn local(self: *const Global, l: *const js.Local) PromiseResolver { + pub const release = deinit; + + pub fn local(self: Global, l: *const js.Local) PromiseResolver { return .{ .local = l, - .handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)), + .handle = @ptrCast(v8.v8__Global__Get(&self.slot.handle, l.isolate.handle)), }; } }; diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig index 4506478ac..0171d8eef 100644 --- a/src/browser/js/Value.zig +++ b/src/browser/js/Value.zig @@ -620,34 +620,17 @@ const CloneDelegate = struct { }; pub fn persist(self: Value) !Global { - return self._persist(true); + return .{ .slot = try js.newTrackedSlot(self.local.ctx, self.handle) }; } -pub fn temp(self: Value) !Temp { - return self._persist(false); -} - -// Like persist(), but not tracked on the context: the caller owns the handle -// and must deinit (Reset) it. A reset is only idempotent through the same -// instance — copies alias one v8 slot, so keep a single canonical instance and -// reset through it. -pub fn bare(self: Value) BareGlobal { +// like persist, but not tracked by the page. Caller takes responsibility for +// resetting and freeing the allocation. +pub fn persistBare(self: Value, arena: std.mem.Allocator) !*js.GlobalSlot { + const slot = try arena.create(js.GlobalSlot); var global: v8.Global = undefined; v8.v8__Global__New(self.local.ctx.isolate.handle, self.handle, &global); - return .{ .handle = global, .temps = {} }; -} - -fn _persist(self: *const Value, comptime is_global: bool) !(if (is_global) Global else Temp) { - var ctx = self.local.ctx; - - var global: v8.Global = undefined; - v8.v8__Global__New(ctx.isolate.handle, self.handle, &global); - if (comptime is_global) { - try ctx.trackGlobal(global); - return .{ .handle = global, .temps = {} }; - } - try ctx.trackTemp(global); - return .{ .handle = global, .temps = &ctx.page.temps }; + slot.* = .{ .handle = global, .tracker = null, .gindex = undefined }; + return slot; } pub fn toZig(self: Value, comptime T: type) !T { @@ -694,48 +677,71 @@ pub fn format(self: Value, writer: *std.Io.Writer) !void { return js_str.format(writer); } -pub const Temp = G(.temp); -pub const Global = G(.global); -pub const BareGlobal = G(.bare); +// Copyable handle to our v8::Global wrapper so that releasing a copy resets +// the underlying v8::Global +pub const Global = struct { + slot: *js.GlobalSlot, -const GlobalType = enum(u8) { - temp, - global, - bare, + pub fn deinit(self: Global) void { + self.slot.release(); + } + pub const release = deinit; + + pub fn local(self: Global, l: *const js.Local) Value { + return .{ + .local = l, + .handle = @ptrCast(v8.v8__Global__Get(&self.slot.handle, l.isolate.handle)), + }; + } + + pub fn isEqual(self: Global, other: Value) bool { + return v8.v8__Global__IsEqual(&self.slot.handle, other.handle); + } }; -fn G(comptime global_type: GlobalType) type { - return struct { - handle: v8.Global, - temps: if (global_type == .temp) *std.AutoHashMapUnmanaged(usize, v8.Global) else void, +const testing = @import("../../testing.zig"); +test "Value: persisted handle early-release swap-removes and fixes up indices" { + const frame = try testing.createFrame(); + defer testing.test_session.closeAllPages(); - const Self = @This(); + var ls: js.Local.Scope = undefined; + frame.js.localScope(&ls); + defer ls.deinit(); - pub fn deinit(self: *Self) void { - v8.v8__Global__Reset(&self.handle); - } + const tracker = &frame.js.page.globals; + const base = tracker.list.items.len; - pub fn local(self: *const Self, l: *const js.Local) Value { - return .{ - .local = l, - .handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)), - }; - } + var a = try (try ls.local.exec("({a:1})", null)).persist(); + var b = try (try ls.local.exec("({b:2})", null)).persist(); + var c = try (try ls.local.exec("({c:3})", null)).persist(); - pub fn isEqual(self: *const Self, other: Value) bool { - return v8.v8__Global__IsEqual(&self.handle, other.handle); - } + try testing.expectEqual(base + 3, tracker.list.items.len); + try testing.expectEqual(base + 0, a.slot.gindex); + try testing.expectEqual(base + 1, b.slot.gindex); + try testing.expectEqual(base + 2, c.slot.gindex); - pub fn release(self: *const Self) void { - if (self.temps.fetchRemove(self.handle.data_ptr)) |kv| { - var g = kv.value; - v8.v8__Global__Reset(&g); - } - } - }; + // Release the middle one: the last live slot (c) must move into b's spot and + // have its stored index rewritten to match, or a later release corrupts. + b.deinit(); + try testing.expectEqual(base + 2, tracker.list.items.len); + try testing.expectEqual(base + 1, c.slot.gindex); + try testing.expectEqual(c.slot, tracker.list.items[base + 1]); + try testing.expectEqual(a.slot, tracker.list.items[base + 0]); + + // a and c are still usable (right handle, not b's). + try testing.expect(a.local(&ls.local).isObject()); + try testing.expect(c.local(&ls.local).isObject()); + + // Release the remaining two via the moved indices — must not corrupt. + a.deinit(); + try testing.expectEqual(base + 1, tracker.list.items.len); + try testing.expectEqual(base + 0, c.slot.gindex); + try testing.expectEqual(c.slot, tracker.list.items[base + 0]); + + c.deinit(); + try testing.expectEqual(base, tracker.list.items.len); } -const testing = @import("../../testing.zig"); test "Value: jsonStringify maps unserializable JS values to null" { const frame = try testing.createFrame(); defer testing.test_session.closeAllPages(); diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index 952c61f6d..675f6955b 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -67,6 +67,84 @@ pub fn Bridge(comptime T: type) type { return bridge.Builder(T); } +// Our wrapper around a v8::Global designed to be tracked (in a GlobalTracker). +pub const GlobalSlot = struct { + handle: v8.Global, + tracker: ?*GlobalTracker, // null for Bare globals (see IndexedDB) + gindex: u32, // position in GlobalTracker, used to efficiently remove + reuse + + pub fn reset(self: *GlobalSlot) void { + v8.v8__Global__Reset(&self.handle); + } + + // Eager free: reset the handle and, if page-tracked, drop the slot from the + // tracker and return it to the pool. Idempotent for bare slots. + pub fn release(self: *GlobalSlot) void { + self.reset(); + if (self.tracker) |t| { + t.untrack(self); + } + } + + pub fn local(self: *const GlobalSlot, l: *const Local) Value { + return .{ + .local = l, + .handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)), + }; + } +}; + +// Per-page owner of persisted v8 handles (v8::Global). Teardown resets all globals +pub const GlobalTracker = struct { + allocator: Allocator, + list: std.ArrayList(*GlobalSlot) = .empty, + pool: std.heap.MemoryPool(GlobalSlot), + + pub fn init(allocator: Allocator) GlobalTracker { + return .{ .allocator = allocator, .pool = std.heap.MemoryPool(GlobalSlot).init(allocator) }; + } + + pub fn deinit(self: *GlobalTracker) void { + for (self.list.items) |slot| { + slot.reset(); + } + self.list.deinit(self.allocator); + self.pool.deinit(); + } + + pub fn track(self: *GlobalTracker, handle: v8.Global) !*GlobalSlot { + const slot = try self.pool.create(); + errdefer self.pool.destroy(slot); + slot.* = .{ + .handle = handle, + .tracker = self, + .gindex = @intCast(self.list.items.len), + }; + try self.list.append(self.allocator, slot); + return slot; + } + + // swapRemove + updating the moved's index + fn untrack(self: *GlobalTracker, slot: *GlobalSlot) void { + const idx = slot.gindex; + const moved = self.list.pop().?; + if (moved != slot) { + self.list.items[idx] = moved; + // moved has..well...moved, we need to update its gindex + moved.gindex = idx; + } + self.pool.destroy(slot); + } +}; + +// Build a v8.Global from a live handle and track it on the context's page. +pub fn newTrackedSlot(ctx: *Context, handle: anytype) !*GlobalSlot { + var global: v8.Global = undefined; + v8.v8__Global__New(ctx.isolate.handle, handle, &global); + errdefer v8.v8__Global__Reset(&global); + return ctx.page.globals.track(global); +} + // If a function returns a []i32, should that map to a plain-old // JavaScript array, or a Int32Array? It's ambiguous. By default, we'll // map arrays/slices to the JavaScript arrays. If you want a TypedArray @@ -126,14 +204,16 @@ pub fn ArrayBufferRef(comptime kind: ArrayType) type { /// Persisted typed array. pub const Global = struct { - handle: v8.Global, + slot: *GlobalSlot, - pub fn deinit(self: *Global) void { - v8.v8__Global__Reset(&self.handle); + pub fn deinit(self: Global) void { + self.slot.release(); } - pub fn local(self: *const Global, l: *const Local) Self { - return .{ .local = l, .handle = v8.v8__Global__Get(&self.handle, l.isolate.handle).? }; + pub const release = deinit; + + pub fn local(self: Global, l: *const Local) Self { + return .{ .local = l, .handle = v8.v8__Global__Get(&self.slot.handle, l.isolate.handle).? }; } }; @@ -173,12 +253,7 @@ pub fn ArrayBufferRef(comptime kind: ArrayType) type { } pub fn persist(self: *const Self) !Global { - var ctx = self.local.ctx; - var global: v8.Global = undefined; - v8.v8__Global__New(ctx.isolate.handle, self.handle, &global); - try ctx.trackGlobal(global); - - return .{ .handle = global }; + return .{ .slot = try js.newTrackedSlot(self.local.ctx, self.handle) }; } // Direct view into the typed array's backing memory. diff --git a/src/browser/webapi/BroadcastChannel.zig b/src/browser/webapi/BroadcastChannel.zig index ccc23c95f..6f3eca460 100644 --- a/src/browser/webapi/BroadcastChannel.zig +++ b/src/browser/webapi/BroadcastChannel.zig @@ -88,7 +88,7 @@ pub fn postMessage(self: *BroadcastChannel, message: js.Value, exec: *Execution) const cloned = message.structuredCloneTo(&ls.local) catch { return error.DataClone; }; - break :blk try cloned.temp(); + break :blk try cloned.persist(); }; errdefer snapshot.release(); @@ -134,7 +134,7 @@ const PostMessageCallback = struct { sender: *BroadcastChannel, // A self-owned structured-clone snapshot of the posted message. Re-cloned // (never shared) into each receiver's MessageEvent, then released. - message: js.Value.Temp, + message: js.Value.Global, exec: *Execution, post_sequence: u64, @@ -221,7 +221,7 @@ const PostMessageCallback = struct { continue; }; - const cloned_temp = cloned.temp() catch |err| { + const cloned_temp = cloned.persist() catch |err| { log.err(.dom, "BroadcastChannel.postMessage", .{ .err = err }); continue; }; diff --git a/src/browser/webapi/DedicatedWorkerGlobalScope.zig b/src/browser/webapi/DedicatedWorkerGlobalScope.zig index 311306d94..d34c20645 100644 --- a/src/browser/webapi/DedicatedWorkerGlobalScope.zig +++ b/src/browser/webapi/DedicatedWorkerGlobalScope.zig @@ -48,7 +48,7 @@ _on_messageerror: ?js.Function.Global = null, // and delivered once the worker is ready (i.e. once onmessage can be set). // Drained by drainPendingMessages, called from Worker.loadInitialScript // after the initial script has been evaluated. -_pending_messages: std.ArrayList(?js.Value.Temp) = .empty, +_pending_messages: std.ArrayList(?js.Value.Global) = .empty, pub fn init(worker: *Worker, url: [:0]const u8) !*DedicatedWorkerGlobalScope { const self = try worker._arena.create(DedicatedWorkerGlobalScope); @@ -104,7 +104,7 @@ pub fn setOnMessageError(self: *DedicatedWorkerGlobalScope, setter: ?WorkerGloba self._on_messageerror = WorkerGlobalScope.getFunctionFromSetter(setter); } -pub fn requestAnimationFrame(self: *DedicatedWorkerGlobalScope, cb: js.Function.Temp, exec: *js.Execution) !u32 { +pub fn requestAnimationFrame(self: *DedicatedWorkerGlobalScope, cb: js.Function.Global, exec: *js.Execution) !u32 { return self._proto._timers.schedule(exec, cb, 5, .{ .repeat = false, .params = &.{}, @@ -123,7 +123,7 @@ pub fn receiveMessage(self: *DedicatedWorkerGlobalScope, data: js.Value) !void { return; } - const cloned_data: ?js.Value.Temp = blk: { + const cloned_data: ?js.Value.Global = blk: { // Enter our context to clone the message var ls: js.Local.Scope = undefined; self._proto.js.localScope(&ls); @@ -131,7 +131,7 @@ pub fn receiveMessage(self: *DedicatedWorkerGlobalScope, data: js.Value) !void { // clones from where it currently is (the Worker's Page context) to our Context const cloned = data.structuredCloneTo(&ls.local) catch break :blk null; - break :blk cloned.temp() catch break :blk null; + break :blk cloned.persist() catch break :blk null; }; if (!self._worker._script_loaded) { @@ -147,7 +147,7 @@ pub fn receiveMessage(self: *DedicatedWorkerGlobalScope, data: js.Value) !void { try self.scheduleMessage(cloned_data); } -fn scheduleMessage(self: *DedicatedWorkerGlobalScope, cloned_data: ?js.Value.Temp) !void { +fn scheduleMessage(self: *DedicatedWorkerGlobalScope, cloned_data: ?js.Value.Global) !void { const wgs = self._proto; const session = wgs._session; @@ -183,7 +183,7 @@ pub fn drainPendingMessages(self: *DedicatedWorkerGlobalScope) void { } const ReceiveMessageCallback = struct { - data: ?js.Value.Temp, + data: ?js.Value.Global, arena: Allocator, worker_scope: *DedicatedWorkerGlobalScope, diff --git a/src/browser/webapi/FileReader.zig b/src/browser/webapi/FileReader.zig index d783bd606..0c455a88a 100644 --- a/src/browser/webapi/FileReader.zig +++ b/src/browser/webapi/FileReader.zig @@ -42,12 +42,12 @@ _ready_state: ReadyState = .empty, _result: ?Result = null, _error: ?[]const u8 = null, -_on_abort: ?js.Function.Temp = null, -_on_error: ?js.Function.Temp = null, -_on_load: ?js.Function.Temp = null, -_on_load_end: ?js.Function.Temp = null, -_on_load_start: ?js.Function.Temp = null, -_on_progress: ?js.Function.Temp = null, +_on_abort: ?js.Function.Global = null, +_on_error: ?js.Function.Global = null, +_on_load: ?js.Function.Global = null, +_on_load_end: ?js.Function.Global = null, +_on_load_start: ?js.Function.Global = null, +_on_progress: ?js.Function.Global = null, _aborted: bool = false, @@ -97,51 +97,51 @@ fn asEventTarget(self: *FileReader) *EventTarget { return self._proto; } -pub fn getOnAbort(self: *const FileReader) ?js.Function.Temp { +pub fn getOnAbort(self: *const FileReader) ?js.Function.Global { return self._on_abort; } -pub fn setOnAbort(self: *FileReader, cb: ?js.Function.Temp) !void { +pub fn setOnAbort(self: *FileReader, cb: ?js.Function.Global) !void { self._on_abort = cb; } -pub fn getOnError(self: *const FileReader) ?js.Function.Temp { +pub fn getOnError(self: *const FileReader) ?js.Function.Global { return self._on_error; } -pub fn setOnError(self: *FileReader, cb: ?js.Function.Temp) !void { +pub fn setOnError(self: *FileReader, cb: ?js.Function.Global) !void { self._on_error = cb; } -pub fn getOnLoad(self: *const FileReader) ?js.Function.Temp { +pub fn getOnLoad(self: *const FileReader) ?js.Function.Global { return self._on_load; } -pub fn setOnLoad(self: *FileReader, cb: ?js.Function.Temp) !void { +pub fn setOnLoad(self: *FileReader, cb: ?js.Function.Global) !void { self._on_load = cb; } -pub fn getOnLoadEnd(self: *const FileReader) ?js.Function.Temp { +pub fn getOnLoadEnd(self: *const FileReader) ?js.Function.Global { return self._on_load_end; } -pub fn setOnLoadEnd(self: *FileReader, cb: ?js.Function.Temp) !void { +pub fn setOnLoadEnd(self: *FileReader, cb: ?js.Function.Global) !void { self._on_load_end = cb; } -pub fn getOnLoadStart(self: *const FileReader) ?js.Function.Temp { +pub fn getOnLoadStart(self: *const FileReader) ?js.Function.Global { return self._on_load_start; } -pub fn setOnLoadStart(self: *FileReader, cb: ?js.Function.Temp) !void { +pub fn setOnLoadStart(self: *FileReader, cb: ?js.Function.Global) !void { self._on_load_start = cb; } -pub fn getOnProgress(self: *const FileReader) ?js.Function.Temp { +pub fn getOnProgress(self: *const FileReader) ?js.Function.Global { return self._on_progress; } -pub fn setOnProgress(self: *FileReader, cb: ?js.Function.Temp) !void { +pub fn setOnProgress(self: *FileReader, cb: ?js.Function.Global) !void { self._on_progress = cb; } diff --git a/src/browser/webapi/IntersectionObserver.zig b/src/browser/webapi/IntersectionObserver.zig index 69bdfcd64..8a70a7436 100644 --- a/src/browser/webapi/IntersectionObserver.zig +++ b/src/browser/webapi/IntersectionObserver.zig @@ -41,7 +41,7 @@ const IntersectionObserver = @This(); _rc: lp.RC(u8) = .{}, _arena: Allocator, -_callback: js.Function.Temp, +_callback: js.Function.Global, _observing: std.ArrayList(*Element) = .{}, _root: ?*Element = null, _root_margin: []const u8 = "0px", @@ -69,7 +69,7 @@ pub const ObserverInit = struct { }; }; -pub fn init(callback: js.Function.Temp, options: ?ObserverInit, frame: *Frame) !*IntersectionObserver { +pub fn init(callback: js.Function.Global, options: ?ObserverInit, frame: *Frame) !*IntersectionObserver { const arena = try frame.getArena(.small, "IntersectionObserver"); errdefer frame.releaseArena(arena); diff --git a/src/browser/webapi/MessagePort.zig b/src/browser/webapi/MessagePort.zig index a2af00fb1..d50387679 100644 --- a/src/browser/webapi/MessagePort.zig +++ b/src/browser/webapi/MessagePort.zig @@ -78,7 +78,7 @@ pub fn postMessage(self: *MessagePort, message: js.Value, frame: *Frame) !void { const c = message.structuredCloneTo(&ls.local) catch { return error.DataClone; }; - break :blk try c.temp(); + break :blk try c.persist(); }; errdefer cloned.release(); @@ -131,7 +131,7 @@ pub fn setOnMessageError(self: *MessagePort, cb: ?js.Function.Global) !void { const PostMessageCallback = struct { port: *MessagePort, - message: js.Value.Temp, + message: js.Value.Global, frame: *Frame, // Called by the scheduler if the task is dropped before it runs. `run` and diff --git a/src/browser/webapi/MutationObserver.zig b/src/browser/webapi/MutationObserver.zig index 6d3dab74e..719f09567 100644 --- a/src/browser/webapi/MutationObserver.zig +++ b/src/browser/webapi/MutationObserver.zig @@ -41,7 +41,7 @@ const MutationObserver = @This(); _rc: lp.RC(u8) = .{}, _arena: Allocator, -_callback: js.Function.Temp, +_callback: js.Function.Global, _observing: std.ArrayList(Observing) = .{}, _pending_records: std.ArrayList(*MutationRecord) = .{}, @@ -74,7 +74,7 @@ pub const ObserveOptions = struct { attributeFilter: ?[]const []const u8 = null, }; -pub fn init(callback: js.Function.Temp, frame: *Frame) !*MutationObserver { +pub fn init(callback: js.Function.Global, frame: *Frame) !*MutationObserver { const arena = try frame.getArena(.small, "MutationObserver"); errdefer frame.releaseArena(arena); const self = try arena.create(MutationObserver); diff --git a/src/browser/webapi/Timers.zig b/src/browser/webapi/Timers.zig index c92f7973b..eb382520f 100644 --- a/src/browser/webapi/Timers.zig +++ b/src/browser/webapi/Timers.zig @@ -58,7 +58,7 @@ pub const Mode = enum { pub const ScheduleOpts = struct { repeat: bool, - params: []js.Value.Temp, + params: []js.Value.Global, name: []const u8, low_priority: bool = false, mode: Mode = .normal, @@ -67,7 +67,7 @@ pub const ScheduleOpts = struct { pub fn schedule( self: *Timers, exec: *js.Execution, - cb: js.Function.Temp, + cb: js.Function.Global, delay_ms: u32, opts: ScheduleOpts, ) !u32 { @@ -82,9 +82,9 @@ pub fn schedule( const timer_id = self._timer_id +% 1; self._timer_id = timer_id; - var persisted_params: []js.Value.Temp = &.{}; + var persisted_params: []js.Value.Global = &.{}; if (opts.params.len > 0) { - persisted_params = try arena.dupe(js.Value.Temp, opts.params); + persisted_params = try arena.dupe(js.Value.Global, opts.params); } const gop = try self._callbacks.getOrPut(exec.arena, timer_id); @@ -128,15 +128,15 @@ pub fn clear(self: *Timers, id: u32) void { // compiled into an anonymous function body, matching how legacy browsers // (and all current UAs) interpret `setTimeout("foo()", 100)`. pub const LegacyHandler = union(enum) { - function: js.Function.Temp, + function: js.Function.Global, string: js.String, - pub fn resolve(handler: LegacyHandler, exec: *js.Execution) !js.Function.Temp { + pub fn resolve(handler: LegacyHandler, exec: *js.Execution) !js.Function.Global { switch (handler) { .function => |fun| return fun, .string => |str| { const fun = try exec.js.local.?.compileFunction(str, &.{}, &.{}); - return fun.temp(); + return fun.persist(); }, } } @@ -152,14 +152,14 @@ const ScheduleCallback = struct { // delay, in ms, to repeat. When null, removed after first invocation. repeat_ms: ?u32, - cb: js.Function.Temp, + cb: js.Function.Global, mode: Mode, exec: *js.Execution, timers: *Timers, arena: Allocator, removed: bool = false, - params: []const js.Value.Temp, + params: []const js.Value.Global, fn cancelled(ptr: *anyopaque) void { var self: *ScheduleCallback = @ptrCast(@alignCast(ptr)); diff --git a/src/browser/webapi/WebDriver.zig b/src/browser/webapi/WebDriver.zig index c87d0cfa9..c91d268f5 100644 --- a/src/browser/webapi/WebDriver.zig +++ b/src/browser/webapi/WebDriver.zig @@ -64,7 +64,7 @@ pub fn actionSequence(_: *const WebDriver, sources: js.Value, frame: *Frame) !vo const arena = try frame.getArena(.tiny, "WebDriver.actionSequence"); errdefer frame.releaseArena(arena); - const persisted = try sources.temp(); + const persisted = try sources.persist(); errdefer persisted.release(); const action_sequence = try arena.create(ActionSequence); @@ -85,7 +85,7 @@ pub fn actionSequence(_: *const WebDriver, sources: js.Value, frame: *Frame) !vo const ActionSequence = struct { frame: *Frame, arena: Allocator, - sources: js.Value.Temp, + sources: js.Value.Global, fn run(ptr: *anyopaque) !?u32 { const self: *ActionSequence = @ptrCast(@alignCast(ptr)); diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index abe0dbf53..7ef34cd55 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -381,7 +381,7 @@ pub fn fetch(_: *const Window, input: Fetch.Input, options: ?Fetch.InitOpts, exe return Fetch.init(input, options, exec); } -pub fn setTimeout(self: *Window, handler: Timers.LegacyHandler, delay_ms: ?u32, params: []js.Value.Temp, exec: *js.Execution) !u32 { +pub fn setTimeout(self: *Window, handler: Timers.LegacyHandler, delay_ms: ?u32, params: []js.Value.Global, exec: *js.Execution) !u32 { const cb = try handler.resolve(exec); return self._timers.schedule(exec, cb, delay_ms orelse 0, .{ .repeat = false, @@ -390,7 +390,7 @@ pub fn setTimeout(self: *Window, handler: Timers.LegacyHandler, delay_ms: ?u32, }); } -pub fn setInterval(self: *Window, handler: Timers.LegacyHandler, delay_ms: ?u32, params: []js.Value.Temp, exec: *js.Execution) !u32 { +pub fn setInterval(self: *Window, handler: Timers.LegacyHandler, delay_ms: ?u32, params: []js.Value.Global, exec: *js.Execution) !u32 { const cb = try handler.resolve(exec); return self._timers.schedule(exec, cb, delay_ms orelse 0, .{ .repeat = true, @@ -399,7 +399,7 @@ pub fn setInterval(self: *Window, handler: Timers.LegacyHandler, delay_ms: ?u32, }); } -pub fn setImmediate(self: *Window, cb: js.Function.Temp, params: []js.Value.Temp, exec: *js.Execution) !u32 { +pub fn setImmediate(self: *Window, cb: js.Function.Global, params: []js.Value.Global, exec: *js.Execution) !u32 { return self._timers.schedule(exec, cb, 0, .{ .repeat = false, .params = params, @@ -407,7 +407,7 @@ pub fn setImmediate(self: *Window, cb: js.Function.Temp, params: []js.Value.Temp }); } -pub fn requestAnimationFrame(self: *Window, cb: js.Function.Temp, exec: *js.Execution) !u32 { +pub fn requestAnimationFrame(self: *Window, cb: js.Function.Global, exec: *js.Execution) !u32 { return self._timers.schedule(exec, cb, 5, .{ .repeat = false, .params = &.{}, @@ -439,7 +439,7 @@ pub fn cancelAnimationFrame(self: *Window, id: u32) void { const RequestIdleCallbackOpts = struct { timeout: ?u32 = null, }; -pub fn requestIdleCallback(self: *Window, cb: js.Function.Temp, opts_: ?RequestIdleCallbackOpts, exec: *js.Execution) !u32 { +pub fn requestIdleCallback(self: *Window, cb: js.Function.Global, opts_: ?RequestIdleCallbackOpts, exec: *js.Execution) !u32 { const opts = opts_ orelse RequestIdleCallbackOpts{}; return self._timers.schedule(exec, cb, opts.timeout orelse 50, .{ .mode = .idle, @@ -456,7 +456,7 @@ pub fn cancelIdleCallback(self: *Window, id: u32) void { pub fn reportError(self: *Window, err: js.Value, frame: *Frame) !void { const error_event = try ErrorEvent.initTrusted(comptime .wrap("error"), .{ - .@"error" = try err.temp(), + .@"error" = try err.persist(), .message = err.toStringSlice() catch "Unknown error", .bubbles = false, .cancelable = true, @@ -683,7 +683,7 @@ pub fn postMessage(self: *Window, message: js.Value, target_origin: ?[]const u8, const c = message.structuredCloneTo(&ls.local) catch { return error.DataClone; }; - break :blk try c.temp(); + break :blk try c.persist(); }; errdefer cloned.release(); @@ -884,8 +884,8 @@ pub fn unhandledPromiseRejection(self: *Window, no_handler: bool, rejection: js. const target = self.asEventTarget(); if (frame._event_manager.hasDirectListeners(target, event_name, attribute_callback)) { const event = (try @import("event/PromiseRejectionEvent.zig").init(event_name, .{ - .reason = if (rejection.reason()) |r| try r.temp() else null, - .promise = try rejection.promise().temp(), + .reason = if (rejection.reason()) |r| try r.persist() else null, + .promise = try rejection.promise().persist(), }, frame._page)).asEvent(); try frame._event_manager.dispatchDirect(target, event, attribute_callback, .{ .context = "window.unhandledrejection" }); } @@ -923,7 +923,7 @@ const PostMessageCallback = struct { source: *Window, arena: Allocator, origin: []const u8, - message: js.Value.Temp, + message: js.Value.Global, ports: []const *MessagePort, fn deinit(self: *PostMessageCallback) void { diff --git a/src/browser/webapi/Worker.zig b/src/browser/webapi/Worker.zig index 9b2659ed7..bd16987db 100644 --- a/src/browser/webapi/Worker.zig +++ b/src/browser/webapi/Worker.zig @@ -265,13 +265,13 @@ fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void { } // Fire an error event on the Worker object (parent context) -fn fireErrorEvent(self: *Worker, message: []const u8, error_value: ?js.Value.Temp) void { +fn fireErrorEvent(self: *Worker, message: []const u8, error_value: ?js.Value.Global) void { self._fireErrorEvent(message, error_value) catch |err| { log.warn(.browser, "worker fire error", .{ .err = err, .message = message }); }; } -fn _fireErrorEvent(self: *Worker, message: []const u8, error_value: ?js.Value.Temp) !void { +fn _fireErrorEvent(self: *Worker, message: []const u8, error_value: ?js.Value.Global) !void { const frame = self._frame; const target = self.asEventTarget(); const on_error = self._on_error; @@ -318,7 +318,7 @@ pub fn receiveMessage(self: *Worker, data: js.Value) !void { // clones from where it currently is (the Worker context) to our Page's context const cloned = data.structuredCloneTo(&ls.local) catch |err| break :blk err; - break :blk cloned.temp(); + break :blk cloned.persist(); }; const message_arena = try frame.getArena(.tiny, "Worker.receiveMessage"); @@ -376,7 +376,7 @@ fn getFunctionFromSetter(setter_: ?FunctionSetter) ?js.Function.Global { } const ReceiveMessageCallback = struct { - data: anyerror!js.Value.Temp, + data: anyerror!js.Value.Global, arena: Allocator, worker: *Worker, diff --git a/src/browser/webapi/WorkerGlobalScope.zig b/src/browser/webapi/WorkerGlobalScope.zig index 030d56853..39b6c25bb 100644 --- a/src/browser/webapi/WorkerGlobalScope.zig +++ b/src/browser/webapi/WorkerGlobalScope.zig @@ -348,8 +348,8 @@ pub fn unhandledPromiseRejection(self: *WorkerGlobalScope, no_handler: bool, rej const target = self.asEventTarget(); if (self._event_manager.hasDirectListeners(target, event_name, attribute_callback)) { const event = (try @import("event/PromiseRejectionEvent.zig").init(event_name, .{ - .reason = if (rejection.reason()) |r| try r.temp() else null, - .promise = try rejection.promise().temp(), + .reason = if (rejection.reason()) |r| try r.persist() else null, + .promise = try rejection.promise().persist(), }, self._page)).asEvent(); try self.dispatch(target, event, attribute_callback, .{}); } @@ -423,7 +423,7 @@ fn importScript(self: *WorkerGlobalScope, arena: Allocator, url: [:0]const u8) ! pub fn reportError(self: *WorkerGlobalScope, err: JS.Value) !void { const error_event = try ErrorEvent.initTrusted(comptime .wrap("error"), .{ - .@"error" = try err.temp(), + .@"error" = try err.persist(), .message = err.toStringSlice() catch "Unknown error", .bubbles = false, .cancelable = true, @@ -479,7 +479,7 @@ pub fn queueMicrotask(self: *WorkerGlobalScope, cb: JS.Function) void { self.js.queueMicrotaskFunc(cb); } -pub fn setTimeout(self: *WorkerGlobalScope, handler: Timers.LegacyHandler, delay_ms: ?u32, params: []JS.Value.Temp, exec: *JS.Execution) !u32 { +pub fn setTimeout(self: *WorkerGlobalScope, handler: Timers.LegacyHandler, delay_ms: ?u32, params: []JS.Value.Global, exec: *JS.Execution) !u32 { const cb = try handler.resolve(exec); return self._timers.schedule(exec, cb, delay_ms orelse 0, .{ .repeat = false, @@ -492,7 +492,7 @@ pub fn clearTimeout(self: *WorkerGlobalScope, id: u32) void { self._timers.clear(id); } -pub fn setInterval(self: *WorkerGlobalScope, handler: Timers.LegacyHandler, delay_ms: ?u32, params: []JS.Value.Temp, exec: *JS.Execution) !u32 { +pub fn setInterval(self: *WorkerGlobalScope, handler: Timers.LegacyHandler, delay_ms: ?u32, params: []JS.Value.Global, exec: *JS.Execution) !u32 { const cb = try handler.resolve(exec); return self._timers.schedule(exec, cb, delay_ms orelse 0, .{ .repeat = true, diff --git a/src/browser/webapi/animation/Animation.zig b/src/browser/webapi/animation/Animation.zig index 91ddff41e..5880df9dd 100644 --- a/src/browser/webapi/animation/Animation.zig +++ b/src/browser/webapi/animation/Animation.zig @@ -43,7 +43,7 @@ _timeline: ?js.Object.Global = null, _ready_resolver: ?js.PromiseResolver.Global = null, _finished_resolver: ?js.PromiseResolver.Global = null, _startTime: ?f64 = null, -_onFinish: ?js.Function.Temp = null, +_onFinish: ?js.Function.Global = null, _playState: PlayState = .idle, // Fake the animation by passing the states: @@ -179,7 +179,7 @@ pub fn setStartTime(self: *Animation, value: ?f64, frame: *Frame) !void { return self.play(frame); } -pub fn getOnFinish(self: *const Animation) ?js.Function.Temp { +pub fn getOnFinish(self: *const Animation) ?js.Function.Global { return self._onFinish; } @@ -215,7 +215,7 @@ fn update(ctx: *anyopaque) !?u32 { return null; } -pub fn setOnFinish(self: *Animation, cb: ?js.Function.Temp) !void { +pub fn setOnFinish(self: *Animation, cb: ?js.Function.Global) !void { self._onFinish = cb; } diff --git a/src/browser/webapi/event/CustomEvent.zig b/src/browser/webapi/event/CustomEvent.zig index 338451811..381f0cdaa 100644 --- a/src/browser/webapi/event/CustomEvent.zig +++ b/src/browser/webapi/event/CustomEvent.zig @@ -30,11 +30,11 @@ const Allocator = std.mem.Allocator; const CustomEvent = @This(); _proto: *Event, -_detail: ?js.Value.Temp = null, +_detail: ?js.Value.Global = null, _arena: Allocator, const CustomEventOptions = struct { - detail: ?js.Value.Temp = null, + detail: ?js.Value.Global = null, }; const Options = Event.inheritOptions(CustomEvent, CustomEventOptions); @@ -64,7 +64,7 @@ pub fn initCustomEvent( event_string: []const u8, bubbles: ?bool, cancelable: ?bool, - detail_: ?js.Value.Temp, + detail_: ?js.Value.Global, ) !void { // This function can only be called after the constructor has called. // So we assume proto is initialized already by constructor. @@ -94,7 +94,7 @@ pub fn asEvent(self: *CustomEvent) *Event { return self._proto; } -pub fn getDetail(self: *const CustomEvent) ?js.Value.Temp { +pub fn getDetail(self: *const CustomEvent) ?js.Value.Global { return self._detail; } diff --git a/src/browser/webapi/event/ErrorEvent.zig b/src/browser/webapi/event/ErrorEvent.zig index de7c1d927..672422f10 100644 --- a/src/browser/webapi/event/ErrorEvent.zig +++ b/src/browser/webapi/event/ErrorEvent.zig @@ -34,7 +34,7 @@ _message: []const u8 = "", _filename: []const u8 = "", _line_number: u32 = 0, _column_number: u32 = 0, -_error: ?js.Value.Temp = null, +_error: ?js.Value.Global = null, _arena: Allocator, pub const ErrorEventOptions = struct { @@ -42,7 +42,7 @@ pub const ErrorEventOptions = struct { filename: ?[]const u8 = null, lineno: u32 = 0, colno: u32 = 0, - @"error": ?js.Value.Temp = null, + @"error": ?js.Value.Global = null, }; const Options = Event.inheritOptions(ErrorEvent, ErrorEventOptions); @@ -116,7 +116,7 @@ pub fn getColumnNumber(self: *const ErrorEvent) u32 { return self._column_number; } -pub fn getError(self: *const ErrorEvent) ?js.Value.Temp { +pub fn getError(self: *const ErrorEvent) ?js.Value.Global { return self._error; } diff --git a/src/browser/webapi/event/MessageEvent.zig b/src/browser/webapi/event/MessageEvent.zig index 6874b5b7e..9ed49ce1b 100644 --- a/src/browser/webapi/event/MessageEvent.zig +++ b/src/browser/webapi/event/MessageEvent.zig @@ -46,7 +46,7 @@ const MessageEventOptions = struct { }; pub const Data = union(enum) { - value: js.Value.Temp, + value: js.Value.Global, string: []const u8, arraybuffer: js.ArrayBuffer, blob: *@import("../Blob.zig"), diff --git a/src/browser/webapi/event/PromiseRejectionEvent.zig b/src/browser/webapi/event/PromiseRejectionEvent.zig index 20ebb0a7c..a622a5d49 100644 --- a/src/browser/webapi/event/PromiseRejectionEvent.zig +++ b/src/browser/webapi/event/PromiseRejectionEvent.zig @@ -27,12 +27,12 @@ const String = lp.String; const PromiseRejectionEvent = @This(); _proto: *Event, -_reason: ?js.Value.Temp = null, -_promise: ?js.Promise.Temp = null, +_reason: ?js.Value.Global = null, +_promise: ?js.Promise.Global = null, const PromiseRejectionEventOptions = struct { - reason: ?js.Value.Temp = null, - promise: ?js.Promise.Temp = null, + reason: ?js.Value.Global = null, + promise: ?js.Promise.Global = null, }; const Options = Event.inheritOptions(PromiseRejectionEvent, PromiseRejectionEventOptions); @@ -79,11 +79,11 @@ pub fn asEvent(self: *PromiseRejectionEvent) *Event { return self._proto; } -pub fn getReason(self: *const PromiseRejectionEvent) ?js.Value.Temp { +pub fn getReason(self: *const PromiseRejectionEvent) ?js.Value.Global { return self._reason; } -pub fn getPromise(self: *const PromiseRejectionEvent) ?js.Promise.Temp { +pub fn getPromise(self: *const PromiseRejectionEvent) ?js.Promise.Global { return self._promise; } diff --git a/src/browser/webapi/net/WebSocket.zig b/src/browser/webapi/net/WebSocket.zig index a98efc591..48914916e 100644 --- a/src/browser/webapi/net/WebSocket.zig +++ b/src/browser/webapi/net/WebSocket.zig @@ -75,10 +75,10 @@ _close_reason: []const u8 = "", _protocol: []const u8 = "", // Event handlers -_on_open: ?js.Function.Temp = null, -_on_message: ?js.Function.Temp = null, -_on_error: ?js.Function.Temp = null, -_on_close: ?js.Function.Temp = null, +_on_open: ?js.Function.Global = null, +_on_message: ?js.Function.Global = null, +_on_error: ?js.Function.Global = null, +_on_close: ?js.Function.Global = null, pub const ReadyState = enum(u8) { connecting = 0, @@ -434,53 +434,53 @@ pub fn setBinaryType(self: *WebSocket, value: []const u8) void { } } -pub fn getOnOpen(self: *const WebSocket) ?js.Function.Temp { +pub fn getOnOpen(self: *const WebSocket) ?js.Function.Global { return self._on_open; } pub fn setOnOpen(self: *WebSocket, cb_: ?js.Function) !void { if (self._on_open) |old| old.release(); if (cb_) |cb| { - self._on_open = try cb.tempWithThis(self); + self._on_open = try cb.persistWithThis(self); } else { self._on_open = null; } } -pub fn getOnMessage(self: *const WebSocket) ?js.Function.Temp { +pub fn getOnMessage(self: *const WebSocket) ?js.Function.Global { return self._on_message; } pub fn setOnMessage(self: *WebSocket, cb_: ?js.Function) !void { if (self._on_message) |old| old.release(); if (cb_) |cb| { - self._on_message = try cb.tempWithThis(self); + self._on_message = try cb.persistWithThis(self); } else { self._on_message = null; } } -pub fn getOnError(self: *const WebSocket) ?js.Function.Temp { +pub fn getOnError(self: *const WebSocket) ?js.Function.Global { return self._on_error; } pub fn setOnError(self: *WebSocket, cb_: ?js.Function) !void { if (self._on_error) |old| old.release(); if (cb_) |cb| { - self._on_error = try cb.tempWithThis(self); + self._on_error = try cb.persistWithThis(self); } else { self._on_error = null; } } -pub fn getOnClose(self: *const WebSocket) ?js.Function.Temp { +pub fn getOnClose(self: *const WebSocket) ?js.Function.Global { return self._on_close; } pub fn setOnClose(self: *WebSocket, cb_: ?js.Function) !void { if (self._on_close) |old| old.release(); if (cb_) |cb| { - self._on_close = try cb.tempWithThis(self); + self._on_close = try cb.persistWithThis(self); } else { self._on_close = null; } diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig index 73460573d..33f369e68 100644 --- a/src/browser/webapi/net/XMLHttpRequest.zig +++ b/src/browser/webapi/net/XMLHttpRequest.zig @@ -71,7 +71,7 @@ _response_headers: std.ArrayList([]const u8) = .empty, _response_type: ResponseType = .text, _ready_state: ReadyState = .unsent, -_on_ready_state_change: ?js.Function.Temp = null, +_on_ready_state_change: ?js.Function.Global = null, _with_credentials: bool = false, _timeout: u32 = 0, @@ -147,13 +147,13 @@ fn asEventTarget(self: *XMLHttpRequest) *EventTarget { return self._proto._proto; } -pub fn getOnReadyStateChange(self: *const XMLHttpRequest) ?js.Function.Temp { +pub fn getOnReadyStateChange(self: *const XMLHttpRequest) ?js.Function.Global { return self._on_ready_state_change; } pub fn setOnReadyStateChange(self: *XMLHttpRequest, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_ready_state_change = try cb.tempWithThis(self); + self._on_ready_state_change = try cb.persistWithThis(self); } else { self._on_ready_state_change = null; } diff --git a/src/browser/webapi/net/XMLHttpRequestEventTarget.zig b/src/browser/webapi/net/XMLHttpRequestEventTarget.zig index a5b64dc0b..cfa258fc0 100644 --- a/src/browser/webapi/net/XMLHttpRequestEventTarget.zig +++ b/src/browser/webapi/net/XMLHttpRequestEventTarget.zig @@ -27,13 +27,13 @@ const XMLHttpRequestEventTarget = @This(); _type: Type, _proto: *EventTarget, -_on_abort: ?js.Function.Temp = null, -_on_error: ?js.Function.Temp = null, -_on_load: ?js.Function.Temp = null, -_on_load_end: ?js.Function.Temp = null, -_on_load_start: ?js.Function.Temp = null, -_on_progress: ?js.Function.Temp = null, -_on_timeout: ?js.Function.Temp = null, +_on_abort: ?js.Function.Global = null, +_on_error: ?js.Function.Global = null, +_on_load: ?js.Function.Global = null, +_on_load_end: ?js.Function.Global = null, +_on_load_start: ?js.Function.Global = null, +_on_progress: ?js.Function.Global = null, +_on_timeout: ?js.Function.Global = null, pub const Type = union(enum) { request: *@import("XMLHttpRequest.zig"), @@ -83,61 +83,61 @@ pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchT ); } -pub fn getOnAbort(self: *const XMLHttpRequestEventTarget) ?js.Function.Temp { +pub fn getOnAbort(self: *const XMLHttpRequestEventTarget) ?js.Function.Global { return self._on_abort; } -pub fn setOnAbort(self: *XMLHttpRequestEventTarget, cb: ?js.Function.Temp) !void { +pub fn setOnAbort(self: *XMLHttpRequestEventTarget, cb: ?js.Function.Global) !void { self._on_abort = cb; } -pub fn getOnError(self: *const XMLHttpRequestEventTarget) ?js.Function.Temp { +pub fn getOnError(self: *const XMLHttpRequestEventTarget) ?js.Function.Global { return self._on_error; } -pub fn setOnError(self: *XMLHttpRequestEventTarget, cb: ?js.Function.Temp) !void { +pub fn setOnError(self: *XMLHttpRequestEventTarget, cb: ?js.Function.Global) !void { self._on_error = cb; } -pub fn getOnLoad(self: *const XMLHttpRequestEventTarget) ?js.Function.Temp { +pub fn getOnLoad(self: *const XMLHttpRequestEventTarget) ?js.Function.Global { return self._on_load; } -pub fn setOnLoad(self: *XMLHttpRequestEventTarget, cb: ?js.Function.Temp) !void { +pub fn setOnLoad(self: *XMLHttpRequestEventTarget, cb: ?js.Function.Global) !void { self._on_load = cb; } -pub fn getOnLoadEnd(self: *const XMLHttpRequestEventTarget) ?js.Function.Temp { +pub fn getOnLoadEnd(self: *const XMLHttpRequestEventTarget) ?js.Function.Global { return self._on_load_end; } -pub fn setOnLoadEnd(self: *XMLHttpRequestEventTarget, cb: ?js.Function.Temp) !void { +pub fn setOnLoadEnd(self: *XMLHttpRequestEventTarget, cb: ?js.Function.Global) !void { self._on_load_end = cb; } -pub fn getOnLoadStart(self: *const XMLHttpRequestEventTarget) ?js.Function.Temp { +pub fn getOnLoadStart(self: *const XMLHttpRequestEventTarget) ?js.Function.Global { return self._on_load_start; } -pub fn setOnLoadStart(self: *XMLHttpRequestEventTarget, cb: ?js.Function.Temp) !void { +pub fn setOnLoadStart(self: *XMLHttpRequestEventTarget, cb: ?js.Function.Global) !void { self._on_load_start = cb; } -pub fn getOnProgress(self: *const XMLHttpRequestEventTarget) ?js.Function.Temp { +pub fn getOnProgress(self: *const XMLHttpRequestEventTarget) ?js.Function.Global { return self._on_progress; } -pub fn setOnProgress(self: *XMLHttpRequestEventTarget, cb: ?js.Function.Temp) !void { +pub fn setOnProgress(self: *XMLHttpRequestEventTarget, cb: ?js.Function.Global) !void { self._on_progress = cb; } -pub fn getOnTimeout(self: *const XMLHttpRequestEventTarget) ?js.Function.Temp { +pub fn getOnTimeout(self: *const XMLHttpRequestEventTarget) ?js.Function.Global { return self._on_timeout; } pub fn setOnTimeout(self: *XMLHttpRequestEventTarget, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_timeout = try cb.tempWithThis(self); + self._on_timeout = try cb.persistWithThis(self); } else { self._on_timeout = null; } diff --git a/src/browser/webapi/storage/idb/IDBCursor.zig b/src/browser/webapi/storage/idb/IDBCursor.zig index f39164a8e..841c6b04d 100644 --- a/src/browser/webapi/storage/idb/IDBCursor.zig +++ b/src/browser/webapi/storage/idb/IDBCursor.zig @@ -50,7 +50,7 @@ _index_id: ?i64 = null, // the JS value of this Cursor, pre-converted and cached as an optimization // since this cursor will be the request value on every iteration. -_js: *js.Value.BareGlobal, +_js: *js.GlobalSlot, // Encoded current key; null before iteration and at the end. For an index cursor // this is the index key; for an object store it equals the primary key. @@ -62,7 +62,7 @@ _primary_key: ?[]const u8 = null, _value: ?[]const u8 = null, // The deserialized JS value, cached so repeated `.value` reads return the same // object (and observe mutations to it). Reset whenever the cursor repositions. -_value_js: ?*js.Value.BareGlobal = null, +_value_js: ?*js.GlobalSlot = null, // Backing storage for _key/_primary_key/_value, reused across positions so a // long scan holds one record's worth of memory, not the whole traversal. @@ -384,7 +384,7 @@ fn exhaust(self: *IDBCursor) void { // value until transaction teardown. fn invalidateValue(self: *IDBCursor) void { if (self._value_js) |slot| { - slot.deinit(); + slot.reset(); self._value_js = null; } } diff --git a/src/browser/webapi/storage/idb/IDBIndex.zig b/src/browser/webapi/storage/idb/IDBIndex.zig index 9aa342120..88aa4670c 100644 --- a/src/browser/webapi/storage/idb/IDBIndex.zig +++ b/src/browser/webapi/storage/idb/IDBIndex.zig @@ -49,7 +49,7 @@ _deleted: bool = false, _created: bool = false, // not just for efficiency, we must return the same v8::Array every time the // compound key is accessed. -_key_path_js: ?*js.Value.BareGlobal = null, +_key_path_js: ?*js.GlobalSlot = null, pub fn init(obj_store: *IDBObjectStore, info: Engine.IndexInfo, name: []const u8) !*IDBIndex { const self = try obj_store._txn._arena.create(IDBIndex); diff --git a/src/browser/webapi/storage/idb/IDBObjectStore.zig b/src/browser/webapi/storage/idb/IDBObjectStore.zig index 4b8cafe02..fa42e0ff3 100644 --- a/src/browser/webapi/storage/idb/IDBObjectStore.zig +++ b/src/browser/webapi/storage/idb/IDBObjectStore.zig @@ -52,7 +52,7 @@ _created: bool = false, _indexes: std.ArrayList(*IDBIndex) = .empty, // not just for efficiency, we must return the same v8::Array every time the // compound key is accessed. -_key_path_js: ?*js.Value.BareGlobal = null, +_key_path_js: ?*js.GlobalSlot = null, pub fn init( txn: *IDBTransaction, @@ -358,10 +358,10 @@ fn write(self: *IDBObjectStore, value: js.Value, key_arg: ?js.Value, kind: Write } }, exec); } -pub fn runWrite(self: *IDBObjectStore, request: *IDBRequest, kind: WriteKind, value_global: *js.Value.BareGlobal, prepared: PreparedKey, exec: *Execution) !void { +pub fn runWrite(self: *IDBObjectStore, request: *IDBRequest, kind: WriteKind, value_global: *js.GlobalSlot, prepared: PreparedKey, exec: *Execution) !void { // Written (or failed) is written: the pinned value is dead once this op // ran, so release its handle now instead of at transaction teardown. - defer value_global.deinit(); + defer value_global.reset(); self.writeInner(request, kind, value_global, prepared, exec) catch |err| { if (err != error.Constraint) { log.warn(.storage, "idb write", .{ .err = err, .kind = kind, .sqlite = self._engine.lastError() }); @@ -370,7 +370,7 @@ pub fn runWrite(self: *IDBObjectStore, request: *IDBRequest, kind: WriteKind, va }; } -fn writeInner(self: *IDBObjectStore, request: *IDBRequest, kind: WriteKind, value_global: *js.Value.BareGlobal, prepared: PreparedKey, exec: *Execution) !void { +fn writeInner(self: *IDBObjectStore, request: *IDBRequest, kind: WriteKind, value_global: *js.GlobalSlot, prepared: PreparedKey, exec: *Execution) !void { const local = exec.js.local.?; const value = value_global.local(local); diff --git a/src/browser/webapi/storage/idb/IDBRequest.zig b/src/browser/webapi/storage/idb/IDBRequest.zig index 7be3f39a1..7c18f3838 100644 --- a/src/browser/webapi/storage/idb/IDBRequest.zig +++ b/src/browser/webapi/storage/idb/IDBRequest.zig @@ -72,7 +72,7 @@ const ReadyState = enum { const Result = union(enum) { none: ?js.Undefined, // null or undefined (different APIs return different values) - value: *js.Value.BareGlobal, // the result of a get/add/put, or a positioned cursor + value: *js.GlobalSlot, // the result of a get/add/put, or a positioned cursor database: *IDBDatabase, // the result of an open }; @@ -139,8 +139,8 @@ fn clearOwnedResult(self: *IDBRequest) void { self._result_owned = false; switch (self._result) { .value => |global| { - // It's ok to keep this in txn._globals, deinit can be called multiple times - global.deinit(); + // It's ok to keep this in txn._globals, reset can be called multiple times + global.reset(); self._result = .{ .none = js.Undefined{} }; }, .none, .database => {}, @@ -159,7 +159,7 @@ pub fn setValue(self: *IDBRequest, value: js.Value) !void { // Not exposed to JS, called internally. The handle is borrowed (a cursor's // transaction-owned _js), not owned by this request. -pub fn setValueGlobal(self: *IDBRequest, global: *js.Value.BareGlobal) void { +pub fn setValueGlobal(self: *IDBRequest, global: *js.GlobalSlot) void { self.clearOwnedResult(); self._result = .{ .value = global }; } @@ -371,7 +371,7 @@ pub const Operation = union(enum) { const StoreQuery = struct { store: *IDBObjectStore, bounds: Engine.Bounds }; const StoreGetAll = struct { store: *IDBObjectStore, args: IDBKeyRange.GetAllArgs, mode: IDBObjectStore.GetAllMode }; - const StoreWrite = struct { store: *IDBObjectStore, kind: IDBObjectStore.WriteKind, value: *js.Value.BareGlobal, key: IDBObjectStore.PreparedKey }; + const StoreWrite = struct { store: *IDBObjectStore, kind: IDBObjectStore.WriteKind, value: *js.GlobalSlot, key: IDBObjectStore.PreparedKey }; const IndexQuery = struct { index: *IDBIndex, bounds: Engine.Bounds }; const IndexGetAll = struct { index: *IDBIndex, args: IDBKeyRange.GetAllArgs, mode: IDBObjectStore.GetAllMode }; const CursorIterate = struct { cursor: *IDBCursor, seek: IDBCursor.Seek, offset: u32 }; diff --git a/src/browser/webapi/storage/idb/IDBTransaction.zig b/src/browser/webapi/storage/idb/IDBTransaction.zig index 08b3738ed..9df7dabb7 100644 --- a/src/browser/webapi/storage/idb/IDBTransaction.zig +++ b/src/browser/webapi/storage/idb/IDBTransaction.zig @@ -65,7 +65,7 @@ _arena: Allocator, // v8 handles owned by the transaction, swept (reset) in deinit. Slots are // arena-allocated so an early release and the sweep hit the same instance — // a v8 Global reset is only idempotent through a single instance. -_globals: std.ArrayList(*js.Value.BareGlobal) = .empty, +_globals: std.ArrayList(*js.GlobalSlot) = .empty, // objectStore() must return the same object for a given name within one // transaction (per spec); this also keeps repeated lookups off sqlite. @@ -180,7 +180,7 @@ pub fn deinit(self: *IDBTransaction, page: *Page) void { std.debug.assert(self._parked == false); } for (self._globals.items) |slot| { - slot.deinit(); + slot.reset(); } page.releaseArena(self._arena); } @@ -194,12 +194,12 @@ pub fn releaseRef(self: *IDBTransaction, page: *Page) void { } // Persist a JS value with the transaction's lifetime: the handle is reset when the -// transaction's memory is released — or earlier, by calling deinit() on the +// transaction's memory is released — or earlier, by calling reset() on the // returned slot (the sweep's second reset is then a no-op). -pub fn persist(self: *IDBTransaction, value: js.Value) !*js.Value.BareGlobal { - const slot = try self._arena.create(js.Value.BareGlobal); +pub fn persist(self: *IDBTransaction, value: js.Value) !*js.GlobalSlot { + const slot = try value.persistBare(self._arena); + errdefer slot.reset(); try self._globals.append(self._arena, slot); - slot.* = value.bare(); return slot; } diff --git a/src/browser/webapi/storage/idb/idb.zig b/src/browser/webapi/storage/idb/idb.zig index 8b196c996..33d7da71a 100644 --- a/src/browser/webapi/storage/idb/idb.zig +++ b/src/browser/webapi/storage/idb/idb.zig @@ -54,7 +54,7 @@ pub fn registerTypes() []const type { // the keyPath attribute need identity equality. For a compound key (an array) // we have to return the same v8::rray on every reach. So users of KeyPath will // cache it locally, and store it in the Transaction's globals list for cleanup. -pub fn cachedKeyPathJs(cache: *?*js.Value.BareGlobal, txn: *IDBTransaction, kp: ?Key.KeyPath, exec: *js.Execution) !js.Value { +pub fn cachedKeyPathJs(cache: *?*js.GlobalSlot, txn: *IDBTransaction, kp: ?Key.KeyPath, exec: *js.Execution) !js.Value { const local = exec.js.local.?; if (kp) |path| { if (std.meta.activeTag(path) == .list) {