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