From 5def997bed7ae7ecef81519278026a2ff71027d0 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 14 Jan 2026 15:12:22 +0800 Subject: [PATCH 1/5] 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, }; From 728b2b7089d74be13872b3e9fadbdcc023b66404 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 14 Jan 2026 15:33:58 +0800 Subject: [PATCH 2/5] update v8 dep --- build.zig.zon | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 774a31d2..2f7c8400 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/global_equality.tar.gz", + .hash = "v8-0.0.0-xddH6-oqBACWs5PNjtmkdEgS4zzEU3XqGKUoLvudBpFW", + }, + // .v8 = .{ .path = "../zig-v8-fork" }, .@"boringssl-zig" = .{ .url = "git+https://github.com/Syndica/boringssl-zig.git#c53df00d06b02b755ad88bbf4d1202ed9687b096", .hash = "boringssl-0.1.0-VtJeWehMAAA4RNnwRnzEvKcS9rjsR1QVRw1uJrwXxmVK", From c7dbb6792d6ac441c764e1fcb276b51c5aad61c5 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 14 Jan 2026 17:30:29 +0800 Subject: [PATCH 3/5] Make js.Object and js.Value have explicit global See: bb06900b6f84abaccc7ecfd386af1a9dc0029c50 for details on this change. --- src/browser/EventManager.zig | 9 +++--- src/browser/js/Context.zig | 31 ++++++++++++++++--- src/browser/js/Object.zig | 28 +++++++++++++++-- src/browser/js/Value.zig | 28 +++++++++++++++-- src/browser/webapi/AbortController.zig | 4 +-- src/browser/webapi/AbortSignal.zig | 10 +++--- src/browser/webapi/Document.zig | 4 +-- src/browser/webapi/EventTarget.zig | 4 +-- src/browser/webapi/MessagePort.zig | 6 ++-- src/browser/webapi/Performance.zig | 9 +++--- src/browser/webapi/Window.zig | 27 +++++++--------- src/browser/webapi/animation/Animation.zig | 24 +++++--------- src/browser/webapi/event/CustomEvent.zig | 14 ++++----- src/browser/webapi/event/ErrorEvent.zig | 8 ++--- src/browser/webapi/event/MessageEvent.zig | 8 ++--- src/browser/webapi/media/VTTCue.zig | 12 +++---- src/browser/webapi/net/XMLHttpRequest.zig | 2 +- src/browser/webapi/streams/ReadableStream.zig | 2 +- 18 files changed, 141 insertions(+), 89 deletions(-) diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig index 8709f57f..0854bdce 100644 --- a/src/browser/EventManager.zig +++ b/src/browser/EventManager.zig @@ -103,7 +103,7 @@ pub fn register(self: *EventManager, target: *EventTarget, typ: []const u8, call const func = switch (callback) { .function => |f| Function{ .value = try f.persist() }, - .object => |o| Function{ .object = o }, + .object => |o| Function{ .object = try o.persist() }, }; const listener = try self.listener_pool.create(); @@ -373,7 +373,8 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe const str = try page.call_arena.dupeZ(u8, string.str()); try self.page.js.eval(str, null); }, - .object => |obj| { + .object => |*obj_global| { + const obj = obj_global.local(); if (try obj.getFunction("handleEvent")) |handleEvent| { try handleEvent.callWithThis(void, obj, .{event}); } @@ -445,7 +446,7 @@ const Listener = struct { const Function = union(enum) { value: js.Function.Global, string: String, - object: js.Object, + object: js.Object.Global, fn eqlFunction(self: Function, func: js.Function) bool { return switch (self) { @@ -456,7 +457,7 @@ const Function = union(enum) { fn eqlObject(self: Function, obj: js.Object) bool { return switch (self) { - .object => |o| return o.getId() == obj.getId(), + .object => |o| return o.isEqual(obj), else => false, }; } diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index d0387abe..f19fccda 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -77,8 +77,8 @@ identity_map: std.AutoHashMapUnmanaged(usize, js.Global(js.Object)) = .empty, // the @intFromPtr(js_obj.handle). But v8 can re-use address. Without // a reliable way to know if an object has already been persisted, // we now simply persist every time persist() is called. -global_values: std.ArrayList(js.Global(js.Value)) = .empty, -global_objects: std.ArrayList(js.Global(js.Object)) = .empty, +global_values: std.ArrayList(v8.Global) = .empty, +global_objects: std.ArrayList(v8.Global) = .empty, global_modules: std.ArrayList(js.Global(js.Module)) = .empty, global_promises: std.ArrayList(js.Global(js.Promise)) = .empty, global_functions: std.ArrayList(v8.Global) = .empty, @@ -142,11 +142,11 @@ pub fn deinit(self: *Context) void { } for (self.global_values.items) |*global| { - global.deinit(); + v8.v8__Global__Reset(global); } for (self.global_objects.items) |*global| { - global.deinit(); + v8.v8__Global__Reset(global); } for (self.global_modules.items) |*global| { @@ -462,6 +462,16 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp return .{ .ctx = self, .handle = @ptrCast(value.handle) }; } + if (T == js.Object.Global) { + // Auto-convert Global to local for bridge + return .{ .ctx = self, .handle = @ptrCast(value.local().handle) }; + } + + if (T == js.Value.Global) { + // Auto-convert Global to local for bridge + return .{ .ctx = self, .handle = @ptrCast(value.local().handle) }; + } + if (T == js.Value) { return value; } @@ -818,6 +828,19 @@ fn jsValueToStruct(self: *Context, comptime T: type, js_value: js.Value) !?T { .handle = @ptrCast(js_value.handle), }; }, + js.Object.Global => { + if (!js_value.isObject()) { + return null; + } + const obj = js.Object{ + .ctx = self, + .handle = @ptrCast(js_value.handle), + }; + return try obj.persist(); + }, + js.Value.Global => { + return try js_value.persist(); + }, else => { if (!js_value.isObject()) { return null; diff --git a/src/browser/js/Object.zig b/src/browser/js/Object.zig index 2ff64b2a..291de401 100644 --- a/src/browser/js/Object.zig +++ b/src/browser/js/Object.zig @@ -103,15 +103,17 @@ pub fn format(self: Object, writer: *std.Io.Writer) !void { return writer.writeAll(str); } -pub fn persist(self: Object) !Object { +pub fn persist(self: Object) !Global { var ctx = self.ctx; - const global = js.Global(Object).init(ctx.isolate.handle, self.handle); + var global: v8.Global = undefined; + v8.v8__Global__New(ctx.isolate.handle, self.handle, &global); + try ctx.global_objects.append(ctx.arena, global); return .{ + .handle = global, .ctx = ctx, - .handle = global.local(), }; } @@ -176,6 +178,26 @@ pub fn toZig(self: Object, comptime T: type) !T { return self.ctx.jsValueToZig(T, js_value); } +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) Object { + return .{ + .ctx = self.ctx, + .handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)), + }; + } + + pub fn isEqual(self: *const Global, other: Object) bool { + return v8.v8__Global__IsEqual(&self.handle, other.handle); + } +}; + pub const NameIterator = struct { count: u32, idx: u32 = 0, diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig index 53e7a70a..6f4c117a 100644 --- a/src/browser/js/Value.zig +++ b/src/browser/js/Value.zig @@ -243,15 +243,17 @@ pub fn fromJson(ctx: *js.Context, json: []const u8) !Value { return .{ .ctx = ctx, .handle = value.handle }; } -pub fn persist(self: Value) !Value { +pub fn persist(self: Value) !Global { var ctx = self.ctx; - const global = js.Global(Value).init(ctx.isolate.handle, self.handle); + var global: v8.Global = undefined; + v8.v8__Global__New(ctx.isolate.handle, self.handle, &global); + try ctx.global_values.append(ctx.arena, global); return .{ + .handle = global, .ctx = ctx, - .handle = global.local(), }; } @@ -298,3 +300,23 @@ pub fn format(self: Value, writer: *std.Io.Writer) !void { const str = self.toString(.{}) catch return error.WriteFailed; return writer.writeAll(str); } + +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) Value { + return .{ + .ctx = self.ctx, + .handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)), + }; + } + + pub fn isEqual(self: *const Global, other: Value) bool { + return v8.v8__Global__IsEqual(&self.handle, other.handle); + } +}; diff --git a/src/browser/webapi/AbortController.zig b/src/browser/webapi/AbortController.zig index e289d33b..d73de6b4 100644 --- a/src/browser/webapi/AbortController.zig +++ b/src/browser/webapi/AbortController.zig @@ -37,8 +37,8 @@ pub fn getSignal(self: *const AbortController) *AbortSignal { return self._signal; } -pub fn abort(self: *AbortController, reason_: ?js.Object, page: *Page) !void { - try self._signal.abort(if (reason_) |r| .{ .js_obj = r } else null, page); +pub fn abort(self: *AbortController, reason_: ?js.Value.Global, page: *Page) !void { + try self._signal.abort(if (reason_) |r| .{ .js_val = r } else null, page); } pub const JsApi = struct { diff --git a/src/browser/webapi/AbortSignal.zig b/src/browser/webapi/AbortSignal.zig index 85095d8c..9f3298aa 100644 --- a/src/browser/webapi/AbortSignal.zig +++ b/src/browser/webapi/AbortSignal.zig @@ -67,7 +67,7 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void { // Store the abort reason (default to a simple string if none provided) if (reason_) |reason| { switch (reason) { - .js_obj => |js_obj| self._reason = .{ .js_obj = try js_obj.persist() }, + .js_val => |js_val| self._reason = .{ .js_val = js_val }, .string => |str| self._reason = .{ .string = try page.dupeString(str) }, .undefined => self._reason = reason, } @@ -87,9 +87,9 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void { } // Static method to create an already-aborted signal -pub fn createAborted(reason_: ?js.Object, page: *Page) !*AbortSignal { +pub fn createAborted(reason_: ?js.Value.Global, page: *Page) !*AbortSignal { const signal = try init(page); - try signal.abort(if (reason_) |r| .{ .js_obj = r } else null, page); + try signal.abort(if (reason_) |r| .{ .js_val = r } else null, page); return signal; } @@ -115,7 +115,7 @@ pub fn throwIfAborted(self: *const AbortSignal, page: *Page) !ThrowIfAborted { if (self._aborted) { const exception = switch (self._reason) { .string => |str| page.js.throw(str), - .js_obj => |js_obj| page.js.throw(try js_obj.toString()), + .js_val => |js_val| page.js.throw(try js_val.local().toString(.{ .allocator = page.call_arena })), .undefined => page.js.throw("AbortError"), }; return .{ .exception = exception }; @@ -124,7 +124,7 @@ pub fn throwIfAborted(self: *const AbortSignal, page: *Page) !ThrowIfAborted { } const Reason = union(enum) { - js_obj: js.Object, + js_val: js.Value.Global, string: []const u8, undefined: void, }; diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index 70627924..57db61fd 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -54,7 +54,7 @@ _active_element: ?*Element = null, _style_sheets: ?*StyleSheetList = null, _write_insertion_point: ?*Node = null, _script_created_parser: ?Parser.Streaming = null, -_adopted_style_sheets: ?js.Object = null, +_adopted_style_sheets: ?js.Object.Global = null, pub const Type = union(enum) { generic, @@ -766,7 +766,7 @@ pub fn getChildElementCount(self: *Document) u32 { return i; } -pub fn getAdoptedStyleSheets(self: *Document, page: *Page) !js.Object { +pub fn getAdoptedStyleSheets(self: *Document, page: *Page) !js.Object.Global { if (self._adopted_style_sheets) |ass| { return ass; } diff --git a/src/browser/webapi/EventTarget.zig b/src/browser/webapi/EventTarget.zig index aba4521b..d1e6c67a 100644 --- a/src/browser/webapi/EventTarget.zig +++ b/src/browser/webapi/EventTarget.zig @@ -72,7 +72,7 @@ pub fn addEventListener(self: *EventTarget, typ: []const u8, callback_: ?EventLi const callback = callback_ orelse return; const em_callback = switch (callback) { - .object => |obj| EventManager.Callback{ .object = try obj.persist() }, + .object => |obj| EventManager.Callback{ .object = obj }, .function => |func| EventManager.Callback{ .function = func }, }; @@ -106,7 +106,7 @@ pub fn removeEventListener(self: *EventTarget, typ: []const u8, callback_: ?Even const em_callback = switch (callback) { .function => |func| EventManager.Callback{ .function = func }, - .object => |obj| EventManager.Callback{ .object = try obj.persist() }, + .object => |obj| EventManager.Callback{ .object = obj }, }; const use_capture = blk: { diff --git a/src/browser/webapi/MessagePort.zig b/src/browser/webapi/MessagePort.zig index ff17ba3d..f567199b 100644 --- a/src/browser/webapi/MessagePort.zig +++ b/src/browser/webapi/MessagePort.zig @@ -48,7 +48,7 @@ pub fn entangle(port1: *MessagePort, port2: *MessagePort) void { port2._entangled_port = port1; } -pub fn postMessage(self: *MessagePort, message: js.Value, page: *Page) !void { +pub fn postMessage(self: *MessagePort, message: js.Value.Global, page: *Page) !void { if (self._closed) { return; } @@ -62,7 +62,7 @@ pub fn postMessage(self: *MessagePort, message: js.Value, page: *Page) !void { const callback = try page._factory.create(PostMessageCallback{ .page = page, .port = other, - .message = try message.persist(), + .message = message, }); try page.scheduler.add(callback, PostMessageCallback.run, 0, .{ @@ -106,7 +106,7 @@ pub fn setOnMessageError(self: *MessagePort, cb: ?js.Function.Global) !void { const PostMessageCallback = struct { port: *MessagePort, - message: js.Value, + message: js.Value.Global, page: *Page, fn deinit(self: *PostMessageCallback) void { diff --git a/src/browser/webapi/Performance.zig b/src/browser/webapi/Performance.zig index bc4743cb..8f2a1bde 100644 --- a/src/browser/webapi/Performance.zig +++ b/src/browser/webapi/Performance.zig @@ -314,7 +314,7 @@ pub const Entry = struct { pub const Mark = struct { _proto: *Entry, - _detail: ?js.Value, + _detail: ?js.Value.Global, const Options = struct { detail: ?js.Value = null, @@ -344,7 +344,8 @@ pub const Mark = struct { return m; } - pub fn getDetail(self: *const Mark) ?js.Value { + + pub fn getDetail(self: *const Mark) ?js.Value.Global { return self._detail; } @@ -362,7 +363,7 @@ pub const Mark = struct { pub const Measure = struct { _proto: *Entry, - _detail: ?js.Object, + _detail: ?js.Object.Global, const Options = struct { detail: ?js.Object = null, @@ -405,7 +406,7 @@ pub const Measure = struct { return m; } - pub fn getDetail(self: *const Measure) ?js.Object { + pub fn getDetail(self: *const Measure) ?js.Object.Global { return self._detail; } diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 185cd08f..7b6938e8 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -189,7 +189,7 @@ pub fn fetch(_: *const Window, input: Fetch.Input, options: ?Fetch.InitOpts, pag return Fetch.init(input, options, page); } -pub fn setTimeout(self: *Window, cb: js.Function.Global, delay_ms: ?u32, params: []js.Value, page: *Page) !u32 { +pub fn setTimeout(self: *Window, cb: js.Function.Global, delay_ms: ?u32, params: []js.Value.Global, 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.Global, delay_ms: ?u32, params: }, page); } -pub fn setInterval(self: *Window, cb: js.Function.Global, delay_ms: ?u32, params: []js.Value, page: *Page) !u32 { +pub fn setInterval(self: *Window, cb: js.Function.Global, delay_ms: ?u32, params: []js.Value.Global, 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.Global, delay_ms: ?u32, params }, page); } -pub fn setImmediate(self: *Window, cb: js.Function.Global, params: []js.Value, page: *Page) !u32 { +pub fn setImmediate(self: *Window, cb: js.Function.Global, params: []js.Value.Global, page: *Page) !u32 { return self.scheduleCallback(cb, 0, .{ .repeat = false, .params = params, @@ -269,10 +269,10 @@ pub fn cancelIdleCallback(self: *Window, id: u32) void { sc.removed = true; } -pub fn reportError(self: *Window, err: js.Value, page: *Page) !void { +pub fn reportError(self: *Window, err: js.Value.Global, page: *Page) !void { const error_event = try ErrorEvent.initTrusted("error", .{ .@"error" = err, - .message = err.toString(.{}) catch "Unknown error", + .message = err.local().toString(.{}) catch "Unknown error", .bubbles = false, .cancelable = true, }, page); @@ -316,7 +316,7 @@ pub fn getIsSecureContext(_: *const Window) bool { return false; } -pub fn postMessage(self: *Window, message: js.Value, target_origin: ?[]const u8, page: *Page) !void { +pub fn postMessage(self: *Window, message: js.Value.Global, target_origin: ?[]const u8, page: *Page) !void { // For now, we ignore targetOrigin checking and just dispatch the message // In a full implementation, we would validate the origin _ = target_origin; @@ -325,7 +325,7 @@ pub fn postMessage(self: *Window, message: js.Value, target_origin: ?[]const u8, const origin = try self._location.getOrigin(page); const callback = try page._factory.create(PostMessageCallback{ .window = self, - .message = try message.persist(), + .message = message, .origin = try page.arena.dupe(u8, origin), .page = page, }); @@ -465,7 +465,7 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !void { const ScheduleOpts = struct { repeat: bool, - params: []js.Value, + params: []js.Value.Global, name: []const u8, low_priority: bool = false, animation_frame: bool = false, @@ -481,12 +481,9 @@ fn scheduleCallback(self: *Window, cb: js.Function.Global, delay_ms: u32, opts: self._timer_id = timer_id; const params = opts.params; - var persisted_params: []js.Value = &.{}; + var persisted_params: []js.Value.Global = &.{}; if (params.len > 0) { - persisted_params = try page.arena.alloc(js.Value, params.len); - for (params, persisted_params) |a, *ca| { - ca.* = try a.persist(); - } + persisted_params = try page.arena.dupe(js.Value.Global, params); } const gop = try self._timers.getOrPut(page.arena, timer_id); @@ -530,7 +527,7 @@ const ScheduleCallback = struct { page: *Page, - params: []const js.Value, + params: []const js.Value.Global, removed: bool = false, @@ -587,7 +584,7 @@ const ScheduleCallback = struct { const PostMessageCallback = struct { window: *Window, - message: js.Value, + message: js.Value.Global, origin: []const u8, page: *Page, diff --git a/src/browser/webapi/animation/Animation.zig b/src/browser/webapi/animation/Animation.zig index f9a7988f..b6167f7c 100644 --- a/src/browser/webapi/animation/Animation.zig +++ b/src/browser/webapi/animation/Animation.zig @@ -21,8 +21,8 @@ const Page = @import("../../Page.zig"); const Animation = @This(); -_effect: ?js.Object = null, -_timeline: ?js.Object = null, +_effect: ?js.Object.Global = null, +_timeline: ?js.Object.Global = null, _ready_resolver: ?js.PromiseResolver = null, _finished_resolver: ?js.PromiseResolver = null, @@ -62,28 +62,20 @@ pub fn getReady(self: *Animation, page: *Page) !js.Promise { return self._ready_resolver.?.promise(); } -pub fn getEffect(self: *const Animation) ?js.Object { +pub fn getEffect(self: *const Animation) ?js.Object.Global { return self._effect; } -pub fn setEffect(self: *Animation, effect: ?js.Object) !void { - if (effect) |e| { - self._effect = try e.persist(); - } else { - self._effect = null; - } +pub fn setEffect(self: *Animation, effect: ?js.Object.Global) !void { + self._effect = effect; } -pub fn getTimeline(self: *const Animation) ?js.Object { +pub fn getTimeline(self: *const Animation) ?js.Object.Global { return self._timeline; } -pub fn setTimeline(self: *Animation, timeline: ?js.Object) !void { - if (timeline) |t| { - self._timeline = try t.persist(); - } else { - self._timeline = null; - } +pub fn setTimeline(self: *Animation, timeline: ?js.Object.Global) !void { + self._timeline = timeline; } pub const JsApi = struct { diff --git a/src/browser/webapi/event/CustomEvent.zig b/src/browser/webapi/event/CustomEvent.zig index bdd6cc95..62aaabad 100644 --- a/src/browser/webapi/event/CustomEvent.zig +++ b/src/browser/webapi/event/CustomEvent.zig @@ -27,11 +27,11 @@ const Allocator = std.mem.Allocator; const CustomEvent = @This(); _proto: *Event, -_detail: ?js.Value = null, _arena: Allocator, +_detail: ?js.Value.Global = null, const CustomEventOptions = struct { - detail: ?js.Value = null, + detail: ?js.Value.Global = null, }; const Options = Event.inheritOptions(CustomEvent, CustomEventOptions); @@ -45,7 +45,7 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*CustomEvent { CustomEvent{ ._arena = arena, ._proto = undefined, - ._detail = if (opts.detail) |detail| try detail.persist() else null, + ._detail = opts.detail, }, ); @@ -58,7 +58,7 @@ pub fn initCustomEvent( event_string: []const u8, bubbles: ?bool, cancelable: ?bool, - detail_: ?js.Value, + detail_: ?js.Value.Global, page: *Page, ) !void { // This function can only be called after the constructor has called. @@ -67,16 +67,14 @@ pub fn initCustomEvent( self._proto._bubbles = bubbles orelse false; self._proto._cancelable = cancelable orelse false; // Detail is stored separately. - if (detail_) |detail| { - self._detail = try detail.persist(); - } + self._detail = detail_; } pub fn asEvent(self: *CustomEvent) *Event { return self._proto; } -pub fn getDetail(self: *const CustomEvent) ?js.Value { +pub fn getDetail(self: *const CustomEvent) ?js.Value.Global { return self._detail; } diff --git a/src/browser/webapi/event/ErrorEvent.zig b/src/browser/webapi/event/ErrorEvent.zig index d6ab71e9..296def3c 100644 --- a/src/browser/webapi/event/ErrorEvent.zig +++ b/src/browser/webapi/event/ErrorEvent.zig @@ -30,7 +30,7 @@ _message: []const u8 = "", _filename: []const u8 = "", _line_number: u32 = 0, _column_number: u32 = 0, -_error: ?js.Value = null, +_error: ?js.Value.Global = null, _arena: Allocator, pub const ErrorEventOptions = struct { @@ -38,7 +38,7 @@ pub const ErrorEventOptions = struct { filename: ?[]const u8 = null, lineno: u32 = 0, colno: u32 = 0, - @"error": ?js.Value = null, + @"error": ?js.Value.Global = null, }; const Options = Event.inheritOptions(ErrorEvent, ErrorEventOptions); @@ -64,7 +64,7 @@ fn initWithTrusted(typ: []const u8, opts_: ?Options, trusted: bool, page: *Page) ._filename = if (opts.filename) |str| try arena.dupe(u8, str) else "", ._line_number = opts.lineno, ._column_number = opts.colno, - ._error = if (opts.@"error") |err| try err.persist() else null, + ._error = opts.@"error", }, ); @@ -92,7 +92,7 @@ pub fn getColumnNumber(self: *const ErrorEvent) u32 { return self._column_number; } -pub fn getError(self: *const ErrorEvent) ?js.Value { +pub fn getError(self: *const ErrorEvent) ?js.Value.Global { return self._error; } diff --git a/src/browser/webapi/event/MessageEvent.zig b/src/browser/webapi/event/MessageEvent.zig index a763e301..bdf89fbd 100644 --- a/src/browser/webapi/event/MessageEvent.zig +++ b/src/browser/webapi/event/MessageEvent.zig @@ -25,12 +25,12 @@ const Window = @import("../Window.zig"); const MessageEvent = @This(); _proto: *Event, -_data: ?js.Value = null, +_data: ?js.Value.Global = null, _origin: []const u8 = "", _source: ?*Window = null, const MessageEventOptions = struct { - data: ?js.Value = null, + data: ?js.Value.Global = null, origin: ?[]const u8 = null, source: ?*Window = null, }; @@ -52,7 +52,7 @@ fn initWithTrusted(typ: []const u8, opts_: ?Options, trusted: bool, page: *Page) typ, MessageEvent{ ._proto = undefined, - ._data = if (opts.data) |d| try d.persist() else null, + ._data = opts.data, ._origin = if (opts.origin) |str| try page.arena.dupe(u8, str) else "", ._source = opts.source, }, @@ -66,7 +66,7 @@ pub fn asEvent(self: *MessageEvent) *Event { return self._proto; } -pub fn getData(self: *const MessageEvent) ?js.Value { +pub fn getData(self: *const MessageEvent) ?js.Value.Global { return self._data; } diff --git a/src/browser/webapi/media/VTTCue.zig b/src/browser/webapi/media/VTTCue.zig index de796a27..5235f907 100644 --- a/src/browser/webapi/media/VTTCue.zig +++ b/src/browser/webapi/media/VTTCue.zig @@ -26,7 +26,7 @@ const VTTCue = @This(); _proto: *TextTrackCue, _text: []const u8 = "", -_region: ?js.Object = null, +_region: ?js.Object.Global = null, _vertical: []const u8 = "", _snap_to_lines: bool = true, _line: ?f64 = null, // null represents "auto" @@ -65,16 +65,12 @@ pub fn setText(self: *VTTCue, value: []const u8, page: *Page) !void { self._text = try page.dupeString(value); } -pub fn getRegion(self: *const VTTCue) ?js.Object { +pub fn getRegion(self: *const VTTCue) ?js.Object.Global { return self._region; } -pub fn setRegion(self: *VTTCue, value: ?js.Object) !void { - if (value) |v| { - self._region = try v.persist(); - } else { - self._region = null; - } +pub fn setRegion(self: *VTTCue, value: ?js.Object.Global) !void { + self._region = value; } pub fn getVertical(self: *const VTTCue) []const u8 { diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig index a5775ae5..519adba4 100644 --- a/src/browser/webapi/net/XMLHttpRequest.zig +++ b/src/browser/webapi/net/XMLHttpRequest.zig @@ -67,7 +67,7 @@ const ReadyState = enum(u8) { const Response = union(ResponseType) { text: []const u8, - json: js.Value, + json: js.Value.Global, document: *Node.Document, }; diff --git a/src/browser/webapi/streams/ReadableStream.zig b/src/browser/webapi/streams/ReadableStream.zig index 6370e281..84025a6a 100644 --- a/src/browser/webapi/streams/ReadableStream.zig +++ b/src/browser/webapi/streams/ReadableStream.zig @@ -80,7 +80,7 @@ pub fn init(src_: ?UnderlyingSource, strategy_: ?QueueingStrategy, page: *Page) if (src.cancel) |callback| { self._cancel = .{ - .callback = try callback.persist(), + .callback = callback, }; } From 7c9941c62922b5af84846b5c77f57d166f583c2c Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 14 Jan 2026 20:22:48 +0800 Subject: [PATCH 4/5] Make Promise, PromiseResolver and Module have explicit globals. See bb06900b6f84abaccc7ecfd386af1a9dc0029c50 for an explanation. --- src/browser/js/Context.zig | 86 ++++++++++++------- src/browser/js/Module.zig | 29 +++++-- src/browser/js/Promise.zig | 33 +++++-- src/browser/js/PromiseResolver.zig | 29 +++++-- src/browser/webapi/CustomElementRegistry.zig | 8 +- src/browser/webapi/animation/Animation.zig | 10 +-- src/browser/webapi/navigation/Navigation.zig | 20 ++--- src/browser/webapi/navigation/root.zig | 2 +- src/browser/webapi/net/Fetch.zig | 8 +- src/browser/webapi/streams/ReadableStream.zig | 12 +-- .../ReadableStreamDefaultController.zig | 14 +-- 11 files changed, 169 insertions(+), 82 deletions(-) diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index f19fccda..2e9f6bed 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -79,10 +79,10 @@ identity_map: std.AutoHashMapUnmanaged(usize, js.Global(js.Object)) = .empty, // we now simply persist every time persist() is called. global_values: std.ArrayList(v8.Global) = .empty, global_objects: std.ArrayList(v8.Global) = .empty, -global_modules: std.ArrayList(js.Global(js.Module)) = .empty, -global_promises: std.ArrayList(js.Global(js.Promise)) = .empty, +global_modules: std.ArrayList(v8.Global) = .empty, +global_promises: std.ArrayList(v8.Global) = .empty, global_functions: std.ArrayList(v8.Global) = .empty, -global_promise_resolvers: std.ArrayList(js.Global(js.PromiseResolver)) = .empty, +global_promise_resolvers: std.ArrayList(v8.Global) = .empty, // Our module cache: normalized module specifier => module. module_cache: std.StringHashMapUnmanaged(ModuleEntry) = .empty, @@ -100,19 +100,19 @@ script_manager: ?*ScriptManager, const ModuleEntry = struct { // Can be null if we're asynchrously loading the module, in // which case resolver_promise cannot be null. - module: ?js.Module = null, + module: ?js.Module.Global = null, // The promise of the evaluating module. The resolved value is // meaningless to us, but the resolver promise needs to chain // to this, since we need to know when it's complete. - module_promise: ?js.Promise = null, + module_promise: ?js.Promise.Global = null, // The promise for the resolver which is loading the module. // (AKA, the first time we try to load it). This resolver will // chain to the module_promise and, when it's done evaluating // will resolve its namespace. Any other attempt to load the // module willchain to this. - resolver_promise: ?js.Promise = null, + resolver_promise: ?js.Promise.Global = null, }; pub fn fromC(c_context: *const v8.Context) *Context { @@ -150,7 +150,7 @@ pub fn deinit(self: *Context) void { } for (self.global_modules.items) |*global| { - global.deinit(); + v8.v8__Global__Reset(global); } for (self.global_functions.items) |*global| { @@ -158,11 +158,11 @@ pub fn deinit(self: *Context) void { } for (self.global_promises.items) |*global| { - global.deinit(); + v8.v8__Global__Reset(global); } for (self.global_promise_resolvers.items) |*global| { - global.deinit(); + v8.v8__Global__Reset(global); } if (self.handle_scope) |*scope| { @@ -472,6 +472,21 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp return .{ .ctx = self, .handle = @ptrCast(value.local().handle) }; } + if (T == js.Promise.Global) { + // Auto-convert Global to local for bridge + return .{ .ctx = self, .handle = @ptrCast(value.local().handle) }; + } + + if (T == js.PromiseResolver.Global) { + // Auto-convert Global to local for bridge + return .{ .ctx = self, .handle = @ptrCast(value.local().handle) }; + } + + if (T == js.Module.Global) { + // Auto-convert Global to local for bridge + return .{ .ctx = self, .handle = @ptrCast(value.local().handle) }; + } + if (T == js.Value) { return value; } @@ -841,6 +856,16 @@ fn jsValueToStruct(self: *Context, comptime T: type, js_value: js.Value) !?T { js.Value.Global => { return try js_value.persist(); }, + js.Promise.Global => { + if (!js_value.isPromise()) { + return null; + } + const promise = js.Promise{ + .ctx = self, + .handle = @ptrCast(js_value.handle), + }; + return try promise.persist(); + }, else => { if (!js_value.isObject()) { return null; @@ -1286,7 +1311,7 @@ fn _resolveModuleCallback(self: *Context, referrer: js.Module, specifier: [:0]co const entry = self.module_cache.getPtr(normalized_specifier).?; if (entry.module) |m| { - return m.handle; + return m.local().handle; } var source = try self.script_manager.?.waitForImport(normalized_specifier); @@ -1299,7 +1324,7 @@ fn _resolveModuleCallback(self: *Context, referrer: js.Module, specifier: [:0]co const mod = try self.compileModule(source.src(), normalized_specifier); try self.postCompileModule(mod, normalized_specifier); entry.module = try mod.persist(); - return entry.module.?.handle; + return entry.module.?.local().handle; } // Will get passed to ScriptManager and then passed back to us when @@ -1307,11 +1332,11 @@ fn _resolveModuleCallback(self: *Context, referrer: js.Module, specifier: [:0]co const DynamicModuleResolveState = struct { // The module that we're resolving (we'll actually resolve its // namespace) - module: ?js.Module, + module: ?js.Module.Global, context_id: usize, context: *Context, specifier: [:0]const u8, - resolver: js.PromiseResolver, + resolver: js.PromiseResolver.Global, }; fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []const u8) !js.Promise { @@ -1320,7 +1345,7 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c // This is easy, there's already something responsible // for loading the module. Maybe it's still loading, maybe // it's complete. Whatever, we can just return that promise. - return gop.value_ptr.resolver_promise.?; + return gop.value_ptr.resolver_promise.?.local(); } const resolver = try self.createPromiseResolver().persist(); @@ -1334,7 +1359,7 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c .resolver = resolver, }; - const promise = try resolver.promise().persist(); + const promise = try resolver.local().promise().persist(); if (!gop.found_existing) { // this module hasn't been seen before. This is the most @@ -1352,13 +1377,13 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c // Next, we need to actually load it. self.script_manager.?.getAsyncImport(specifier, dynamicModuleSourceCallback, state, referrer) catch |err| { const error_msg = self.newString(@errorName(err)); - _ = resolver.reject("dynamic module get async", error_msg); + _ = resolver.local().reject("dynamic module get async", error_msg); }; // For now, we're done. but this will be continued in // `dynamicModuleSourceCallback`, once the source for the // moduel is loaded. - return promise; + return promise.local(); } // So we have a module, but no async resolver. This can only @@ -1374,20 +1399,21 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c // If the module hasn't been evaluated yet (it was only instantiated // as a static import dependency), we need to evaluate it now. if (gop.value_ptr.module_promise == null) { - const mod = gop.value_ptr.module.?; + const mod_global = gop.value_ptr.module.?; + const mod = mod_global.local(); const status = mod.getStatus(); if (status == .kEvaluated or status == .kEvaluating) { // Module was already evaluated (shouldn't normally happen, but handle it). // Create a pre-resolved promise with the module namespace. const module_resolver = try self.createPromiseResolver().persist(); - _ = module_resolver.resolve("resolve module", mod.getModuleNamespace()); - gop.value_ptr.module_promise = try module_resolver.promise().persist(); + _ = module_resolver.local().resolve("resolve module", mod.getModuleNamespace()); + gop.value_ptr.module_promise = try module_resolver.local().promise().persist(); } else { // the module was loaded, but not evaluated, we _have_ to evaluate it now const evaluated = mod.evaluate() catch { std.debug.assert(status == .kErrored); - _ = resolver.reject("module evaluation", self.newString("Module evaluation failed")); - return promise; + _ = resolver.local().reject("module evaluation", self.newString("Module evaluation failed")); + return promise.local(); }; std.debug.assert(evaluated.isPromise()); gop.value_ptr.module_promise = try evaluated.toPromise().persist(); @@ -1402,7 +1428,7 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c // But we can skip direclty to `resolveDynamicModule` which is // what the above callback will eventually do. self.resolveDynamicModule(state, gop.value_ptr.*); - return promise; + return promise.local(); } fn dynamicModuleSourceCallback(ctx: *anyopaque, module_source_: anyerror!ScriptManager.ModuleSource) void { @@ -1410,7 +1436,7 @@ fn dynamicModuleSourceCallback(ctx: *anyopaque, module_source_: anyerror!ScriptM var self = state.context; var ms = module_source_ catch |err| { - _ = state.resolver.reject("dynamic module source", self.newString(@errorName(err))); + _ = state.resolver.local().reject("dynamic module source", self.newString(@errorName(err))); return; }; @@ -1427,7 +1453,7 @@ fn dynamicModuleSourceCallback(ctx: *anyopaque, module_source_: anyerror!ScriptM .caught = caught, .specifier = state.specifier, }); - _ = state.resolver.reject("dynamic compilation failure", self.newString(caught.exception orelse "")); + _ = state.resolver.local().reject("dynamic compilation failure", self.newString(caught.exception orelse "")); return; }; }; @@ -1471,8 +1497,8 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul } defer caller.context.runMicrotasks(); - const namespace = s.module.?.getModuleNamespace(); - _ = s.resolver.resolve("resolve namespace", namespace); + const namespace = s.module.?.local().getModuleNamespace(); + _ = s.resolver.local().resolve("resolve namespace", namespace); } }.callback, @ptrCast(state)); @@ -1491,19 +1517,19 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul } defer ctx.runMicrotasks(); - _ = s.resolver.reject("catch callback", js.Value{ + _ = s.resolver.local().reject("catch callback", js.Value{ .ctx = ctx, .handle = v8.v8__FunctionCallbackInfo__Data(callback_handle).?, }); } }.callback, @ptrCast(state)); - _ = module_entry.module_promise.?.thenAndCatch(then_callback, catch_callback) catch |err| { + _ = module_entry.module_promise.?.local().thenAndCatch(then_callback, catch_callback) catch |err| { log.err(.js, "module evaluation is promise", .{ .err = err, .specifier = state.specifier, }); - _ = state.resolver.reject("module promise", self.newString("Failed to evaluate promise")); + _ = state.resolver.local().reject("module promise", self.newString("Failed to evaluate promise")); }; } diff --git a/src/browser/js/Module.zig b/src/browser/js/Module.zig index ef8dec57..44d14b43 100644 --- a/src/browser/js/Module.zig +++ b/src/browser/js/Module.zig @@ -89,18 +89,37 @@ pub fn getScriptId(self: Module) u32 { return @intCast(v8.v8__Module__ScriptId(self.handle)); } -pub fn persist(self: Module) !Module { +pub fn persist(self: Module) !Global { var ctx = self.ctx; - - const global = js.Global(Module).init(ctx.isolate.handle, self.handle); + var global: v8.Global = undefined; + v8.v8__Global__New(ctx.isolate.handle, self.handle, &global); try ctx.global_modules.append(ctx.arena, global); - return .{ + .handle = global, .ctx = ctx, - .handle = global.local(), }; } +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) Module { + return .{ + .ctx = self.ctx, + .handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)), + }; + } + + pub fn isEqual(self: *const Global, other: Module) bool { + return v8.v8__Global__IsEqual(&self.handle, other.handle); + } +}; + const Requests = struct { ctx: *const v8.Context, handle: *const v8.FixedArray, diff --git a/src/browser/js/Promise.zig b/src/browser/js/Promise.zig index f520a9d6..437b327a 100644 --- a/src/browser/js/Promise.zig +++ b/src/browser/js/Promise.zig @@ -47,14 +47,37 @@ pub fn thenAndCatch(self: Promise, on_fulfilled: js.Function, on_rejected: js.Fu } return error.PromiseChainFailed; } -pub fn persist(self: Promise) !Promise { +pub fn persist(self: Promise) !Global { var ctx = self.ctx; - - const global = js.Global(Promise).init(ctx.isolate.handle, self.handle); + var global: v8.Global = undefined; + v8.v8__Global__New(ctx.isolate.handle, self.handle, &global); try ctx.global_promises.append(ctx.arena, global); - return .{ + .handle = global, .ctx = ctx, - .handle = global.local(), }; } + +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) Promise { + return .{ + .ctx = self.ctx, + .handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)), + }; + } + + pub fn isEqual(self: *const Global, other: Promise) bool { + return v8.v8__Global__IsEqual(&self.handle, other.handle); + } + + pub fn promise(self: *const Global) Promise { + return self.local(); + } +}; diff --git a/src/browser/js/PromiseResolver.zig b/src/browser/js/PromiseResolver.zig index a12d3a26..86f11cac 100644 --- a/src/browser/js/PromiseResolver.zig +++ b/src/browser/js/PromiseResolver.zig @@ -75,14 +75,33 @@ fn _reject(self: PromiseResolver, value: anytype) !void { ctx.runMicrotasks(); } -pub fn persist(self: PromiseResolver) !PromiseResolver { +pub fn persist(self: PromiseResolver) !Global { var ctx = self.ctx; - - const global = js.Global(PromiseResolver).init(ctx.isolate.handle, self.handle); + var global: v8.Global = undefined; + v8.v8__Global__New(ctx.isolate.handle, self.handle, &global); try ctx.global_promise_resolvers.append(ctx.arena, global); - return .{ + .handle = global, .ctx = ctx, - .handle = global.local(), }; } + +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) PromiseResolver { + return .{ + .ctx = self.ctx, + .handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)), + }; + } + + pub fn isEqual(self: *const Global, other: PromiseResolver) bool { + return v8.v8__Global__IsEqual(&self.handle, other.handle); + } +}; diff --git a/src/browser/webapi/CustomElementRegistry.zig b/src/browser/webapi/CustomElementRegistry.zig index 591dca01..956dd8bf 100644 --- a/src/browser/webapi/CustomElementRegistry.zig +++ b/src/browser/webapi/CustomElementRegistry.zig @@ -30,7 +30,7 @@ const CustomElementDefinition = @import("CustomElementDefinition.zig"); const CustomElementRegistry = @This(); _definitions: std.StringHashMapUnmanaged(*CustomElementDefinition) = .{}, -_when_defined: std.StringHashMapUnmanaged(js.PromiseResolver) = .{}, +_when_defined: std.StringHashMapUnmanaged(js.PromiseResolver.Global) = .{}, const DefineOptions = struct { extends: ?[]const u8 = null, @@ -106,7 +106,7 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu } if (self._when_defined.fetchRemove(name)) |entry| { - entry.value.resolve("whenDefined", constructor); + entry.value.local().resolve("whenDefined", constructor); } } @@ -126,7 +126,7 @@ pub fn whenDefined(self: *CustomElementRegistry, name: []const u8, page: *Page) const gop = try self._when_defined.getOrPut(page.arena, name); if (gop.found_existing) { - return gop.value_ptr.promise(); + return gop.value_ptr.local().promise(); } errdefer _ = self._when_defined.remove(name); const owned_name = try page.dupeString(name); @@ -135,7 +135,7 @@ pub fn whenDefined(self: *CustomElementRegistry, name: []const u8, page: *Page) gop.key_ptr.* = owned_name; gop.value_ptr.* = resolver; - return resolver.promise(); + return resolver.local().promise(); } fn upgradeNode(self: *CustomElementRegistry, node: *Node, page: *Page) !void { diff --git a/src/browser/webapi/animation/Animation.zig b/src/browser/webapi/animation/Animation.zig index b6167f7c..8fc6c99a 100644 --- a/src/browser/webapi/animation/Animation.zig +++ b/src/browser/webapi/animation/Animation.zig @@ -23,8 +23,8 @@ const Animation = @This(); _effect: ?js.Object.Global = null, _timeline: ?js.Object.Global = null, -_ready_resolver: ?js.PromiseResolver = null, -_finished_resolver: ?js.PromiseResolver = null, +_ready_resolver: ?js.PromiseResolver.Global = null, +_finished_resolver: ?js.PromiseResolver.Global = null, pub fn init(page: *Page) !*Animation { return page._factory.create(Animation{}); @@ -47,10 +47,10 @@ pub fn getPending(_: *const Animation) bool { pub fn getFinished(self: *Animation, page: *Page) !js.Promise { if (self._finished_resolver == null) { const resolver = try page.js.createPromiseResolver().persist(); - resolver.resolve("Animation.getFinished", self); + resolver.local().resolve("Animation.getFinished", self); self._finished_resolver = resolver; } - return self._finished_resolver.?.promise(); + return self._finished_resolver.?.local().promise(); } pub fn getReady(self: *Animation, page: *Page) !js.Promise { @@ -59,7 +59,7 @@ pub fn getReady(self: *Animation, page: *Page) !js.Promise { const resolver = try page.js.createPromiseResolver().persist(); self._ready_resolver = resolver; } - return self._ready_resolver.?.promise(); + return self._ready_resolver.?.local().promise(); } pub fn getEffect(self: *const Animation) ?js.Object.Global { diff --git a/src/browser/webapi/navigation/Navigation.zig b/src/browser/webapi/navigation/Navigation.zig index c257d1d2..d2fabff1 100644 --- a/src/browser/webapi/navigation/Navigation.zig +++ b/src/browser/webapi/navigation/Navigation.zig @@ -92,8 +92,8 @@ pub fn getTransition(_: *const Navigation) ?NavigationTransition { } const NavigationReturn = struct { - committed: js.Promise, - finished: js.Promise, + committed: js.Promise.Global, + finished: js.Promise.Global, }; pub fn back(self: *Navigation, page: *Page) !NavigationReturn { @@ -278,9 +278,9 @@ pub fn navigateInner( if (is_same_document) { page.url = new_url; - committed.resolve("navigation push", {}); + committed.local().resolve("navigation push", {}); // todo: Fire navigate event - finished.resolve("navigation push", {}); + finished.local().resolve("navigation push", {}); _ = try self.pushEntry(url, .{ .source = .navigation, .value = state }, page, true); } else { @@ -291,9 +291,9 @@ pub fn navigateInner( if (is_same_document) { page.url = new_url; - committed.resolve("navigation replace", {}); + committed.local().resolve("navigation replace", {}); // todo: Fire navigate event - finished.resolve("navigation replace", {}); + finished.local().resolve("navigation replace", {}); _ = try self.replaceEntry(url, .{ .source = .navigation, .value = state }, page, true); } else { @@ -306,9 +306,9 @@ pub fn navigateInner( if (is_same_document) { page.url = new_url; - committed.resolve("navigation traverse", {}); + committed.local().resolve("navigation traverse", {}); // todo: Fire navigate event - finished.resolve("navigation traverse", {}); + finished.local().resolve("navigation traverse", {}); } else { try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .script); } @@ -327,8 +327,8 @@ pub fn navigateInner( try self._proto.dispatch(.{ .currententrychange = event }, page); return .{ - .committed = committed.promise(), - .finished = finished.promise(), + .committed = try committed.local().promise().persist(), + .finished = try finished.local().promise().persist(), }; } diff --git a/src/browser/webapi/navigation/root.zig b/src/browser/webapi/navigation/root.zig index e007c714..26c963ac 100644 --- a/src/browser/webapi/navigation/root.zig +++ b/src/browser/webapi/navigation/root.zig @@ -47,7 +47,7 @@ pub const NavigationState = struct { // https://developer.mozilla.org/en-US/docs/Web/API/NavigationTransition pub const NavigationTransition = struct { - finished: js.Promise, + finished: js.Promise.Global, from: NavigationHistoryEntry, navigation_type: NavigationType, }; diff --git a/src/browser/webapi/net/Fetch.zig b/src/browser/webapi/net/Fetch.zig index 7a2d7f33..688f3a26 100644 --- a/src/browser/webapi/net/Fetch.zig +++ b/src/browser/webapi/net/Fetch.zig @@ -36,7 +36,7 @@ _page: *Page, _url: []const u8, _buf: std.ArrayList(u8), _response: *Response, -_resolver: js.PromiseResolver, +_resolver: js.PromiseResolver.Global, pub const Input = Request.Input; pub const InitOpts = Request.InitOpts; @@ -77,7 +77,7 @@ pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise { .done_callback = httpDoneCallback, .error_callback = httpErrorCallback, }); - return fetch._resolver.promise(); + return fetch._resolver.local().promise(); } pub fn deinit(self: *Fetch) void { @@ -149,13 +149,13 @@ fn httpDoneCallback(ctx: *anyopaque) !void { .len = self._buf.items.len, }); - return self._resolver.resolve("fetch done", self._response); + return self._resolver.local().resolve("fetch done", self._response); } fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void { const self: *Fetch = @ptrCast(@alignCast(ctx)); self._response._type = .@"error"; // Set type to error for network failures - self._resolver.reject("fetch error", @errorName(err)); + self._resolver.local().reject("fetch error", @errorName(err)); } const testing = @import("../../../testing.zig"); diff --git a/src/browser/webapi/streams/ReadableStream.zig b/src/browser/webapi/streams/ReadableStream.zig index 84025a6a..af50d8bc 100644 --- a/src/browser/webapi/streams/ReadableStream.zig +++ b/src/browser/webapi/streams/ReadableStream.zig @@ -170,7 +170,7 @@ pub fn cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !js.Promi if (self._state != .readable) { if (self._cancel) |c| { if (c.resolver) |r| { - return r.promise(); + return r.local().promise(); } } return page.js.resolvePromise(.{}); @@ -201,19 +201,19 @@ pub fn cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !js.Promi .done = true, .value = .empty, }; - for (self._controller._pending_reads.items) |resolver| { - resolver.resolve("stream cancelled", result); + for (self._controller._pending_reads.items) |*resolver| { + resolver.local().resolve("stream cancelled", result); } self._controller._pending_reads.clearRetainingCapacity(); - c.resolver.?.resolve("ReadableStream.cancel", {}); - return c.resolver.?.promise(); + c.resolver.?.local().resolve("ReadableStream.cancel", {}); + return c.resolver.?.local().promise(); } const Cancel = struct { callback: ?js.Function.Global = null, reason: ?[]const u8 = null, - resolver: ?js.PromiseResolver = null, + resolver: ?js.PromiseResolver.Global = null, }; pub const JsApi = struct { diff --git a/src/browser/webapi/streams/ReadableStreamDefaultController.zig b/src/browser/webapi/streams/ReadableStreamDefaultController.zig index 72956f06..da23f77e 100644 --- a/src/browser/webapi/streams/ReadableStreamDefaultController.zig +++ b/src/browser/webapi/streams/ReadableStreamDefaultController.zig @@ -42,7 +42,7 @@ _page: *Page, _stream: *ReadableStream, _arena: std.mem.Allocator, _queue: std.ArrayList(Chunk), -_pending_reads: std.ArrayList(js.PromiseResolver), +_pending_reads: std.ArrayList(js.PromiseResolver.Global), _high_water_mark: u32, pub fn init(stream: *ReadableStream, high_water_mark: u32, page: *Page) !*ReadableStreamDefaultController { @@ -59,7 +59,7 @@ pub fn init(stream: *ReadableStream, high_water_mark: u32, page: *Page) !*Readab pub fn addPendingRead(self: *ReadableStreamDefaultController, page: *Page) !js.Promise { const resolver = try page.js.createPromiseResolver().persist(); try self._pending_reads.append(self._arena, resolver); - return resolver.promise(); + return resolver.local().promise(); } pub fn enqueue(self: *ReadableStreamDefaultController, chunk: Chunk) !void { @@ -79,7 +79,7 @@ pub fn enqueue(self: *ReadableStreamDefaultController, chunk: Chunk) !void { .done = false, .value = .fromChunk(chunk), }; - resolver.resolve("stream enqueue", result); + resolver.local().resolve("stream enqueue", result); } pub fn close(self: *ReadableStreamDefaultController) !void { @@ -94,8 +94,8 @@ pub fn close(self: *ReadableStreamDefaultController) !void { .done = true, .value = .empty, }; - for (self._pending_reads.items) |resolver| { - resolver.resolve("stream close", result); + for (self._pending_reads.items) |*resolver| { + resolver.local().resolve("stream close", result); } self._pending_reads.clearRetainingCapacity(); } @@ -109,8 +109,8 @@ pub fn doError(self: *ReadableStreamDefaultController, err: []const u8) !void { self._stream._stored_error = try self._page.arena.dupe(u8, err); // Reject all pending reads - for (self._pending_reads.items) |resolver| { - resolver.reject("stream errror", err); + for (self._pending_reads.items) |*resolver| { + resolver.local().reject("stream errror", err); } self._pending_reads.clearRetainingCapacity(); } From c5870353e384a999eb7b9dd051b8080535dfdac3 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Mon, 19 Jan 2026 07:17:45 +0800 Subject: [PATCH 5/5] update v8 dep --- .github/actions/install/action.yml | 2 +- Dockerfile | 2 +- build.zig.zon | 4 ++-- src/browser/webapi/Performance.zig | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/actions/install/action.yml b/.github/actions/install/action.yml index 3a00da65..842f39cc 100644 --- a/.github/actions/install/action.yml +++ b/.github/actions/install/action.yml @@ -13,7 +13,7 @@ inputs: zig-v8: description: 'zig v8 version to install' required: false - default: 'v0.2.3' + default: 'v0.2.4' v8: description: 'v8 version to install' required: false diff --git a/Dockerfile b/Dockerfile index aee4842e..e683674a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM debian:stable-slim ARG MINISIG=0.12 ARG ZIG_MINISIG=RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U ARG V8=14.0.365.4 -ARG ZIG_V8=v0.2.3 +ARG ZIG_V8=v0.2.4 ARG TARGETPLATFORM RUN apt-get update -yq && \ diff --git a/build.zig.zon b/build.zig.zon index 2f7c8400..191e1f0c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -6,8 +6,8 @@ .minimum_zig_version = "0.15.2", .dependencies = .{ .v8 = .{ - .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/global_equality.tar.gz", - .hash = "v8-0.0.0-xddH6-oqBACWs5PNjtmkdEgS4zzEU3XqGKUoLvudBpFW", + .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/v0.2.4.tar.gz", + .hash = "v8-0.0.0-xddH66YvBAD0YI9xr6F0Xgnw9wN30FdZ10FLyuoV3e66", }, // .v8 = .{ .path = "../zig-v8-fork" }, .@"boringssl-zig" = .{ diff --git a/src/browser/webapi/Performance.zig b/src/browser/webapi/Performance.zig index 8f2a1bde..add39942 100644 --- a/src/browser/webapi/Performance.zig +++ b/src/browser/webapi/Performance.zig @@ -344,7 +344,6 @@ pub const Mark = struct { return m; } - pub fn getDetail(self: *const Mark) ?js.Value.Global { return self._detail; }