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 a44dd0b4..191e1f0c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -6,10 +6,10 @@ .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", + .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" }, + // .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..0854bdce 100644 --- a/src/browser/EventManager.zig +++ b/src/browser/EventManager.zig @@ -102,8 +102,8 @@ pub fn register(self: *EventManager, target: *EventTarget, typ: []const u8, call } const func = switch (callback) { - .function => |f| Function{ .value = f }, - .object => |o| Function{ .object = o }, + .function => |f| Function{ .value = try f.persist() }, + .object => |o| Function{ .object = try o.persist() }, }; const listener = try self.listener_pool.create(); @@ -368,12 +368,13 @@ 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); }, - .object => |obj| { + .object => |*obj_global| { + const obj = obj_global.local(); if (try obj.getFunction("handleEvent")) |handleEvent| { try handleEvent.callWithThis(void, obj, .{event}); } @@ -443,20 +444,20 @@ const Listener = struct { }; const Function = union(enum) { - value: js.Function, + value: js.Function.Global, string: String, - object: js.Object, + object: js.Object.Global, 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, }; } 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/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..2e9f6bed 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -77,12 +77,12 @@ 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_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_promise_resolvers: std.ArrayList(js.Global(js.PromiseResolver)) = .empty, +global_values: std.ArrayList(v8.Global) = .empty, +global_objects: std.ArrayList(v8.Global) = .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(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 { @@ -142,27 +142,27 @@ 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| { - global.deinit(); + v8.v8__Global__Reset(global); } for (self.global_functions.items) |*global| { - global.deinit(); + v8.v8__Global__Reset(global); } 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| { @@ -452,11 +452,41 @@ 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) }; } + 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.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; } @@ -778,6 +808,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), @@ -806,6 +843,29 @@ 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(); + }, + 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; @@ -1251,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); @@ -1264,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 @@ -1272,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 { @@ -1285,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(); @@ -1299,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 @@ -1317,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 @@ -1339,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(); @@ -1367,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 { @@ -1375,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; }; @@ -1392,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; }; }; @@ -1436,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)); @@ -1456,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/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/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/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/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/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 f85afe23..9f3298aa 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 { @@ -71,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, } @@ -81,18 +77,19 @@ 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" }, ); } // 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; } @@ -118,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 }; @@ -127,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/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..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,11 +106,11 @@ 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); } } -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; } @@ -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 { @@ -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/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 1e6d806b..d1e6c67a 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) { - .object => |obj| EventManager.Callback{ .object = try obj.persist() }, - .function => |func| EventManager.Callback{ .function = try func.persist() }, + .object => |obj| EventManager.Callback{ .object = obj }, + .function => |func| EventManager.Callback{ .function = func }, }; const options = blk: { @@ -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/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..f567199b 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 { @@ -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, .{ @@ -88,33 +88,25 @@ 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 { port: *MessagePort, - message: js.Value, + message: js.Value.Global, page: *Page, fn deinit(self: *PostMessageCallback) void { @@ -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/Performance.zig b/src/browser/webapi/Performance.zig index bc4743cb..add39942 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,7 @@ 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 +362,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 +405,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/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..7b6938e8 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.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, 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.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, 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.Global, 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, @@ -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,13 +465,13 @@ 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, 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; @@ -481,12 +481,9 @@ fn scheduleCallback(self: *Window, cb: js.Function, delay_ms: u32, opts: Schedul 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); @@ -497,7 +494,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,11 +523,11 @@ 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, - params: []const js.Value, + params: []const js.Value.Global, removed: bool = false, @@ -558,17 +555,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 }); }; }, @@ -587,7 +584,7 @@ const ScheduleCallback = struct { const PostMessageCallback = struct { window: *Window, - message: js.Value, + message: js.Value.Global, origin: []const u8, page: *Page, @@ -615,17 +612,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/animation/Animation.zig b/src/browser/webapi/animation/Animation.zig index f9a7988f..8fc6c99a 100644 --- a/src/browser/webapi/animation/Animation.zig +++ b/src/browser/webapi/animation/Animation.zig @@ -21,10 +21,10 @@ const Page = @import("../../Page.zig"); const Animation = @This(); -_effect: ?js.Object = null, -_timeline: ?js.Object = null, -_ready_resolver: ?js.PromiseResolver = null, -_finished_resolver: ?js.PromiseResolver = null, +_effect: ?js.Object.Global = null, +_timeline: ?js.Object.Global = 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,31 +59,23 @@ 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 { +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/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/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/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/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/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/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/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/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig index 954ad8fa..519adba4 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, @@ -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, }; @@ -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..af50d8bc 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, }; @@ -80,12 +80,12 @@ pub fn init(src_: ?UnderlyingSource, strategy_: ?QueueingStrategy, page: *Page) if (src.cancel) |callback| { self._cancel = .{ - .callback = try callback.persist(), + .callback = callback, }; } 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; @@ -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(.{}); @@ -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, .{}); } } @@ -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 = null, + 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(); }