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.
This commit is contained in:
Karl Seguin
2026-07-04 22:14:17 +08:00
parent 958bea53de
commit 2a7b399d90
36 changed files with 350 additions and 380 deletions

View File

@@ -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;

View File

@@ -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))?"),
};

View File

@@ -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);

View File

@@ -277,14 +277,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,
@@ -1134,7 +1126,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<v8::Function> 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

View File

@@ -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);
}
}
};
}

View File

@@ -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 => {

View File

@@ -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);
}
};

View File

@@ -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);
}
}
};
}

View File

@@ -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)),
};
}
};

View File

@@ -622,34 +622,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 {
@@ -696,48 +679,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();

View File

@@ -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.

View File

@@ -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;
};

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -78,7 +78,7 @@ pub fn postMessage(self: *MessagePort, message: js.Value, exec: *Execution) !voi
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,
exec: *Execution,
// Called by the scheduler if the task is dropped before it runs. `run` and

View File

@@ -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);

View File

@@ -68,7 +68,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,
@@ -77,7 +77,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 {
@@ -95,9 +95,9 @@ pub fn schedule(
const nesting = @min(self._nesting_level + 1, CLAMP_NESTING + 1);
const delay = if (nesting > CLAMP_NESTING and delay_ms < CLAMP_MS) CLAMP_MS else delay_ms;
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);
@@ -142,15 +142,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();
},
}
}
@@ -170,14 +170,14 @@ const ScheduleCallback = struct {
// the Timer's _nesting_level so that any new timers will become nesting + 1
nesting: u8,
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));

View File

@@ -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));

View File

@@ -416,7 +416,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,
@@ -425,7 +425,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,
@@ -434,7 +434,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,
@@ -442,7 +442,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 = &.{},
@@ -474,7 +474,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,
@@ -491,7 +491,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,
@@ -725,7 +725,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();
@@ -926,8 +926,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" });
}
@@ -965,7 +965,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 {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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"),

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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 };

View File

@@ -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;
}

View File

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