Make Global Function explicit.

This is the first in a series of changes to make globals explicit. The ultimate
goal of having explicit Globals is to move away from the global HandleScope and
to explicit HandleScopes.

Currently, we treat globals and locals interchangeably. In fact, for Global ->
Local, we just ptrCast. This works because we have 1 global HandleScope, which
effectively disables V8's GC and thus nothing ever gets moved.

If we're going to introduce explicit HandleScopes, then we need to first have
correct Globals. Specifically, when we want to act on the global, we need to
get the local value, and that will eventually mean making sure there's a
HandleScope.

While adding explicit globals, we're keeping the global HandleScope so that we
can minimize the change. So, given that we still have the global HandleScope
the change is largely two things:
1 - js.Function.persit() returns a js.Function.Global. Types that persist global
   functions must be updated to js.Function.Global.
2 - To turn js.Function.Global -> js.Function, we need to call .local() on it.

The bridge has been updated to support js.Function.Global for both input and
output parameters. Thus, window.setOnLoad can now directly take a
js.Function.Global, and window.getOnLoad can directly return that
js.Function.Global.
This commit is contained in:
Karl Seguin
2026-01-14 15:12:22 +08:00
parent a30c65966b
commit 5def997bed
25 changed files with 198 additions and 191 deletions

View File

