From 5def997bed7ae7ecef81519278026a2ff71027d0 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 14 Jan 2026 15:12:22 +0800 Subject: [PATCH] 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. --- build.zig.zon | 10 +-- src/browser/EventManager.zig | 8 +-- src/browser/Page.zig | 8 ++- src/browser/ScriptManager.zig | 5 +- src/browser/js/Context.zig | 16 ++++- src/browser/js/Function.zig | 39 ++++++++--- src/browser/webapi/AbortSignal.zig | 15 ++-- .../webapi/CustomElementDefinition.zig | 2 +- src/browser/webapi/CustomElementRegistry.zig | 4 +- src/browser/webapi/EventTarget.zig | 2 +- src/browser/webapi/History.zig | 3 +- src/browser/webapi/IntersectionObserver.zig | 8 +-- src/browser/webapi/MessagePort.zig | 27 +++----- src/browser/webapi/MutationObserver.zig | 8 +-- src/browser/webapi/NodeFilter.zig | 12 ++-- src/browser/webapi/PerformanceObserver.zig | 8 +-- src/browser/webapi/Window.zig | 68 +++++++++---------- src/browser/webapi/element/html/Body.zig | 7 +- src/browser/webapi/element/html/Custom.zig | 2 +- src/browser/webapi/element/html/Script.zig | 48 +++++-------- src/browser/webapi/media/TextTrackCue.zig | 24 +++---- .../navigation/NavigationEventTarget.zig | 7 +- src/browser/webapi/net/XMLHttpRequest.zig | 7 +- .../webapi/net/XMLHttpRequestEventTarget.zig | 31 +++++---- src/browser/webapi/streams/ReadableStream.zig | 20 +++--- 25 files changed, 198 insertions(+), 191 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index a44dd0b4..774a31d2 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,11 +5,11 @@ .fingerprint = 0xda130f3af836cea0, // Changing this has security and trust implications. .minimum_zig_version = "0.15.2", .dependencies = .{ - .v8 = .{ - .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/5b0555e6b6154f957f9d7002ecb8005cc5a41b7a.tar.gz", - .hash = "v8-0.0.0-xddH6xUqBABofwwIBsof3cD3c2FstBvm7_VzoughX1Km", - }, - //.v8 = .{ .path = "../zig-v8-fork" }, + //.v8 = .{ + // .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/5b0555e6b6154f957f9d7002ecb8005cc5a41b7a.tar.gz", + // .hash = "v8-0.0.0-xddH6xUqBABofwwIBsof3cD3c2FstBvm7_VzoughX1Km", + //}, + .v8 = .{ .path = "../zig-v8-fork" }, .@"boringssl-zig" = .{ .url = "git+https://github.com/Syndica/boringssl-zig.git#c53df00d06b02b755ad88bbf4d1202ed9687b096", .hash = "boringssl-0.1.0-VtJeWehMAAA4RNnwRnzEvKcS9rjsR1QVRw1uJrwXxmVK", diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig index 6c188b83..8709f57f 100644 --- a/src/browser/EventManager.zig +++ b/src/browser/EventManager.zig @@ -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, }; } diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 913014cd..65e6ca0a 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -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; }; diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig index da95bc51..62507138 100644 --- a/src/browser/ScriptManager.zig +++ b/src/browser/ScriptManager.zig @@ -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| { diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 13c5b153..d0387abe 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -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), diff --git a/src/browser/js/Function.zig b/src/browser/js/Function.zig index f0ec8950..28e9b49c 100644 --- a/src/browser/js/Function.zig +++ b/src/browser/js/Function.zig @@ -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); + } +}; diff --git a/src/browser/webapi/AbortSignal.zig b/src/browser/webapi/AbortSignal.zig index f85afe23..85095d8c 100644 --- a/src/browser/webapi/AbortSignal.zig +++ b/src/browser/webapi/AbortSignal.zig @@ -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" }, ); } diff --git a/src/browser/webapi/CustomElementDefinition.zig b/src/browser/webapi/CustomElementDefinition.zig index 458a3319..41da64cb 100644 --- a/src/browser/webapi/CustomElementDefinition.zig +++ b/src/browser/webapi/CustomElementDefinition.zig @@ -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 diff --git a/src/browser/webapi/CustomElementRegistry.zig b/src/browser/webapi/CustomElementRegistry.zig index 3f0aa382..591dca01 100644 --- a/src/browser/webapi/CustomElementRegistry.zig +++ b/src/browser/webapi/CustomElementRegistry.zig @@ -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; }; diff --git a/src/browser/webapi/EventTarget.zig b/src/browser/webapi/EventTarget.zig index 1e6d806b..aba4521b 100644 --- a/src/browser/webapi/EventTarget.zig +++ b/src/browser/webapi/EventTarget.zig @@ -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: { diff --git a/src/browser/webapi/History.zig b/src/browser/webapi/History.zig index 124d57b0..41464753 100644 --- a/src/browser/webapi/History.zig +++ b/src/browser/webapi/History.zig @@ -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" }, ); } diff --git a/src/browser/webapi/IntersectionObserver.zig b/src/browser/webapi/IntersectionObserver.zig index ea7fecf5..115430fc 100644 --- a/src/browser/webapi/IntersectionObserver.zig +++ b/src/browser/webapi/IntersectionObserver.zig @@ -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; }; diff --git a/src/browser/webapi/MessagePort.zig b/src/browser/webapi/MessagePort.zig index 8d37441f..ff17ba3d 100644 --- a/src/browser/webapi/MessagePort.zig +++ b/src/browser/webapi/MessagePort.zig @@ -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 }); diff --git a/src/browser/webapi/MutationObserver.zig b/src/browser/webapi/MutationObserver.zig index 168b4dca..81220182 100644 --- a/src/browser/webapi/MutationObserver.zig +++ b/src/browser/webapi/MutationObserver.zig @@ -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; }; diff --git a/src/browser/webapi/NodeFilter.zig b/src/browser/webapi/NodeFilter.zig index a83cb5f2..db0c3bf6 100644 --- a/src/browser/webapi/NodeFilter.zig +++ b/src/browser/webapi/NodeFilter.zig @@ -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 { diff --git a/src/browser/webapi/PerformanceObserver.zig b/src/browser/webapi/PerformanceObserver.zig index a15c7f0d..28899e7e 100644 --- a/src/browser/webapi/PerformanceObserver.zig +++ b/src/browser/webapi/PerformanceObserver.zig @@ -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; }; diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index c26424f7..185cd08f 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -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, }; } diff --git a/src/browser/webapi/element/html/Body.zig b/src/browser/webapi/element/html/Body.zig index 5be6d4fe..6f35b16f 100644 --- a/src/browser/webapi/element/html/Body.zig +++ b/src/browser/webapi/element/html/Body.zig @@ -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; - }; + } } }; diff --git a/src/browser/webapi/element/html/Custom.zig b/src/browser/webapi/element/html/Custom.zig index 247f82f1..c814fe5e 100644 --- a/src/browser/webapi/element/html/Custom.zig +++ b/src/browser/webapi/element/html/Custom.zig @@ -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; }; diff --git a/src/browser/webapi/element/html/Script.zig b/src/browser/webapi/element/html/Script.zig index 8531d275..6b1d3823 100644 --- a/src/browser/webapi/element/html/Script.zig +++ b/src/browser/webapi/element/html/Script.zig @@ -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 }); + } } } }; diff --git a/src/browser/webapi/media/TextTrackCue.zig b/src/browser/webapi/media/TextTrackCue.zig index b82a95f1..3f757d19 100644 --- a/src/browser/webapi/media/TextTrackCue.zig +++ b/src/browser/webapi/media/TextTrackCue.zig @@ -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 { diff --git a/src/browser/webapi/navigation/NavigationEventTarget.zig b/src/browser/webapi/navigation/NavigationEventTarget.zig index 7bf5d858..c5d03d2a 100644 --- a/src/browser/webapi/navigation/NavigationEventTarget.zig +++ b/src/browser/webapi/navigation/NavigationEventTarget.zig @@ -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; } diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig index 954ad8fa..a5775ae5 100644 --- a/src/browser/webapi/net/XMLHttpRequest.zig +++ b/src/browser/webapi/net/XMLHttpRequest.zig @@ -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" }, ); } diff --git a/src/browser/webapi/net/XMLHttpRequestEventTarget.zig b/src/browser/webapi/net/XMLHttpRequestEventTarget.zig index 20bea353..a211d30e 100644 --- a/src/browser/webapi/net/XMLHttpRequestEventTarget.zig +++ b/src/browser/webapi/net/XMLHttpRequestEventTarget.zig @@ -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; } diff --git a/src/browser/webapi/streams/ReadableStream.zig b/src/browser/webapi/streams/ReadableStream.zig index f8edb594..6370e281 100644 --- a/src/browser/webapi/streams/ReadableStream.zig +++ b/src/browser/webapi/streams/ReadableStream.zig @@ -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, };