Merge pull request #2898 from lightpanda-io/js_global_rework

refactor: Rework how v8::Globals are managed
This commit is contained in:
Karl Seguin
2026-07-13 07:20:37 +08:00
committed by GitHub
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) {