@@ -102,7 +102,7 @@ pub fn register(self: *EventManager, target: *EventTarget, typ: []const u8, call
}
const func = switch (callback) {
.function => |f| Function{ .value = f },
.function => |f| Function{ .value = try f.persist() },
.object => |o| Function{ .object = o },
};
@@ -368,7 +368,7 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
}
switch (listener.function) {
.value => |value| try value.callWithThis(void, current_target, .{event}),
.value => |value| try value.local().callWithThis(void, current_target, .{event}),
.string => |string| {
const str = try page.call_arena.dupeZ(u8, string.str());
try self.page.js.eval(str, null);
@@ -443,13 +443,13 @@ const Listener = struct {
};
const Function = union(enum) {
value: js.Function,
value: js.Function.Global,
string: String,
object: js.Object,
fn eqlFunction(self: Function, func: js.Function) bool {
return switch (self) {
.value => |v| return v.id() == func.id(),
.value => |v| v.isEqual(func),
else => false,
};
}

View File

@@ -562,18 +562,20 @@ fn _documentIsComplete(self: *Page) !void {
// this event is weird, it's dispatched directly on the window, but
// with the document as the target
event._target = self.document.asEventTarget();
const on_load = if (self.window._on_load) |*g| g.local() else null;
try self._event_manager.dispatchWithFunction(
self.window.asEventTarget(),
event,
self.window._on_load,
on_load,
.{ .inject_target = false, .context = "page load" },
);
const pageshow_event = try PageTransitionEvent.initTrusted("pageshow", .{}, self);
const on_pageshow = if (self.window._on_pageshow) |*g| g.local() else null;
try self._event_manager.dispatchWithFunction(
self.window.asEventTarget(),
pageshow_event.asEvent(),
self.window._on_pageshow,
on_pageshow,
.{ .context = "page show" },
);
}
@@ -1997,7 +1999,7 @@ pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const
defer self._upgrading_element = prev_upgrading;
var caught: JS.TryCatch.Caught = undefined;
_ = def.constructor.newInstance(&caught) catch |err| {
_ = def.constructor.local().newInstance(&caught) catch |err| {
log.warn(.js, "custom element constructor", .{ .name = name, .err = err, .caught = caught });
return node;
};

View File

@@ -844,8 +844,9 @@ pub const Script = struct {
self.executeCallback("error", script_element._on_error, page);
}
fn executeCallback(self: *const Script, comptime typ: []const u8, cb_: ?js.Function, page: *Page) void {
const cb = cb_ orelse return;
fn executeCallback(self: *const Script, comptime typ: []const u8, cb_: ?js.Function.Global, page: *Page) void {
const cb_global = cb_ orelse return;
const cb = cb_global.local();
const Event = @import("webapi/Event.zig");
const event = Event.initTrusted(typ, .{}, page) catch |err| {

View File

@@ -81,7 +81,7 @@ global_values: std.ArrayList(js.Global(js.Value)) = .empty,
global_objects: std.ArrayList(js.Global(js.Object)) = .empty,
global_modules: std.ArrayList(js.Global(js.Module)) = .empty,
global_promises: std.ArrayList(js.Global(js.Promise)) = .empty,
global_functions: std.ArrayList(js.Global(js.Function)) = .empty,
global_functions: std.ArrayList(v8.Global) = .empty,
global_promise_resolvers: std.ArrayList(js.Global(js.PromiseResolver)) = .empty,
// Our module cache: normalized module specifier => module.
@@ -154,7 +154,7 @@ pub fn deinit(self: *Context) void {
}
for (self.global_functions.items) |*global| {
global.deinit();
v8.v8__Global__Reset(global);
}
for (self.global_promises.items) |*global| {
@@ -452,6 +452,11 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp
return .{ .ctx = self, .handle = @ptrCast(value.handle) };
}
if (T == js.Function.Global) {
// Auto-convert Global to local for bridge
return .{ .ctx = self, .handle = @ptrCast(value.local().handle) };
}
if (T == js.Object) {
// we're returning a v8.Object
return .{ .ctx = self, .handle = @ptrCast(value.handle) };
@@ -778,6 +783,13 @@ fn jsValueToStruct(self: *Context, comptime T: type, js_value: js.Value) !?T {
}
return try self.newFunction(js_value);
},
js.Function.Global => {
if (!js_value.isFunction()) {
return null;
}
const func = try self.newFunction(js_value);
return try func.persist();
},
// zig fmt: off
js.TypedArray(u8), js.TypedArray(u16), js.TypedArray(u32), js.TypedArray(u64),
js.TypedArray(i8), js.TypedArray(i16), js.TypedArray(i32), js.TypedArray(i64),

View File

@@ -33,10 +33,6 @@ pub const Result = struct {
exception: []const u8,
};
pub fn id(self: *const Function) u32 {
return @as(u32, @bitCast(v8.v8__Object__GetIdentityHash(@ptrCast(self.handle))));
}
pub fn withThis(self: *const Function, value: anytype) !Function {
const this_obj = if (@TypeOf(value) == js.Object)
value.handle
@@ -172,20 +168,41 @@ pub fn getPropertyValue(self: *const Function, name: []const u8) !?js.Value {
};
}
pub fn persist(self: *const Function) !Function {
pub fn persist(self: *const Function) !Global {
var ctx = self.ctx;
const global = js.Global(Function).init(ctx.isolate.handle, self.handle);
var global: v8.Global = undefined;
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
try ctx.global_functions.append(ctx.arena, global);
return .{
.handle = global,
.ctx = ctx,
.this = self.this,
.handle = global.local(),
};
}
pub fn persistWithThis(self: *const Function, value: anytype) !Function {
var persisted = try self.persist();
return persisted.withThis(value);
pub fn persistWithThis(self: *const Function, value: anytype) !Global {
const with_this = try self.withThis(value);
return with_this.persist();
}
pub const Global = struct {
handle: v8.Global,
ctx: *js.Context,
pub fn deinit(self: *Global) void {
v8.v8__Global__Reset(&self.handle);
}
pub fn local(self: *const Global) Function {
return .{
.ctx = self.ctx,
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
};
}
pub fn isEqual(self: *const Global, other: Function) bool {
return v8.v8__Global__IsEqual(&self.handle, other.handle);
}
};

View File

@@ -29,7 +29,7 @@ const AbortSignal = @This();
_proto: *EventTarget,
_aborted: bool = false,
_reason: Reason = .undefined,
_on_abort: ?js.Function = null,
_on_abort: ?js.Function.Global = null,
pub fn init(page: *Page) !*AbortSignal {
return page._factory.eventTarget(AbortSignal{
@@ -45,16 +45,12 @@ pub fn getReason(self: *const AbortSignal) Reason {
return self._reason;
}
pub fn getOnAbort(self: *const AbortSignal) ?js.Function {
pub fn getOnAbort(self: *const AbortSignal) ?js.Function.Global {
return self._on_abort;
}
pub fn setOnAbort(self: *AbortSignal, cb_: ?js.Function) !void {
if (cb_) |cb| {
self._on_abort = try cb.persistWithThis(self);
} else {
self._on_abort = null;
}
pub fn setOnAbort(self: *AbortSignal, cb: ?js.Function.Global) !void {
self._on_abort = cb;
}
pub fn asEventTarget(self: *AbortSignal) *EventTarget {
@@ -81,10 +77,11 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void {
// Dispatch abort event
const event = try Event.initTrusted("abort", .{}, page);
const func = if (self._on_abort) |*g| g.local() else null;
try page._event_manager.dispatchWithFunction(
self.asEventTarget(),
event,
self._on_abort,
func,
.{ .context = "abort signal" },
);
}

View File

@@ -24,7 +24,7 @@ const Element = @import("Element.zig");
const CustomElementDefinition = @This();
name: []const u8,
constructor: js.Function,
constructor: js.Function.Global,
observed_attributes: std.StringHashMapUnmanaged(void) = .{},
// For customized built-in elements, this is the element tag they extend (e.g., .button)
// For autonomous custom elements, this is null

View File

@@ -110,7 +110,7 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu
}
}
pub fn get(self: *CustomElementRegistry, name: []const u8) ?js.Function {
pub fn get(self: *CustomElementRegistry, name: []const u8) ?js.Function.Global {
const definition = self._definitions.get(name) orelse return null;
return definition.constructor;
}
@@ -175,7 +175,7 @@ pub fn upgradeCustomElement(custom: *Custom, definition: *CustomElementDefinitio
defer page._upgrading_element = prev_upgrading;
var caught: js.TryCatch.Caught = undefined;
_ = definition.constructor.newInstance(&caught) catch |err| {
_ = definition.constructor.local().newInstance(&caught) catch |err| {
log.warn(.js, "custom element upgrade", .{ .name = definition.name, .err = err, .caught = caught });
return error.CustomElementUpgradeFailed;
};

View File

@@ -73,7 +73,7 @@ pub fn addEventListener(self: *EventTarget, typ: []const u8, callback_: ?EventLi
const em_callback = switch (callback) {
.object => |obj| EventManager.Callback{ .object = try obj.persist() },
.function => |func| EventManager.Callback{ .function = try func.persist() },
.function => |func| EventManager.Callback{ .function = func },
};
const options = blk: {

View File

@@ -81,10 +81,11 @@ fn goInner(delta: i32, page: *Page) !void {
if (try page.isSameOrigin(url)) {
const event = try PopStateEvent.initTrusted("popstate", .{ .state = entry._state.value }, page);
const func = if (page.window._on_popstate) |*g| g.local() else null;
try page._event_manager.dispatchWithFunction(
page.window.asEventTarget(),
event.asEvent(),
page.window._on_popstate,
func,
.{ .context = "Pop State" },
);
}

View File

@@ -32,7 +32,7 @@ pub fn registerTypes() []const type {
const IntersectionObserver = @This();
_callback: js.Function,
_callback: js.Function.Global,
_observing: std.ArrayList(*Element) = .{},
_root: ?*Element = null,
_root_margin: []const u8 = "0px",
@@ -59,7 +59,7 @@ pub const ObserverInit = struct {
};
};
pub fn init(callback: js.Function, options: ?ObserverInit, page: *Page) !*IntersectionObserver {
pub fn init(callback: js.Function.Global, options: ?ObserverInit, page: *Page) !*IntersectionObserver {
const opts = options orelse ObserverInit{};
const root_margin = if (opts.rootMargin) |rm| try page.arena.dupe(u8, rm) else "0px";
@@ -73,7 +73,7 @@ pub fn init(callback: js.Function, options: ?ObserverInit, page: *Page) !*Inters
};
return page._factory.create(IntersectionObserver{
._callback = try callback.persist(),
._callback = callback,
._root = opts.root,
._root_margin = root_margin,
._threshold = threshold,
@@ -246,7 +246,7 @@ pub fn deliverEntries(self: *IntersectionObserver, page: *Page) !void {
const entries = try self.takeRecords(page);
var caught: js.TryCatch.Caught = undefined;
self._callback.tryCall(void, .{ entries, self }, &caught) catch |err| {
self._callback.local().tryCall(void, .{ entries, self }, &caught) catch |err| {
log.err(.page, "IntsctObserver.deliverEntries", .{ .err = err, .caught = caught });
return err;
};

View File

@@ -29,8 +29,8 @@ const MessagePort = @This();
_proto: *EventTarget,
_enabled: bool = false,
_closed: bool = false,
_on_message: ?js.Function = null,
_on_message_error: ?js.Function = null,
_on_message: ?js.Function.Global = null,
_on_message_error: ?js.Function.Global = null,
_entangled_port: ?*MessagePort = null,
pub fn init(page: *Page) !*MessagePort {
@@ -88,28 +88,20 @@ pub fn close(self: *MessagePort) void {
self._entangled_port = null;
}
pub fn getOnMessage(self: *const MessagePort) ?js.Function {
pub fn getOnMessage(self: *const MessagePort) ?js.Function.Global {
return self._on_message;
}
pub fn setOnMessage(self: *MessagePort, cb_: ?js.Function) !void {
if (cb_) |cb| {
self._on_message = try cb.persist();
} else {
self._on_message = null;
}
pub fn setOnMessage(self: *MessagePort, cb: ?js.Function.Global) !void {
self._on_message = cb;
}
pub fn getOnMessageError(self: *const MessagePort) ?js.Function {
pub fn getOnMessageError(self: *const MessagePort) ?js.Function.Global {
return self._on_message_error;
}
pub fn setOnMessageError(self: *MessagePort, cb_: ?js.Function) !void {
if (cb_) |cb| {
self._on_message_error = try cb.persist();
} else {
self._on_message_error = null;
}
pub fn setOnMessageError(self: *MessagePort, cb: ?js.Function.Global) !void {
self._on_message_error = cb;
}
const PostMessageCallback = struct {
@@ -138,10 +130,11 @@ const PostMessageCallback = struct {
return null;
};
const func = if (self.port._on_message) |*g| g.local() else null;
self.page._event_manager.dispatchWithFunction(
self.port.asEventTarget(),
event.asEvent(),
self.port._on_message,
func,
.{ .context = "MessagePort message" },
) catch |err| {
log.err(.dom, "MessagePort.postMessage", .{ .err = err });

View File

@@ -32,7 +32,7 @@ pub fn registerTypes() []const type {
const MutationObserver = @This();
_callback: js.Function,
_callback: js.Function.Global,
_observing: std.ArrayList(Observing) = .{},
_pending_records: std.ArrayList(*MutationRecord) = .{},
/// Intrusively linked to next element (see Page.zig).
@@ -53,9 +53,9 @@ pub const ObserveOptions = struct {
attributeFilter: ?[]const []const u8 = null,
};
pub fn init(callback: js.Function, page: *Page) !*MutationObserver {
pub fn init(callback: js.Function.Global, page: *Page) !*MutationObserver {
return page._factory.create(MutationObserver{
._callback = try callback.persist(),
._callback = callback,
});
}
@@ -249,7 +249,7 @@ pub fn deliverRecords(self: *MutationObserver, page: *Page) !void {
// This ensures mutations triggered during the callback go into a fresh list
const records = try self.takeRecords(page);
var caught: js.TryCatch.Caught = undefined;
self._callback.tryCall(void, .{ records, self }, &caught) catch |err| {
self._callback.local().tryCall(void, .{ records, self }, &caught) catch |err| {
log.err(.page, "MutObserver.deliverRecords", .{ .err = err, .caught = caught });
return err;
};

View File

@@ -22,22 +22,22 @@ const Node = @import("Node.zig");
const NodeFilter = @This();
_func: ?js.Function,
_func: ?js.Function.Global,
_original_filter: ?FilterOpts,
pub const FilterOpts = union(enum) {
function: js.Function,
function: js.Function.Global,
object: struct {
pub const js_as_object = true;
acceptNode: js.Function,
acceptNode: js.Function.Global,
},
};
pub fn init(opts_: ?FilterOpts) !NodeFilter {
const opts = opts_ orelse return .{ ._func = null, ._original_filter = null };
const func = switch (opts) {
.function => |func| try func.persist(),
.object => |obj| try obj.acceptNode.persist(),
.function => |func| func,
.object => |obj| obj.acceptNode,
};
return .{
._func = func,
@@ -67,7 +67,7 @@ pub const SHOW_NOTATION: u32 = 0x800;
pub fn acceptNode(self: *const NodeFilter, node: *Node) !i32 {
const func = self._func orelse return FILTER_ACCEPT;
return func.call(i32, .{node});
return func.local().call(i32, .{node});
}
pub fn shouldShow(node: *const Node, what_to_show: u32) bool {

View File

@@ -32,7 +32,7 @@ pub fn registerTypes() []const type {
const PerformanceObserver = @This();
/// Emitted when there are events with same interests.
_callback: js.Function,
_callback: js.Function.Global,
/// The threshold to deliver `PerformanceEventTiming` entries.
_duration_threshold: f64,
/// Entry types we're looking for are encoded as bit flags.
@@ -44,9 +44,9 @@ _entries: std.ArrayList(*Performance.Entry),
const DefaultDurationThreshold: f64 = 104;
/// Creates a new PerformanceObserver object with the given observer callback.
pub fn init(callback: js.Function, page: *Page) !*PerformanceObserver {
pub fn init(callback: js.Function.Global, page: *Page) !*PerformanceObserver {
return page._factory.create(PerformanceObserver{
._callback = try callback.persist(),
._callback = callback,
._duration_threshold = DefaultDurationThreshold,
._interests = 0,
._entries = .{},
@@ -154,7 +154,7 @@ pub inline fn hasRecords(self: *const PerformanceObserver) bool {
pub fn dispatch(self: *PerformanceObserver, page: *Page) !void {
const records = try self.takeRecords(page);
var caught: js.TryCatch.Caught = undefined;
self._callback.tryCall(void, .{ EntryList{ ._entries = records }, self }, &caught) catch |err| {
self._callback.local().tryCall(void, .{ EntryList{ ._entries = records }, self }, &caught) catch |err| {
log.err(.page, "PerfObserver.dispatch", .{ .err = err, .caught = caught });
return err;
};

View File

@@ -54,11 +54,11 @@ _navigator: Navigator = .init,
_screen: *Screen,
_performance: Performance,
_storage_bucket: *storage.Bucket,
_on_load: ?js.Function = null,
_on_pageshow: ?js.Function = null,
_on_popstate: ?js.Function = null,
_on_error: ?js.Function = null, // TODO: invoke on error?
_on_unhandled_rejection: ?js.Function = null, // TODO: invoke on error
_on_load: ?js.Function.Global = null,
_on_pageshow: ?js.Function.Global = null,
_on_popstate: ?js.Function.Global = null,
_on_error: ?js.Function.Global = null, // TODO: invoke on error?
_on_unhandled_rejection: ?js.Function.Global = null, // TODO: invoke on error
_location: *Location,
_timer_id: u30 = 0,
_timers: std.AutoHashMapUnmanaged(u32, *ScheduleCallback) = .{},
@@ -145,51 +145,51 @@ pub fn getCustomElements(self: *Window) *CustomElementRegistry {
return &self._custom_elements;
}
pub fn getOnLoad(self: *const Window) ?js.Function {
pub fn getOnLoad(self: *const Window) ?js.Function.Global {
return self._on_load;
}
pub fn setOnLoad(self: *Window, setter: ?FunctionSetter) !void {
self._on_load = try getFunctionFromSetter(setter);
pub fn setOnLoad(self: *Window, setter: ?FunctionSetter) void {
self._on_load = getFunctionFromSetter(setter);
}
pub fn getOnPageShow(self: *const Window) ?js.Function {
pub fn getOnPageShow(self: *const Window) ?js.Function.Global {
return self._on_pageshow;
}
pub fn setOnPageShow(self: *Window, setter: ?FunctionSetter) !void {
self._on_pageshow = try getFunctionFromSetter(setter);
pub fn setOnPageShow(self: *Window, setter: ?FunctionSetter) void {
self._on_pageshow = getFunctionFromSetter(setter);
}
pub fn getOnPopState(self: *const Window) ?js.Function {
pub fn getOnPopState(self: *const Window) ?js.Function.Global {
return self._on_popstate;
}
pub fn setOnPopState(self: *Window, setter: ?FunctionSetter) !void {
self._on_popstate = try getFunctionFromSetter(setter);
pub fn setOnPopState(self: *Window, setter: ?FunctionSetter) void {
self._on_popstate = getFunctionFromSetter(setter);
}
pub fn getOnError(self: *const Window) ?js.Function {
pub fn getOnError(self: *const Window) ?js.Function.Global {
return self._on_error;
}
pub fn setOnError(self: *Window, setter: ?FunctionSetter) !void {
self._on_error = try getFunctionFromSetter(setter);
pub fn setOnError(self: *Window, setter: ?FunctionSetter) void {
self._on_error = getFunctionFromSetter(setter);
}
pub fn getOnUnhandledRejection(self: *const Window) ?js.Function {
pub fn getOnUnhandledRejection(self: *const Window) ?js.Function.Global {
return self._on_unhandled_rejection;
}
pub fn setOnUnhandledRejection(self: *Window, setter: ?FunctionSetter) !void {
self._on_unhandled_rejection = try getFunctionFromSetter(setter);
pub fn setOnUnhandledRejection(self: *Window, setter: ?FunctionSetter) void {
self._on_unhandled_rejection = getFunctionFromSetter(setter);
}
pub fn fetch(_: *const Window, input: Fetch.Input, options: ?Fetch.InitOpts, page: *Page) !js.Promise {
return Fetch.init(input, options, page);
}
pub fn setTimeout(self: *Window, cb: js.Function, delay_ms: ?u32, params: []js.Value, page: *Page) !u32 {
pub fn setTimeout(self: *Window, cb: js.Function.Global, delay_ms: ?u32, params: []js.Value, page: *Page) !u32 {
return self.scheduleCallback(cb, delay_ms orelse 0, .{
.repeat = false,
.params = params,
@@ -198,7 +198,7 @@ pub fn setTimeout(self: *Window, cb: js.Function, delay_ms: ?u32, params: []js.V
}, page);
}
pub fn setInterval(self: *Window, cb: js.Function, delay_ms: ?u32, params: []js.Value, page: *Page) !u32 {
pub fn setInterval(self: *Window, cb: js.Function.Global, delay_ms: ?u32, params: []js.Value, page: *Page) !u32 {
return self.scheduleCallback(cb, delay_ms orelse 0, .{
.repeat = true,
.params = params,
@@ -207,7 +207,7 @@ pub fn setInterval(self: *Window, cb: js.Function, delay_ms: ?u32, params: []js.
}, page);
}
pub fn setImmediate(self: *Window, cb: js.Function, params: []js.Value, page: *Page) !u32 {
pub fn setImmediate(self: *Window, cb: js.Function.Global, params: []js.Value, page: *Page) !u32 {
return self.scheduleCallback(cb, 0, .{
.repeat = false,
.params = params,
@@ -216,7 +216,7 @@ pub fn setImmediate(self: *Window, cb: js.Function, params: []js.Value, page: *P
}, page);
}
pub fn requestAnimationFrame(self: *Window, cb: js.Function, page: *Page) !u32 {
pub fn requestAnimationFrame(self: *Window, cb: js.Function.Global, page: *Page) !u32 {
return self.scheduleCallback(cb, 5, .{
.repeat = false,
.params = &.{},
@@ -253,7 +253,7 @@ pub fn cancelAnimationFrame(self: *Window, id: u32) void {
const RequestIdleCallbackOpts = struct {
timeout: ?u32 = null,
};
pub fn requestIdleCallback(self: *Window, cb: js.Function, opts_: ?RequestIdleCallbackOpts, page: *Page) !u32 {
pub fn requestIdleCallback(self: *Window, cb: js.Function.Global, opts_: ?RequestIdleCallbackOpts, page: *Page) !u32 {
const opts = opts_ orelse RequestIdleCallbackOpts{};
return self.scheduleCallback(cb, opts.timeout orelse 50, .{
.mode = .idle,
@@ -471,7 +471,7 @@ const ScheduleOpts = struct {
animation_frame: bool = false,
mode: ScheduleCallback.Mode = .normal,
};
fn scheduleCallback(self: *Window, cb: js.Function, delay_ms: u32, opts: ScheduleOpts, page: *Page) !u32 {
fn scheduleCallback(self: *Window, cb: js.Function.Global, delay_ms: u32, opts: ScheduleOpts, page: *Page) !u32 {
if (self._timers.count() > 512) {
// these are active
return error.TooManyTimeout;
@@ -497,7 +497,7 @@ fn scheduleCallback(self: *Window, cb: js.Function, delay_ms: u32, opts: Schedul
errdefer _ = self._timers.remove(timer_id);
const callback = try page._factory.create(ScheduleCallback{
.cb = try cb.persist(),
.cb = cb,
.page = page,
.mode = opts.mode,
.name = opts.name,
@@ -526,7 +526,7 @@ const ScheduleCallback = struct {
// delay, in ms, to repeat. When null, will be removed after the first time
repeat_ms: ?u32,
cb: js.Function,
cb: js.Function.Global,
page: *Page,
@@ -558,17 +558,17 @@ const ScheduleCallback = struct {
switch (self.mode) {
.idle => {
const IdleDeadline = @import("IdleDeadline.zig");
self.cb.call(void, .{IdleDeadline{}}) catch |err| {
self.cb.local().call(void, .{IdleDeadline{}}) catch |err| {
log.warn(.js, "window.idleCallback", .{ .name = self.name, .err = err });
};
},
.animation_frame => {
self.cb.call(void, .{page.window._performance.now()}) catch |err| {
self.cb.local().call(void, .{page.window._performance.now()}) catch |err| {
log.warn(.js, "window.RAF", .{ .name = self.name, .err = err });
};
},
.normal => {
self.cb.call(void, self.params) catch |err| {
self.cb.local().call(void, self.params) catch |err| {
log.warn(.js, "window.timer", .{ .name = self.name, .err = err });
};
},
@@ -615,17 +615,17 @@ const PostMessageCallback = struct {
};
const FunctionSetter = union(enum) {
func: js.Function,
func: js.Function.Global,
anything: js.Value,
};
// window.onload = {}; doesn't fail, but it doesn't do anything.
// seems like setting to null is ok (though, at least on Firefix, it preserves
// the original value, which we could do, but why?)
fn getFunctionFromSetter(setter_: ?FunctionSetter) !?js.Function {
fn getFunctionFromSetter(setter_: ?FunctionSetter) ?js.Function.Global {
const setter = setter_ orelse return null;
return switch (setter) {
.func => |func| try func.persist(),
.func => |func| func, // Already a Global from bridge auto-conversion
.anything => null,
};
}

View File

@@ -50,9 +50,10 @@ pub const Build = struct {
pub fn complete(node: *Node, page: *Page) !void {
const el = node.as(Element);
const on_load = el.getAttributeSafe("onload") orelse return;
page.window._on_load = page.js.stringToFunction(on_load) catch |err| blk: {
if (page.js.stringToFunction(on_load)) |func| {
page.window._on_load = try func.persist();
} else |err| {
log.err(.js, "body.onload", .{ .err = err, .str = on_load });
break :blk null;
};
}
}
};

View File

@@ -196,7 +196,7 @@ pub fn checkAndAttachBuiltIn(element: *Element, page: *Page) !void {
defer page._upgrading_element = prev_upgrading;
var caught: js.TryCatch.Caught = undefined;
_ = definition.constructor.newInstance(&caught) catch |err| {
_ = definition.constructor.local().newInstance(&caught) catch |err| {
log.warn(.js, "custom builtin ctor", .{ .name = is_value, .err = err, .caught = caught });
return;
};

View File

@@ -29,8 +29,8 @@ const Script = @This();
_proto: *HtmlElement,
_src: []const u8 = "",
_on_load: ?js.Function = null,
_on_error: ?js.Function = null,
_on_load: ?js.Function.Global = null,
_on_error: ?js.Function.Global = null,
_executed: bool = false,
pub fn asElement(self: *Script) *Element {
@@ -74,28 +74,20 @@ pub fn setNonce(self: *Script, value: []const u8, page: *Page) !void {
return self.asElement().setAttributeSafe("nonce", value, page);
}
pub fn getOnLoad(self: *const Script) ?js.Function {
pub fn getOnLoad(self: *const Script) ?js.Function.Global {
return self._on_load;
}
pub fn setOnLoad(self: *Script, cb_: ?js.Function) !void {
if (cb_) |cb| {
self._on_load = try cb.persist();
} else {
self._on_load = null;
}
pub fn setOnLoad(self: *Script, cb: ?js.Function.Global) void {
self._on_load = cb;
}
pub fn getOnError(self: *const Script) ?js.Function {
pub fn getOnError(self: *const Script) ?js.Function.Global {
return self._on_error;
}
pub fn setOnError(self: *Script, cb_: ?js.Function) !void {
if (cb_) |cb| {
self._on_error = try cb.persist();
} else {
self._on_error = null;
}
pub fn setOnError(self: *Script, cb: ?js.Function.Global) void {
self._on_error = cb;
}
pub fn getNoModule(self: *const Script) bool {
@@ -136,23 +128,19 @@ pub const Build = struct {
self._src = element.getAttributeSafe("src") orelse "";
if (element.getAttributeSafe("onload")) |on_load| {
self._on_load = blk: {
const func = page.js.stringToFunction(on_load) catch |err| {
log.err(.js, "script.onload", .{ .err = err, .str = on_load });
break :blk null;
};
break :blk try func.persist();
};
if (page.js.stringToFunction(on_load)) |func| {
self._on_load = try func.persist();
} else |err| {
log.err(.js, "script.onload", .{ .err = err, .str = on_load });
}
}
if (element.getAttributeSafe("onerror")) |on_error| {
self._on_error = blk: {
const func = page.js.stringToFunction(on_error) catch |err| {
log.err(.js, "script.onerror", .{ .err = err, .str = on_error });
break :blk null;
};
break :blk try func.persist();
};
if (page.js.stringToFunction(on_error)) |func| {
self._on_error = try func.persist();
} else |err| {
log.err(.js, "script.onerror", .{ .err = err, .str = on_error });
}
}
}
};

View File

@@ -30,8 +30,8 @@ _id: []const u8 = "",
_start_time: f64 = 0,
_end_time: f64 = 0,
_pause_on_exit: bool = false,
_on_enter: ?js.Function = null,
_on_exit: ?js.Function = null,
_on_enter: ?js.Function.Global = null,
_on_exit: ?js.Function.Global = null,
pub const Type = union(enum) {
vtt: *@import("VTTCue.zig"),
@@ -73,28 +73,20 @@ pub fn setPauseOnExit(self: *TextTrackCue, value: bool) void {
self._pause_on_exit = value;
}
pub fn getOnEnter(self: *const TextTrackCue) ?js.Function {
pub fn getOnEnter(self: *const TextTrackCue) ?js.Function.Global {
return self._on_enter;
}
pub fn setOnEnter(self: *TextTrackCue, cb_: ?js.Function) !void {
if (cb_) |cb| {
self._on_enter = try cb.persistWithThis(self);
} else {
self._on_enter = null;
}
pub fn setOnEnter(self: *TextTrackCue, cb: ?js.Function.Global) !void {
self._on_enter = cb;
}
pub fn getOnExit(self: *const TextTrackCue) ?js.Function {
pub fn getOnExit(self: *const TextTrackCue) ?js.Function.Global {
return self._on_exit;
}
pub fn setOnExit(self: *TextTrackCue, cb_: ?js.Function) !void {
if (cb_) |cb| {
self._on_exit = try cb.persistWithThis(self);
} else {
self._on_exit = null;
}
pub fn setOnExit(self: *TextTrackCue, cb: ?js.Function.Global) !void {
self._on_exit = cb;
}
pub const JsApi = struct {

View File

@@ -26,7 +26,7 @@ const NavigationCurrentEntryChangeEvent = @import("../event/NavigationCurrentEnt
pub const NavigationEventTarget = @This();
_proto: *EventTarget,
_on_currententrychange: ?js.Function = null,
_on_currententrychange: ?js.Function.Global = null,
pub fn asEventTarget(self: *NavigationEventTarget) *EventTarget {
return self._proto;
@@ -43,15 +43,16 @@ pub fn dispatch(self: *NavigationEventTarget, event_type: DispatchType, page: *P
};
};
const func = if (@field(self, field)) |*g| g.local() else null;
return page._event_manager.dispatchWithFunction(
self.asEventTarget(),
event,
@field(self, field),
func,
.{ .context = "Navigation" },
);
}
pub fn getOnCurrentEntryChange(self: *NavigationEventTarget) ?js.Function {
pub fn getOnCurrentEntryChange(self: *NavigationEventTarget) ?js.Function.Global {
return self._on_currententrychange;
}

View File

@@ -55,7 +55,7 @@ _response_headers: std.ArrayList([]const u8) = .empty,
_response_type: ResponseType = .text,
_ready_state: ReadyState = .unsent,
_on_ready_state_change: ?js.Function = null,
_on_ready_state_change: ?js.Function.Global = null,
const ReadyState = enum(u8) {
unsent = 0,
@@ -98,7 +98,7 @@ fn asEventTarget(self: *XMLHttpRequest) *EventTarget {
return self._proto._proto;
}
pub fn getOnReadyStateChange(self: *const XMLHttpRequest) ?js.Function {
pub fn getOnReadyStateChange(self: *const XMLHttpRequest) ?js.Function.Global {
return self._on_ready_state_change;
}
@@ -416,10 +416,11 @@ fn stateChanged(self: *XMLHttpRequest, state: ReadyState, page: *Page) !void {
self._ready_state = state;
const event = try Event.initTrusted("readystatechange", .{}, page);
const func = if (self._on_ready_state_change) |*g| g.local() else null;
try page._event_manager.dispatchWithFunction(
self.asEventTarget(),
event,
self._on_ready_state_change,
func,
.{ .context = "XHR state change" },
);
}

View File

@@ -26,13 +26,13 @@ const XMLHttpRequestEventTarget = @This();
_type: Type,
_proto: *EventTarget,
_on_abort: ?js.Function = null,
_on_error: ?js.Function = null,
_on_load: ?js.Function = null,
_on_load_end: ?js.Function = null,
_on_load_start: ?js.Function = null,
_on_progress: ?js.Function = null,
_on_timeout: ?js.Function = 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"),
@@ -63,15 +63,16 @@ pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchT
page,
);
const func = if (@field(self, field)) |*g| g.local() else null;
return page._event_manager.dispatchWithFunction(
self.asEventTarget(),
event.asEvent(),
@field(self, field),
func,
.{ .context = "XHR " ++ typ },
);
}
pub fn getOnAbort(self: *const XMLHttpRequestEventTarget) ?js.Function {
pub fn getOnAbort(self: *const XMLHttpRequestEventTarget) ?js.Function.Global {
return self._on_abort;
}
@@ -83,7 +84,7 @@ pub fn setOnAbort(self: *XMLHttpRequestEventTarget, cb_: ?js.Function) !void {
}
}
pub fn getOnError(self: *const XMLHttpRequestEventTarget) ?js.Function {
pub fn getOnError(self: *const XMLHttpRequestEventTarget) ?js.Function.Global {
return self._on_error;
}
@@ -95,7 +96,7 @@ pub fn setOnError(self: *XMLHttpRequestEventTarget, cb_: ?js.Function) !void {
}
}
pub fn getOnLoad(self: *const XMLHttpRequestEventTarget) ?js.Function {
pub fn getOnLoad(self: *const XMLHttpRequestEventTarget) ?js.Function.Global {
return self._on_load;
}
@@ -107,7 +108,7 @@ pub fn setOnLoad(self: *XMLHttpRequestEventTarget, cb_: ?js.Function) !void {
}
}
pub fn getOnLoadEnd(self: *const XMLHttpRequestEventTarget) ?js.Function {
pub fn getOnLoadEnd(self: *const XMLHttpRequestEventTarget) ?js.Function.Global {
return self._on_load_end;
}
@@ -119,7 +120,7 @@ pub fn setOnLoadEnd(self: *XMLHttpRequestEventTarget, cb_: ?js.Function) !void {
}
}
pub fn getOnLoadStart(self: *const XMLHttpRequestEventTarget) ?js.Function {
pub fn getOnLoadStart(self: *const XMLHttpRequestEventTarget) ?js.Function.Global {
return self._on_load_start;
}
@@ -131,7 +132,7 @@ pub fn setOnLoadStart(self: *XMLHttpRequestEventTarget, cb_: ?js.Function) !void
}
}
pub fn getOnProgress(self: *const XMLHttpRequestEventTarget) ?js.Function {
pub fn getOnProgress(self: *const XMLHttpRequestEventTarget) ?js.Function.Global {
return self._on_progress;
}
@@ -143,7 +144,7 @@ pub fn setOnProgress(self: *XMLHttpRequestEventTarget, cb_: ?js.Function) !void
}
}
pub fn getOnTimeout(self: *const XMLHttpRequestEventTarget) ?js.Function {
pub fn getOnTimeout(self: *const XMLHttpRequestEventTarget) ?js.Function.Global {
return self._on_timeout;
}

View File

@@ -43,15 +43,15 @@ _state: State,
_reader: ?*ReadableStreamDefaultReader,
_controller: *ReadableStreamDefaultController,
_stored_error: ?[]const u8,
_pull_fn: ?js.Function = null,
_pull_fn: ?js.Function.Global = null,
_pulling: bool = false,
_pull_again: bool = false,
_cancel: ?Cancel = null,
const UnderlyingSource = struct {
start: ?js.Function = null,
pull: ?js.Function = null,
cancel: ?js.Function = null,
pull: ?js.Function.Global = null,
cancel: ?js.Function.Global = null,
type: ?[]const u8 = null,
};
@@ -85,7 +85,7 @@ pub fn init(src_: ?UnderlyingSource, strategy_: ?QueueingStrategy, page: *Page)
}
if (src.pull) |pull| {
self._pull_fn = try pull.persist();
self._pull_fn = pull;
try self.callPullIfNeeded();
}
}
@@ -137,12 +137,12 @@ pub fn callPullIfNeeded(self: *ReadableStream) !void {
self._pulling = true;
const pull_fn = self._pull_fn orelse return;
const pull_fn = &(self._pull_fn orelse return);
// Call the pull function
// Note: In a complete implementation, we'd handle the promise returned by pull
// and set _pulling = false when it resolves
try pull_fn.call(void, .{self._controller});
try pull_fn.local().call(void, .{self._controller});
self._pulling = false;
@@ -186,11 +186,11 @@ pub fn cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !js.Promi
}
// Execute the cancel callback if provided
if (c.callback) |cb| {
if (c.callback) |*cb| {
if (reason) |r| {
try cb.call(void, .{r});
try cb.local().call(void, .{r});
} else {
try cb.call(void, .{});
try cb.local().call(void, .{});
}
}
@@ -211,7 +211,7 @@ pub fn cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !js.Promi
}
const Cancel = struct {
callback: ?js.Function = null,
callback: ?js.Function.Global = null,
reason: ?[]const u8 = null,
resolver: ?js.PromiseResolver = null,
};