From 8aaef674fe558c184edeadf811c8f0a5f22e2304 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Tue, 30 Dec 2025 16:27:42 +0800 Subject: [PATCH] Migrate Function and String --- src/browser/EventManager.zig | 2 +- src/browser/Page.zig | 2 +- src/browser/js/Context.zig | 29 ++---- src/browser/js/Function.zig | 94 ++++++++++++------- src/browser/js/String.zig | 53 +++++++++++ src/browser/js/Value.zig | 32 ++++++- src/browser/js/global.zig | 4 +- src/browser/js/js.zig | 10 +- src/browser/webapi/AbortSignal.zig | 2 +- src/browser/webapi/CustomElementRegistry.zig | 4 +- src/browser/webapi/EventTarget.zig | 2 +- src/browser/webapi/IntersectionObserver.zig | 7 +- src/browser/webapi/KeyValueList.zig | 2 +- src/browser/webapi/MessagePort.zig | 4 +- src/browser/webapi/MutationObserver.zig | 2 +- src/browser/webapi/NodeFilter.zig | 9 +- src/browser/webapi/Window.zig | 16 ++-- src/browser/webapi/element/html/Script.zig | 22 +++-- src/browser/webapi/media/TextTrackCue.zig | 4 +- .../navigation/NavigationEventTarget.zig | 2 +- src/browser/webapi/net/URLSearchParams.zig | 2 +- src/browser/webapi/net/XMLHttpRequest.zig | 2 +- .../webapi/net/XMLHttpRequestEventTarget.zig | 14 +-- src/browser/webapi/streams/ReadableStream.zig | 2 +- 24 files changed, 212 insertions(+), 110 deletions(-) create mode 100644 src/browser/js/String.zig diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig index 067cd367..6c188b83 100644 --- a/src/browser/EventManager.zig +++ b/src/browser/EventManager.zig @@ -449,7 +449,7 @@ const Function = union(enum) { fn eqlFunction(self: Function, func: js.Function) bool { return switch (self) { - .value => |v| return v.id == func.id, + .value => |v| return v.id() == func.id(), else => false, }; } diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 032e5ac6..86374179 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -1309,7 +1309,7 @@ pub fn appendNew(self: *Page, parent: *Node, child: Node.NodeOrText) !void { // called from the parser when the node and all its children have been added pub fn nodeComplete(self: *Page, node: *Node) !void { Node.Build.call(node, "complete", .{ node, self }) catch |err| { - log.err(.bug, "build.complete", .{ .tag = node.getNodeName(self), .err = err }); + log.err(.bug, "build.complete", .{ .tag = node.getNodeName(&self.buf), .err = err }); return err; }; return self.nodeIsReady(true, node); diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index c3e397cc..c8fc312c 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -65,10 +65,6 @@ call_arena: Allocator, // the call which is calling the callback. call_depth: usize = 0, -// Callbacks are PesistendObjects. When the context ends, we need -// to free every callback we created. -callbacks: std.ArrayListUnmanaged(v8.Persistent(v8.Function)) = .empty, - // Serves two purposes. Like `callbacks` above, this is used to free // every PeristentObjet we've created during the lifetime of the context. // More importantly, it serves as an identity map - for a given Zig @@ -86,8 +82,8 @@ identity_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .empty, // we now simply persist every time persist() is called. js_object_list: std.ArrayListUnmanaged(PersistentObject) = .empty, -// tracks Global(v8.c.Value). global_values: std.ArrayList(js.Global(js.Value)) = .empty, +global_functions: std.ArrayList(js.Global(js.Function)) = .empty, // Various web APIs depend on having a persistent promise resolver. They // require for this PromiseResolver to be valid for a lifetime longer than @@ -169,6 +165,10 @@ pub fn deinit(self: *Context) void { global.deinit(); } + for (self.global_functions.items) |*global| { + global.deinit(); + } + for (self.persisted_promise_resolvers.items) |*p| { p.deinit(); } @@ -188,9 +188,6 @@ pub fn deinit(self: *Context) void { } } - for (self.callbacks.items) |*cb| { - cb.deinit(); - } if (self.handle_scope) |*scope| { scope.deinit(); self.v8_context.exit(); @@ -199,10 +196,6 @@ pub fn deinit(self: *Context) void { presistent_context.deinit(); } -fn trackCallback(self: *Context, pf: PersistentFunction) !void { - return self.callbacks.append(self.arena, pf); -} - // == Executors == pub fn eval(self: *Context, src: []const u8, name: ?[]const u8) !void { _ = try self.exec(src, name); @@ -392,13 +385,9 @@ pub fn createFunction(self: *Context, js_value: v8.Value) !js.Function { // caller should have made sure this was a function std.debug.assert(js_value.isFunction()); - const func = v8.Persistent(v8.Function).init(self.isolate, js_value.castTo(v8.Function)); - try self.trackCallback(func); - return .{ - .func = func, - .context = self, - .id = js_value.castTo(v8.Object).getIdentityHash(), + .ctx = self, + .handle = @ptrCast(js_value.handle), }; } @@ -488,7 +477,7 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp if (T == js.Function) { // we're returning a callback - return value.func.toValue(); + return .{ .handle = @ptrCast(value.handle) }; } if (T == js.Object) { @@ -2031,7 +2020,7 @@ pub fn queueSlotchangeDelivery(self: *Context) !void { } pub fn queueMicrotaskFunc(self: *Context, cb: js.Function) void { - self.isolate.enqueueMicrotaskFunc(cb.func.castToFunction()); + self.isolate.enqueueMicrotaskFunc(.{ .handle = cb.handle }); } // == Misc == diff --git a/src/browser/js/Function.zig b/src/browser/js/Function.zig index 109fa231..21d45014 100644 --- a/src/browser/js/Function.zig +++ b/src/browser/js/Function.zig @@ -20,24 +20,25 @@ const std = @import("std"); const js = @import("js.zig"); const v8 = js.v8; -const PersistentFunction = v8.Persistent(v8.Function); - const Allocator = std.mem.Allocator; const Function = @This(); -id: usize, -context: *js.Context, +ctx: *js.Context, this: ?v8.Object = null, -func: PersistentFunction, +handle: *const v8.c.Function, pub const Result = struct { stack: ?[]const u8, exception: []const u8, }; +pub fn id(self: *const Function) u32 { + return @as(u32, @bitCast(v8.c.v8__Object__GetIdentityHash(@ptrCast(self.handle)))); +} + pub fn getName(self: *const Function, allocator: Allocator) ![]const u8 { - const name = self.func.castToFunction().getName(); + const name = v8.c.v8__Function__GetName(self.handle).?; return self.context.valueToString(name, .{ .allocator = allocator }); } @@ -50,28 +51,27 @@ pub fn withThis(self: *const Function, value: anytype) !Function { const this_obj = if (@TypeOf(value) == js.Object) value.js_obj else - (try self.context.zigValueToJs(value, .{})).castTo(v8.Object); + (try self.ctx.zigValueToJs(value, .{})).castTo(v8.Object); return .{ - .id = self.id, + .ctx = self.ctx, .this = this_obj, - .func = self.func, - .context = self.context, + .handle = self.handle, }; } pub fn newInstance(self: *const Function, result: *Result) !js.Object { - const context = self.context; + const ctx = self.ctx; var try_catch: js.TryCatch = undefined; - try_catch.init(context); + try_catch.init(ctx); defer try_catch.deinit(); // This creates a new instance using this Function as a constructor. - // This returns a generic Object - const js_obj = self.func.castToFunction().initInstance(context.v8_context, &.{}) orelse { + // const c_args = @as(?[*]const ?*c.Value, @ptrCast(&.{})); + const handle = v8.c.v8__Function__NewInstance(self.handle, ctx.v8_context.handle, 0, null) orelse { if (try_catch.hasCaught()) { - const allocator = context.call_arena; + const allocator = ctx.call_arena; result.stack = try_catch.stack(allocator) catch null; result.exception = (try_catch.exception(allocator) catch "???") orelse "???"; } else { @@ -82,8 +82,8 @@ pub fn newInstance(self: *const Function, result: *Result) !js.Object { }; return .{ - .context = context, - .js_obj = js_obj, + .context = ctx, + .js_obj = .{ .handle = handle }, }; } @@ -97,12 +97,13 @@ pub fn tryCall(self: *const Function, comptime T: type, args: anytype, result: * pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, result: *Result) !T { var try_catch: js.TryCatch = undefined; - try_catch.init(self.context); + + try_catch.init(self.ctx); defer try_catch.deinit(); return self.callWithThis(T, this, args) catch |err| { if (try_catch.hasCaught()) { - const allocator = self.context.call_arena; + const allocator = self.ctx.call_arena; result.stack = try_catch.stack(allocator) catch null; result.exception = (try_catch.exception(allocator) catch @errorName(err)) orelse @errorName(err); } else { @@ -114,8 +115,9 @@ pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, a } pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype) !T { - const context = self.context; + const ctx = self.ctx; +<<<<<<< HEAD // When we're calling a function from within JavaScript itself, this isn't // necessary. We're within a Caller instantiation, which will already have // incremented the call_depth and it won't decrement it until the Caller is @@ -147,33 +149,35 @@ pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args const fields = s.fields; var js_args: [fields.len]v8.Value = undefined; inline for (fields, 0..) |f, i| { - js_args[i] = try context.zigValueToJs(@field(aargs, f.name), .{}); + js_args[i] = try ctx.zigValueToJs(@field(aargs, f.name), .{}); } const cargs: [fields.len]v8.Value = js_args; break :blk &cargs; }, .pointer => blk: { - var values = try context.call_arena.alloc(v8.Value, args.len); + var values = try ctx.call_arena.alloc(v8.Value, args.len); for (args, 0..) |a, i| { - values[i] = try context.zigValueToJs(a, .{}); + values[i] = try ctx.zigValueToJs(a, .{}); } break :blk values; }, else => @compileError("JS Function called with invalid paremter type"), }; - const result = self.func.castToFunction().call(context.v8_context, js_this, js_args); - if (result == null) { + const c_args = @as(?[*]const ?*v8.c.Value, @ptrCast(js_args.ptr)); + const handle = v8.c.v8__Function__Call(self.handle, ctx.v8_context.handle, js_this.handle, @as(c_int, @intCast(js_args.len)), c_args) orelse { // std.debug.print("CB ERR: {s}\n", .{self.src() catch "???"}); return error.JSExecCallback; - } + }; - if (@typeInfo(T) == .void) return {}; - return context.jsValueToZig(T, result.?); + if (@typeInfo(T) == .void) { + return {}; + } + return ctx.jsValueToZig(T, .{ .handle = handle }); } fn getThis(self: *const Function) v8.Object { - return self.this orelse self.context.v8_context.getGlobal(); + return self.this orelse self.ctx.v8_context.getGlobal(); } pub fn src(self: *const Function) ![]const u8 { @@ -182,8 +186,32 @@ pub fn src(self: *const Function) ![]const u8 { } pub fn getPropertyValue(self: *const Function, name: []const u8) !?js.Value { - const func_obj = self.func.castToFunction().toObject(); - const key = v8.String.initUtf8(self.context.isolate, name); - const value = func_obj.getValue(self.context.v8_context, key) catch return null; - return self.context.createValue(value); + const ctx = self.ctx; + const key = v8.String.initUtf8(ctx.isolate, name); + const handle = v8.c.v8__Object__Get(self.handle, ctx.v8_context.handle, key.handle) orelse { + return error.JsException; + }; + + return .{ + .ctx = ctx, + .handle = handle, + }; +} + +pub fn persist(self: *const Function) !Function { + var ctx = self.ctx; + + const global = js.Global(Function).init(ctx.isolate.handle, self.handle); + try ctx.global_functions.append(ctx.arena, global); + + return .{ + .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); } diff --git a/src/browser/js/String.zig b/src/browser/js/String.zig new file mode 100644 index 00000000..a170e538 --- /dev/null +++ b/src/browser/js/String.zig @@ -0,0 +1,53 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); +const js = @import("js.zig"); + +const Allocator = std.mem.Allocator; + +const v8 = js.v8; + +const String = @This(); + +ctx: *js.Context, +handle: *const v8.c.String, + +pub const ToZigOpts = struct { + allocator: ?Allocator = null, +}; + +pub fn toZig(self: String, opts: ToZigOpts) ![]u8 { + return self._toZig(false, opts); +} + +pub fn toZigZ(self: String, opts: ToZigOpts) ![:0]u8 { + return self._toZig(true, opts); +} + +fn _toZig(self: String, comptime null_terminate: bool, opts: ToZigOpts) !(if (null_terminate) [:0]u8 else []u8) { + const isolate = self.ctx.isolate.handle; + const allocator = opts.allocator orelse self.ctx.call_arena; + const len: u32 = @intCast(v8.c.v8__String__Utf8Length(self.handle, isolate)); + const buf = if (null_terminate) try allocator.allocSentinel(u8, len, 0) else try allocator.alloc(u8, len); + + const options = v8.c.NO_NULL_TERMINATION | v8.c.REPLACE_INVALID_UTF8; + const n = v8.c.v8__String__WriteUtf8(self.handle, isolate, buf.ptr, buf.len, options); + std.debug.assert(n == len); + return buf; +} diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig index 43892733..b419688d 100644 --- a/src/browser/js/Value.zig +++ b/src/browser/js/Value.zig @@ -50,8 +50,34 @@ pub fn isUndefined(self: Value) bool { return self.js_val.isUndefined(); } -pub fn toString(self: Value, allocator: Allocator) ![]const u8 { - return self.ctx.valueToString(.{ .handle = self.handle }, .{ .allocator = allocator }); +pub fn isSymbol(self: Value) bool { + return v8.c.v8__Value__IsSymbol(self.handle); +} + +pub fn toString(self: Value, opts: js.String.ToZigOpts) ![]u8 { + return self._toString(false, opts); +} +pub fn toStringZ(self: Value, opts: js.String.ToZigOpts) ![:0]u8 { + return self._toString(true, opts); +} + +fn _toString(self: Value, comptime null_terminate: bool, opts: js.String.ToZigOpts) !(if (null_terminate) [:0]u8 else []u8) { + const ctx = self.ctx; + + if (self.isSymbol()) { + const sym_handle = v8.c.v8__Symbol__Description(@ptrCast(self.handle), ctx.isolate.handle).?; + return _toString(.{ .handle = @ptrCast(sym_handle), .ctx = ctx }, null_terminate, opts); + } + + const str_handle = v8.c.v8__Value__ToString(self.handle, ctx.v8_context.handle) orelse { + return error.JsException; + }; + + const str = js.String{ .ctx = ctx, .handle = str_handle }; + if (comptime null_terminate) { + return js.String.toZigZ(str, opts); + } + return js.String.toZig(str, opts); } pub fn toBool(self: Value) bool { @@ -67,7 +93,7 @@ pub fn fromJson(ctx: *js.Context, json: []const u8) !Value { pub fn persist(self: Value) !Value { var ctx = self.ctx; - const global = js.Global(Value).init(ctx.isolate.handle, self); + const global = js.Global(Value).init(ctx.isolate.handle, self.handle); try ctx.global_values.append(ctx.arena, global); return .{ diff --git a/src/browser/js/global.zig b/src/browser/js/global.zig index 94540b65..82c2c55b 100644 --- a/src/browser/js/global.zig +++ b/src/browser/js/global.zig @@ -29,9 +29,9 @@ pub fn Global(comptime T: type) type { const Self = @This(); - pub fn init(isolate: *v8.c.Isolate, data: T) Self { + pub fn init(isolate: *v8.c.Isolate, handle: H) Self { var global: v8.c.Global = undefined; - v8.c.v8__Global__New(isolate, data.handle, &global); + v8.c.v8__Global__New(isolate, handle, &global); return .{ .global = global, }; diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index 82a81979..55b9b724 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -33,6 +33,7 @@ pub const Platform = @import("Platform.zig"); pub const This = @import("This.zig"); pub const Value = @import("Value.zig"); pub const Array = @import("Array.zig"); +pub const String = @import("String.zig"); pub const Object = @import("Object.zig"); pub const TryCatch = @import("TryCatch.zig"); pub const Function = @import("Function.zig"); @@ -157,15 +158,6 @@ pub const PersistentPromiseResolver = struct { pub const Promise = v8.Promise; -// When doing jsValueToZig, string ([]const u8) are managed by the -// call_arena. That means that if the API wants to persist the string -// (which is relatively common), it needs to dupe it again. -// If the parameter is an Env.String rather than a []const u8, then -// the page's arena will be used (rather than the call arena). -pub const String = struct { - string: []const u8, -}; - pub const Exception = struct { inner: v8.Value, context: *const Context, diff --git a/src/browser/webapi/AbortSignal.zig b/src/browser/webapi/AbortSignal.zig index c4b0f7ed..f85afe23 100644 --- a/src/browser/webapi/AbortSignal.zig +++ b/src/browser/webapi/AbortSignal.zig @@ -51,7 +51,7 @@ pub fn getOnAbort(self: *const AbortSignal) ?js.Function { pub fn setOnAbort(self: *AbortSignal, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_abort = try cb.withThis(self); + self._on_abort = try cb.persistWithThis(self); } else { self._on_abort = null; } diff --git a/src/browser/webapi/CustomElementRegistry.zig b/src/browser/webapi/CustomElementRegistry.zig index 2cb8c2b1..70c67bef 100644 --- a/src/browser/webapi/CustomElementRegistry.zig +++ b/src/browser/webapi/CustomElementRegistry.zig @@ -63,7 +63,7 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu const definition = try page._factory.create(CustomElementDefinition{ .name = owned_name, - .constructor = constructor, + .constructor = try constructor.persist(), .extends = extends_tag, }); @@ -73,7 +73,7 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu var js_arr = observed_attrs.toArray(); for (0..js_arr.len()) |i| { const attr_val = js_arr.get(@intCast(i)) catch continue; - const attr_name = attr_val.toString(page.arena) catch continue; + const attr_name = attr_val.toString(.{ .allocator = page.arena }) catch continue; const owned_attr = page.dupeString(attr_name) catch continue; definition.observed_attributes.put(page.arena, owned_attr, {}) catch continue; } diff --git a/src/browser/webapi/EventTarget.zig b/src/browser/webapi/EventTarget.zig index 1c75b0f0..1e6d806b 100644 --- a/src/browser/webapi/EventTarget.zig +++ b/src/browser/webapi/EventTarget.zig @@ -72,8 +72,8 @@ pub fn addEventListener(self: *EventTarget, typ: []const u8, callback_: ?EventLi const callback = callback_ orelse return; const em_callback = switch (callback) { - .function => |func| EventManager.Callback{ .function = func }, .object => |obj| EventManager.Callback{ .object = try obj.persist() }, + .function => |func| EventManager.Callback{ .function = try func.persist() }, }; const options = blk: { diff --git a/src/browser/webapi/IntersectionObserver.zig b/src/browser/webapi/IntersectionObserver.zig index 5bc94428..42eedcf5 100644 --- a/src/browser/webapi/IntersectionObserver.zig +++ b/src/browser/webapi/IntersectionObserver.zig @@ -70,7 +70,12 @@ pub fn init(callback: js.Function, options: ?ObserverInit, page: *Page) !*Inters .array => |arr| try page.arena.dupe(f64, arr), }; - return page._factory.create(IntersectionObserver{ ._callback = callback, ._root = opts.root, ._root_margin = root_margin, ._threshold = threshold }); + return page._factory.create(IntersectionObserver{ + ._callback = try callback.persist(), + ._root = opts.root, + ._root_margin = root_margin, + ._threshold = threshold, + }); } pub fn observe(self: *IntersectionObserver, target: *Element, page: *Page) !void { diff --git a/src/browser/webapi/KeyValueList.zig b/src/browser/webapi/KeyValueList.zig index b91e6d3b..b239e7f0 100644 --- a/src/browser/webapi/KeyValueList.zig +++ b/src/browser/webapi/KeyValueList.zig @@ -68,7 +68,7 @@ pub fn fromJsObject(arena: Allocator, js_obj: js.Object, comptime normalizer: ?N while (try it.next()) |name| { const js_value = try js_obj.get(name); - const value = try js_value.toString(arena); + const value = try js_value.toString(.{ .allocator = arena }); const normalized = if (comptime normalizer) |n| n(name, page) else name; list._entries.appendAssumeCapacity(.{ diff --git a/src/browser/webapi/MessagePort.zig b/src/browser/webapi/MessagePort.zig index ea6e79ca..d6e85aa9 100644 --- a/src/browser/webapi/MessagePort.zig +++ b/src/browser/webapi/MessagePort.zig @@ -94,7 +94,7 @@ pub fn getOnMessage(self: *const MessagePort) ?js.Function { pub fn setOnMessage(self: *MessagePort, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_message = cb; + self._on_message = try cb.persist(); } else { self._on_message = null; } @@ -106,7 +106,7 @@ pub fn getOnMessageError(self: *const MessagePort) ?js.Function { pub fn setOnMessageError(self: *MessagePort, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_message_error = cb; + self._on_message_error = try cb.persist(); } else { self._on_message_error = null; } diff --git a/src/browser/webapi/MutationObserver.zig b/src/browser/webapi/MutationObserver.zig index 1e35e2b8..d496406f 100644 --- a/src/browser/webapi/MutationObserver.zig +++ b/src/browser/webapi/MutationObserver.zig @@ -55,7 +55,7 @@ pub const ObserveOptions = struct { pub fn init(callback: js.Function, page: *Page) !*MutationObserver { return page._factory.create(MutationObserver{ - ._callback = callback, + ._callback = try callback.persist(), }); } diff --git a/src/browser/webapi/NodeFilter.zig b/src/browser/webapi/NodeFilter.zig index b1020fc5..a83cb5f2 100644 --- a/src/browser/webapi/NodeFilter.zig +++ b/src/browser/webapi/NodeFilter.zig @@ -36,10 +36,13 @@ pub const FilterOpts = union(enum) { pub fn init(opts_: ?FilterOpts) !NodeFilter { const opts = opts_ orelse return .{ ._func = null, ._original_filter = null }; const func = switch (opts) { - .function => |func| func, - .object => |obj| obj.acceptNode, + .function => |func| try func.persist(), + .object => |obj| try obj.acceptNode.persist(), + }; + return .{ + ._func = func, + ._original_filter = opts_, }; - return .{ ._func = func, ._original_filter = opts_ }; } // Constants diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 977273a9..24d7e97a 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -150,7 +150,7 @@ pub fn getOnLoad(self: *const Window) ?js.Function { } pub fn setOnLoad(self: *Window, setter: ?FunctionSetter) !void { - self._on_load = getFunctionFromSetter(setter); + self._on_load = try getFunctionFromSetter(setter); } pub fn getOnPageShow(self: *const Window) ?js.Function { @@ -158,7 +158,7 @@ pub fn getOnPageShow(self: *const Window) ?js.Function { } pub fn setOnPageShow(self: *Window, setter: ?FunctionSetter) !void { - self._on_pageshow = getFunctionFromSetter(setter); + self._on_pageshow = try getFunctionFromSetter(setter); } pub fn getOnPopState(self: *const Window) ?js.Function { @@ -166,7 +166,7 @@ pub fn getOnPopState(self: *const Window) ?js.Function { } pub fn setOnPopState(self: *Window, setter: ?FunctionSetter) !void { - self._on_popstate = getFunctionFromSetter(setter); + self._on_popstate = try getFunctionFromSetter(setter); } pub fn getOnError(self: *const Window) ?js.Function { @@ -174,7 +174,7 @@ pub fn getOnError(self: *const Window) ?js.Function { } pub fn setOnError(self: *Window, setter: ?FunctionSetter) !void { - self._on_error = getFunctionFromSetter(setter); + self._on_error = try getFunctionFromSetter(setter); } pub fn getOnUnhandledRejection(self: *const Window) ?js.Function { @@ -182,7 +182,7 @@ pub fn getOnUnhandledRejection(self: *const Window) ?js.Function { } pub fn setOnUnhandledRejection(self: *Window, setter: ?FunctionSetter) !void { - self._on_unhandled_rejection = getFunctionFromSetter(setter); + self._on_unhandled_rejection = try getFunctionFromSetter(setter); } pub fn fetch(_: *const Window, input: Fetch.Input, options: ?Fetch.InitOpts, page: *Page) !js.Promise { @@ -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 = cb, + .cb = try cb.persist(), .page = page, .mode = opts.mode, .name = opts.name, @@ -622,10 +622,10 @@ const FunctionSetter = union(enum) { // 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 { const setter = setter_ orelse return null; return switch (setter) { - .func => |func| func, + .func => |func| try func.persist(), .anything => null, }; } diff --git a/src/browser/webapi/element/html/Script.zig b/src/browser/webapi/element/html/Script.zig index c4fa83de..8531d275 100644 --- a/src/browser/webapi/element/html/Script.zig +++ b/src/browser/webapi/element/html/Script.zig @@ -80,7 +80,7 @@ pub fn getOnLoad(self: *const Script) ?js.Function { pub fn setOnLoad(self: *Script, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_load = cb; + self._on_load = try cb.persist(); } else { self._on_load = null; } @@ -92,7 +92,7 @@ pub fn getOnError(self: *const Script) ?js.Function { pub fn setOnError(self: *Script, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_error = cb; + self._on_error = try cb.persist(); } else { self._on_error = null; } @@ -136,16 +136,22 @@ pub const Build = struct { self._src = element.getAttributeSafe("src") orelse ""; if (element.getAttributeSafe("onload")) |on_load| { - self._on_load = page.js.stringToFunction(on_load) catch |err| blk: { - log.err(.js, "script.onload", .{ .err = err, .str = on_load }); - break :blk null; + 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 (element.getAttributeSafe("onerror")) |on_error| { - self._on_error = page.js.stringToFunction(on_error) catch |err| blk: { - log.err(.js, "script.onerror", .{ .err = err, .str = on_error }); - break :blk null; + 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(); }; } } diff --git a/src/browser/webapi/media/TextTrackCue.zig b/src/browser/webapi/media/TextTrackCue.zig index e590fa7f..b82a95f1 100644 --- a/src/browser/webapi/media/TextTrackCue.zig +++ b/src/browser/webapi/media/TextTrackCue.zig @@ -79,7 +79,7 @@ pub fn getOnEnter(self: *const TextTrackCue) ?js.Function { pub fn setOnEnter(self: *TextTrackCue, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_enter = try cb.withThis(self); + self._on_enter = try cb.persistWithThis(self); } else { self._on_enter = null; } @@ -91,7 +91,7 @@ pub fn getOnExit(self: *const TextTrackCue) ?js.Function { pub fn setOnExit(self: *TextTrackCue, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_exit = try cb.withThis(self); + self._on_exit = try cb.persistWithThis(self); } else { self._on_exit = null; } diff --git a/src/browser/webapi/navigation/NavigationEventTarget.zig b/src/browser/webapi/navigation/NavigationEventTarget.zig index 4ab6df66..7bf5d858 100644 --- a/src/browser/webapi/navigation/NavigationEventTarget.zig +++ b/src/browser/webapi/navigation/NavigationEventTarget.zig @@ -57,7 +57,7 @@ pub fn getOnCurrentEntryChange(self: *NavigationEventTarget) ?js.Function { pub fn setOnCurrentEntryChange(self: *NavigationEventTarget, listener: ?js.Function) !void { if (listener) |listen| { - self._on_currententrychange = try listen.withThis(self); + self._on_currententrychange = try listen.persistWithThis(self); } else { self._on_currententrychange = null; } diff --git a/src/browser/webapi/net/URLSearchParams.zig b/src/browser/webapi/net/URLSearchParams.zig index 52615ccb..e9384d28 100644 --- a/src/browser/webapi/net/URLSearchParams.zig +++ b/src/browser/webapi/net/URLSearchParams.zig @@ -50,7 +50,7 @@ pub fn init(opts_: ?InitOpts, page: *Page) !*URLSearchParams { break :blk try KeyValueList.fromJsObject(arena, js_val.toObject(), null, page); } if (js_val.isString()) { - break :blk try paramsFromString(arena, try js_val.toString(arena), &page.buf); + break :blk try paramsFromString(arena, try js_val.toString(.{ .allocator = arena }), &page.buf); } return error.InvalidArgument; }, diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig index c3aef7f6..93ee323f 100644 --- a/src/browser/webapi/net/XMLHttpRequest.zig +++ b/src/browser/webapi/net/XMLHttpRequest.zig @@ -104,7 +104,7 @@ pub fn getOnReadyStateChange(self: *const XMLHttpRequest) ?js.Function { pub fn setOnReadyStateChange(self: *XMLHttpRequest, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_ready_state_change = try cb.withThis(self); + self._on_ready_state_change = try cb.persistWithThis(self); } else { self._on_ready_state_change = null; } diff --git a/src/browser/webapi/net/XMLHttpRequestEventTarget.zig b/src/browser/webapi/net/XMLHttpRequestEventTarget.zig index b8efe47d..20bea353 100644 --- a/src/browser/webapi/net/XMLHttpRequestEventTarget.zig +++ b/src/browser/webapi/net/XMLHttpRequestEventTarget.zig @@ -77,7 +77,7 @@ pub fn getOnAbort(self: *const XMLHttpRequestEventTarget) ?js.Function { pub fn setOnAbort(self: *XMLHttpRequestEventTarget, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_abort = try cb.withThis(self); + self._on_abort = try cb.persistWithThis(self); } else { self._on_abort = null; } @@ -89,7 +89,7 @@ pub fn getOnError(self: *const XMLHttpRequestEventTarget) ?js.Function { pub fn setOnError(self: *XMLHttpRequestEventTarget, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_error = try cb.withThis(self); + self._on_error = try cb.persistWithThis(self); } else { self._on_error = null; } @@ -101,7 +101,7 @@ pub fn getOnLoad(self: *const XMLHttpRequestEventTarget) ?js.Function { pub fn setOnLoad(self: *XMLHttpRequestEventTarget, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_load = try cb.withThis(self); + self._on_load = try cb.persistWithThis(self); } else { self._on_load = null; } @@ -113,7 +113,7 @@ pub fn getOnLoadEnd(self: *const XMLHttpRequestEventTarget) ?js.Function { pub fn setOnLoadEnd(self: *XMLHttpRequestEventTarget, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_load_end = try cb.withThis(self); + self._on_load_end = try cb.persistWithThis(self); } else { self._on_load_end = null; } @@ -125,7 +125,7 @@ pub fn getOnLoadStart(self: *const XMLHttpRequestEventTarget) ?js.Function { pub fn setOnLoadStart(self: *XMLHttpRequestEventTarget, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_load_start = try cb.withThis(self); + self._on_load_start = try cb.persistWithThis(self); } else { self._on_load_start = null; } @@ -137,7 +137,7 @@ pub fn getOnProgress(self: *const XMLHttpRequestEventTarget) ?js.Function { pub fn setOnProgress(self: *XMLHttpRequestEventTarget, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_progress = try cb.withThis(self); + self._on_progress = try cb.persistWithThis(self); } else { self._on_progress = null; } @@ -149,7 +149,7 @@ pub fn getOnTimeout(self: *const XMLHttpRequestEventTarget) ?js.Function { pub fn setOnTimeout(self: *XMLHttpRequestEventTarget, cb_: ?js.Function) !void { if (cb_) |cb| { - self._on_timeout = try cb.withThis(self); + self._on_timeout = try cb.persistWithThis(self); } else { self._on_timeout = null; } diff --git a/src/browser/webapi/streams/ReadableStream.zig b/src/browser/webapi/streams/ReadableStream.zig index 70c59ba0..ed1d0149 100644 --- a/src/browser/webapi/streams/ReadableStream.zig +++ b/src/browser/webapi/streams/ReadableStream.zig @@ -85,7 +85,7 @@ pub fn init(src_: ?UnderlyingSource, strategy_: ?QueueingStrategy, page: *Page) } if (src.pull) |pull| { - self._pull_fn = pull; + self._pull_fn = try pull.persist(); try self.callPullIfNeeded(); } }