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) {