From 226d1ff1831d43ceaea275924e8fe7336e716ca1 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 3 Apr 2026 09:36:40 +0800 Subject: [PATCH] Introduce Execution A Worker has no page. So any API that is accessible to a worker cannot take a *Page parameter. Such APIs will now take a js.Execution which the context will own and create from the Page (or from the WorkerGlobalScope when that's created). To test this, in addition to introducing the Execution, this change also updates URLSearchParams which is accessible to Worker (and the Page obviously). This change is obviously viral..if URLSearchParams no longer has a *Page but instead has an *Execution, then any function it calls must also be updated. So some APIs will take a *Page (those only accessible from a Page) and some will take an *Execution (those accessible from a Page or Worker). I'm ok with that. A lot of private/internal functions take a *Page, because it's simple, but all they want is a call_arena or something. We'll try to update those as much as possible. The Page/Execution being injected from the bridge is convenient, but we should be more specific for internal calls and pass only what's needed. --- src/browser/js/Caller.zig | 16 +++++- src/browser/js/Context.zig | 5 ++ src/browser/js/Env.zig | 3 + src/browser/js/Execution.zig | 56 +++++++++++++++++++ src/browser/js/js.zig | 1 + src/browser/webapi/KeyValueList.zig | 8 +-- src/browser/webapi/Location.zig | 8 +-- src/browser/webapi/URL.zig | 39 +++++++------ src/browser/webapi/collections/ChildNodes.zig | 6 +- .../webapi/collections/DOMTokenList.zig | 30 +++++----- .../webapi/collections/HTMLAllCollection.zig | 7 ++- .../webapi/collections/HTMLCollection.zig | 7 ++- src/browser/webapi/collections/NodeList.zig | 25 +++++---- src/browser/webapi/collections/iterator.zig | 16 ++++-- src/browser/webapi/net/FormData.zig | 14 ++--- src/browser/webapi/net/Headers.zig | 14 ++--- src/browser/webapi/net/URLSearchParams.zig | 40 ++++++------- 17 files changed, 194 insertions(+), 101 deletions(-) create mode 100644 src/browser/js/Execution.zig diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index cd4d25d3..d9a5fc55 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -434,6 +434,10 @@ fn isPage(comptime T: type) bool { return T == *Page or T == *const Page; } +fn isExecution(comptime T: type) bool { + return T == *js.Execution or T == *const js.Execution; +} + // These wrap the raw v8 C API to provide a cleaner interface. pub const FunctionCallbackInfo = struct { handle: *const v8.FunctionCallbackInfo, @@ -710,7 +714,13 @@ fn getArgs(comptime F: type, comptime offset: usize, local: *const Local, info: break :blk params[0 .. params.len - 1]; } - // we have neither a Page nor a JsObject. All params must be + // If the last parameter is Execution, set it from the context + if (comptime isExecution(params[params.len - 1].type.?)) { + @field(args, tupleFieldName(params.len - 1 + offset)) = &local.ctx.execution; + break :blk params[0 .. params.len - 1]; + } + + // we have neither a Page, Execution, nor a JsObject. All params must be // bound to a JavaScript value. break :blk params; }; @@ -759,7 +769,9 @@ fn getArgs(comptime F: type, comptime offset: usize, local: *const Local, info: } if (comptime isPage(param.type.?)) { - @compileError("Page must be the last parameter (or 2nd last if there's a JsThis): " ++ @typeName(F)); + @compileError("Page must be the last parameter: " ++ @typeName(F)); + } else if (comptime isExecution(param.type.?)) { + @compileError("Execution must be the last parameter: " ++ @typeName(F)); } else if (i >= js_parameter_count) { if (@typeInfo(param.type.?) != .optional) { return error.InvalidArgument; diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index beec0625..b83554db 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -25,6 +25,7 @@ const bridge = @import("bridge.zig"); const Env = @import("Env.zig"); const Origin = @import("Origin.zig"); const Scheduler = @import("Scheduler.zig"); +const Execution = @import("Execution.zig"); const Page = @import("../Page.zig"); const Session = @import("../Session.zig"); @@ -111,6 +112,10 @@ script_manager: ?*ScriptManager, // Our macrotasks scheduler: Scheduler, +// Execution context for worker-compatible APIs. This provides a common +// interface that works in both Page and Worker contexts. +execution: Execution, + unknown_properties: (if (IS_DEBUG) std.StringHashMapUnmanaged(UnknownPropertyStat) else void) = if (IS_DEBUG) .{} else {}, const ModuleEntry = struct { diff --git a/src/browser/js/Env.zig b/src/browser/js/Env.zig index bae6a8f0..24cc5363 100644 --- a/src/browser/js/Env.zig +++ b/src/browser/js/Env.zig @@ -330,7 +330,10 @@ pub fn createContext(self: *Env, page: *Page, params: ContextParams) !*Context { .scheduler = .init(context_arena), .identity = params.identity, .identity_arena = params.identity_arena, + .execution = undefined, }; + // Initialize execution after context is created since it contains self-references + context.execution = js.Execution.fromContext(context); { // Multiple contexts can be created for the same Window (via CDP). We only diff --git a/src/browser/js/Execution.zig b/src/browser/js/Execution.zig new file mode 100644 index 00000000..6c807aa2 --- /dev/null +++ b/src/browser/js/Execution.zig @@ -0,0 +1,56 @@ +// Copyright (C) 2023-2026 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 . + +//! Execution context for worker-compatible APIs. +//! +//! This provides a common interface for APIs that work in both Window and Worker +//! contexts. Instead of taking `*Page` (which is DOM-specific), these APIs take +//! `*Execution` which abstracts the common infrastructure. +//! +//! The bridge constructs an Execution on-the-fly from the current context, +//! whether it's a Page context or a Worker context. + +const std = @import("std"); +const Context = @import("Context.zig"); +const Scheduler = @import("Scheduler.zig"); +const Factory = @import("../Factory.zig"); + +const Allocator = std.mem.Allocator; + +const Execution = @This(); + +context: *Context, + +// Fields named to match Page for generic code (executor._factory works for both) +_factory: *Factory, +arena: Allocator, +call_arena: Allocator, +_scheduler: *Scheduler, +buf: []u8, + +pub fn fromContext(ctx: *Context) Execution { + const page = ctx.page; + return .{ + .context = ctx, + ._factory = page._factory, + .arena = page.arena, + .call_arena = ctx.call_arena, + ._scheduler = &ctx.scheduler, + .buf = &page.buf, + }; +} diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index 10867167..74ee0c7a 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -27,6 +27,7 @@ pub const Caller = @import("Caller.zig"); pub const Origin = @import("Origin.zig"); pub const Identity = @import("Identity.zig"); pub const Context = @import("Context.zig"); +pub const Execution = @import("Execution.zig"); pub const Local = @import("Local.zig"); pub const Inspector = @import("Inspector.zig"); pub const Snapshot = @import("Snapshot.zig"); diff --git a/src/browser/webapi/KeyValueList.zig b/src/browser/webapi/KeyValueList.zig index b068c2b9..7bfa5807 100644 --- a/src/browser/webapi/KeyValueList.zig +++ b/src/browser/webapi/KeyValueList.zig @@ -22,6 +22,7 @@ const String = @import("../../string.zig").String; const js = @import("../js/js.zig"); const Page = @import("../Page.zig"); +const Execution = js.Execution; const Allocator = std.mem.Allocator; @@ -111,12 +112,11 @@ pub fn get(self: *const KeyValueList, name: []const u8) ?[]const u8 { return null; } -pub fn getAll(self: *const KeyValueList, name: []const u8, page: *Page) ![]const []const u8 { - const arena = page.call_arena; +pub fn getAll(self: *const KeyValueList, allocator: Allocator, name: []const u8) ![]const []const u8 { var arr: std.ArrayList([]const u8) = .empty; for (self._entries.items) |*entry| { if (entry.name.eqlSlice(name)) { - try arr.append(arena, entry.value.str()); + try arr.append(allocator, entry.value.str()); } } return arr.items; @@ -260,7 +260,7 @@ pub const Iterator = struct { pub const Entry = struct { []const u8, []const u8 }; - pub fn next(self: *Iterator, _: *const Page) ?Iterator.Entry { + pub fn next(self: *Iterator, _: *const Execution) ?Iterator.Entry { const index = self.index; const entries = self.kv._entries.items; if (index >= entries.len) { diff --git a/src/browser/webapi/Location.zig b/src/browser/webapi/Location.zig index 9055abbb..cb8806ff 100644 --- a/src/browser/webapi/Location.zig +++ b/src/browser/webapi/Location.zig @@ -27,7 +27,7 @@ const Location = @This(); _url: *URL, pub fn init(raw_url: [:0]const u8, page: *Page) !*Location { - const url = try URL.init(raw_url, null, page); + const url = try URL.init(raw_url, null, &page.js.execution); return page._factory.create(Location{ ._url = url, }); @@ -54,11 +54,11 @@ pub fn getPort(self: *const Location) []const u8 { } pub fn getOrigin(self: *const Location, page: *const Page) ![]const u8 { - return self._url.getOrigin(page); + return self._url.getOrigin(&page.js.execution); } pub fn getSearch(self: *const Location, page: *const Page) ![]const u8 { - return self._url.getSearch(page); + return self._url.getSearch(&page.js.execution); } pub fn getHash(self: *const Location) []const u8 { @@ -99,7 +99,7 @@ pub fn reload(_: *const Location, page: *Page) !void { } pub fn toString(self: *const Location, page: *const Page) ![:0]const u8 { - return self._url.toString(page); + return self._url.toString(&page.js.execution); } pub const JsApi = struct { diff --git a/src/browser/webapi/URL.zig b/src/browser/webapi/URL.zig index 8856c83d..3070b7a1 100644 --- a/src/browser/webapi/URL.zig +++ b/src/browser/webapi/URL.zig @@ -23,6 +23,7 @@ const U = @import("../URL.zig"); const Page = @import("../Page.zig"); const URLSearchParams = @import("net/URLSearchParams.zig"); const Blob = @import("Blob.zig"); +const Execution = js.Execution; const Allocator = std.mem.Allocator; @@ -36,11 +37,12 @@ _search_params: ?*URLSearchParams = null, pub const resolve = @import("../URL.zig").resolve; pub const eqlDocument = @import("../URL.zig").eqlDocument; -pub fn init(url: [:0]const u8, base_: ?[:0]const u8, page: *Page) !*URL { - const arena = page.arena; +pub fn init(url: [:0]const u8, base_: ?[:0]const u8, exec: *const Execution) !*URL { + const arena = exec.arena; + const page = exec.context.page; if (std.mem.eql(u8, url, "about:blank")) { - return page._factory.create(URL{ + return exec._factory.create(URL{ ._raw = "about:blank", ._arena = arena, }); @@ -63,7 +65,7 @@ pub fn init(url: [:0]const u8, base_: ?[:0]const u8, page: *Page) !*URL { const raw = try resolve(arena, base, url, .{ .always_dupe = true }); - return page._factory.create(URL{ + return exec._factory.create(URL{ ._raw = raw, ._arena = arena, }); @@ -107,20 +109,20 @@ pub fn getPort(self: *const URL) []const u8 { return U.getPort(self._raw); } -pub fn getOrigin(self: *const URL, page: *const Page) ![]const u8 { - return (try U.getOrigin(page.call_arena, self._raw)) orelse { +pub fn getOrigin(self: *const URL, exec: *const Execution) ![]const u8 { + return (try U.getOrigin(exec.call_arena, self._raw)) orelse { // yes, a null string, that's what the spec wants return "null"; }; } -pub fn getSearch(self: *const URL, page: *const Page) ![]const u8 { +pub fn getSearch(self: *const URL, exec: *const Execution) ![]const u8 { // If searchParams has been accessed, generate search from it if (self._search_params) |sp| { if (sp.getSize() == 0) { return ""; } - var buf = std.Io.Writer.Allocating.init(page.call_arena); + var buf = std.Io.Writer.Allocating.init(exec.call_arena); try buf.writer.writeByte('?'); try sp.toString(&buf.writer); return buf.written(); @@ -132,30 +134,31 @@ pub fn getHash(self: *const URL) []const u8 { return U.getHash(self._raw); } -pub fn getSearchParams(self: *URL, page: *Page) !*URLSearchParams { +pub fn getSearchParams(self: *URL, exec: *const Execution) !*URLSearchParams { if (self._search_params) |sp| { return sp; } // Get current search string (without the '?') - const search = try self.getSearch(page); + const search = try self.getSearch(exec); const search_value = if (search.len > 0) search[1..] else ""; - const params = try URLSearchParams.init(.{ .query_string = search_value }, page); + const params = try URLSearchParams.init(.{ .query_string = search_value }, exec); self._search_params = params; return params; } -pub fn setHref(self: *URL, value: []const u8, page: *Page) !void { +pub fn setHref(self: *URL, value: []const u8, exec: *const Execution) !void { + const page = exec.context.page; const base = if (U.isCompleteHTTPUrl(value)) page.url else self._raw; - const raw = try U.resolve(self._arena orelse page.arena, base, value, .{ .always_dupe = true }); + const raw = try U.resolve(self._arena orelse exec.arena, base, value, .{ .always_dupe = true }); self._raw = raw; // Update existing searchParams if it exists if (self._search_params) |sp| { const search = U.getSearch(raw); const search_value = if (search.len > 0) search[1..] else ""; - try sp.updateFromString(search_value, page); + try sp.updateFromString(search_value, exec); } } @@ -184,7 +187,7 @@ pub fn setPathname(self: *URL, value: []const u8) !void { self._raw = try U.setPathname(self._raw, value, allocator); } -pub fn setSearch(self: *URL, value: []const u8, page: *Page) !void { +pub fn setSearch(self: *URL, value: []const u8, exec: *const Execution) !void { const allocator = self._arena orelse return error.NoAllocator; self._raw = try U.setSearch(self._raw, value, allocator); @@ -192,7 +195,7 @@ pub fn setSearch(self: *URL, value: []const u8, page: *Page) !void { if (self._search_params) |sp| { const search = U.getSearch(self._raw); const search_value = if (search.len > 0) search[1..] else ""; - try sp.updateFromString(search_value, page); + try sp.updateFromString(search_value, exec); } } @@ -201,7 +204,7 @@ pub fn setHash(self: *URL, value: []const u8) !void { self._raw = try U.setHash(self._raw, value, allocator); } -pub fn toString(self: *const URL, page: *const Page) ![:0]const u8 { +pub fn toString(self: *const URL, exec: *const Execution) ![:0]const u8 { const sp = self._search_params orelse { return self._raw; }; @@ -217,7 +220,7 @@ pub fn toString(self: *const URL, page: *const Page) ![:0]const u8 { const hash = self.getHash(); // Build the new URL string - var buf = std.Io.Writer.Allocating.init(page.call_arena); + var buf = std.Io.Writer.Allocating.init(exec.call_arena); try buf.writer.writeAll(base); // Add / if missing (e.g., "https://example.com" -> "https://example.com/") diff --git a/src/browser/webapi/collections/ChildNodes.zig b/src/browser/webapi/collections/ChildNodes.zig index 9c2bde91..f23fe189 100644 --- a/src/browser/webapi/collections/ChildNodes.zig +++ b/src/browser/webapi/collections/ChildNodes.zig @@ -18,10 +18,12 @@ const std = @import("std"); +const js = @import("../../js/js.zig"); const Node = @import("../Node.zig"); const Page = @import("../../Page.zig"); const Session = @import("../../Session.zig"); const GenericIterator = @import("iterator.zig").Entry; +const Execution = js.Execution; // Optimized for node.childNodes, which has to be a live list. // No need to go through a TreeWalker or add any filtering. @@ -136,9 +138,9 @@ const Iterator = struct { const Entry = struct { u32, *Node }; - pub fn next(self: *Iterator, page: *Page) !?Entry { + pub fn next(self: *Iterator, exec: *const Execution) !?Entry { const index = self.index; - const node = try self.list.getAtIndex(index, page) orelse return null; + const node = try self.list.getAtIndex(index, exec.context.page) orelse return null; self.index = index + 1; return .{ index, node }; } diff --git a/src/browser/webapi/collections/DOMTokenList.zig b/src/browser/webapi/collections/DOMTokenList.zig index c9843895..a0409bcf 100644 --- a/src/browser/webapi/collections/DOMTokenList.zig +++ b/src/browser/webapi/collections/DOMTokenList.zig @@ -24,6 +24,7 @@ const js = @import("../../js/js.zig"); const Page = @import("../../Page.zig"); const Element = @import("../Element.zig"); const GenericIterator = @import("iterator.zig").Entry; +const Execution = js.Execution; pub const DOMTokenList = @This(); @@ -43,7 +44,7 @@ const Lookup = std.StringArrayHashMapUnmanaged(void); const WHITESPACE = " \t\n\r\x0C"; pub fn length(self: *const DOMTokenList, page: *Page) !u32 { - const tokens = try self.getTokens(page); + const tokens = try self.getTokens(page.call_arena); return @intCast(tokens.count()); } @@ -82,8 +83,8 @@ pub fn add(self: *DOMTokenList, tokens: []const []const u8, page: *Page) !void { try validateToken(token); } - var lookup = try self.getTokens(page); const allocator = page.call_arena; + var lookup = try self.getTokens(allocator); try lookup.ensureUnusedCapacity(allocator, tokens.len); for (tokens) |token| { @@ -98,7 +99,7 @@ pub fn remove(self: *DOMTokenList, tokens: []const []const u8, page: *Page) !voi try validateToken(token); } - var lookup = try self.getTokens(page); + var lookup = try self.getTokens(page.call_arena); for (tokens) |token| { _ = lookup.orderedRemove(token); } @@ -149,7 +150,8 @@ pub fn replace(self: *DOMTokenList, old_token: []const u8, new_token: []const u8 return error.InvalidCharacterError; } - var lookup = try self.getTokens(page); + const allocator = page.call_arena; + var lookup = try self.getTokens(page.call_arena); // Check if old_token exists if (!lookup.contains(old_token)) { @@ -162,7 +164,6 @@ pub fn replace(self: *DOMTokenList, old_token: []const u8, new_token: []const u8 return true; } - const allocator = page.call_arena; // Build new token list preserving order but replacing old with new var new_tokens = try std.ArrayList([]const u8).initCapacity(allocator, lookup.count()); var replaced_old = false; @@ -202,16 +203,16 @@ pub fn setValue(self: *DOMTokenList, value: String, page: *Page) !void { try self._element.setAttribute(self._attribute_name, value, page); } -pub fn keys(self: *DOMTokenList, page: *Page) !*KeyIterator { - return .init(.{ .list = self }, page); +pub fn keys(self: *DOMTokenList, exec: *const Execution) !*KeyIterator { + return .init(.{ .list = self }, exec); } -pub fn values(self: *DOMTokenList, page: *Page) !*ValueIterator { - return .init(.{ .list = self }, page); +pub fn values(self: *DOMTokenList, exec: *const Execution) !*ValueIterator { + return .init(.{ .list = self }, exec); } -pub fn entries(self: *DOMTokenList, page: *Page) !*EntryIterator { - return .init(.{ .list = self }, page); +pub fn entries(self: *DOMTokenList, exec: *const Execution) !*EntryIterator { + return .init(.{ .list = self }, exec); } pub fn forEach(self: *DOMTokenList, cb_: js.Function, js_this_: ?js.Object, page: *Page) !void { @@ -237,14 +238,13 @@ pub fn forEach(self: *DOMTokenList, cb_: js.Function, js_this_: ?js.Object, page } } -fn getTokens(self: *const DOMTokenList, page: *Page) !Lookup { +fn getTokens(self: *const DOMTokenList, allocator: std.mem.Allocator) !Lookup { const value = self.getValue(); if (value.len == 0) { return .empty; } var list: Lookup = .empty; - const allocator = page.call_arena; try list.ensureTotalCapacity(allocator, 4); var it = std.mem.tokenizeAny(u8, value, WHITESPACE); @@ -282,9 +282,9 @@ const Iterator = struct { const Entry = struct { u32, []const u8 }; - pub fn next(self: *Iterator, page: *Page) !?Entry { + pub fn next(self: *Iterator, exec: *const Execution) !?Entry { const index = self.index; - const node = try self.list.item(index, page) orelse return null; + const node = try self.list.item(index, exec.context.page) orelse return null; self.index = index + 1; return .{ index, node }; } diff --git a/src/browser/webapi/collections/HTMLAllCollection.zig b/src/browser/webapi/collections/HTMLAllCollection.zig index acada474..ddad1d08 100644 --- a/src/browser/webapi/collections/HTMLAllCollection.zig +++ b/src/browser/webapi/collections/HTMLAllCollection.zig @@ -24,6 +24,7 @@ const Page = @import("../../Page.zig"); const Node = @import("../Node.zig"); const Element = @import("../Element.zig"); const TreeWalker = @import("../TreeWalker.zig"); +const Execution = js.Execution; const HTMLAllCollection = @This(); @@ -133,11 +134,11 @@ pub fn callable(self: *HTMLAllCollection, arg: CAllAsFunctionArg, page: *Page) ? }; } -pub fn iterator(self: *HTMLAllCollection, page: *Page) !*Iterator { +pub fn iterator(self: *HTMLAllCollection, exec: *const Execution) !*Iterator { return Iterator.init(.{ .list = self, .tw = self._tw.clone(), - }, page); + }, exec); } const GenericIterator = @import("iterator.zig").Entry; @@ -145,7 +146,7 @@ pub const Iterator = GenericIterator(struct { list: *HTMLAllCollection, tw: TreeWalker.FullExcludeSelf, - pub fn next(self: *@This(), _: *Page) ?*Element { + pub fn next(self: *@This(), _: *const Execution) ?*Element { while (self.tw.next()) |node| { if (node.is(Element)) |el| { return el; diff --git a/src/browser/webapi/collections/HTMLCollection.zig b/src/browser/webapi/collections/HTMLCollection.zig index fc73ec6d..c3842e77 100644 --- a/src/browser/webapi/collections/HTMLCollection.zig +++ b/src/browser/webapi/collections/HTMLCollection.zig @@ -23,6 +23,7 @@ const Page = @import("../../Page.zig"); const Element = @import("../Element.zig"); const TreeWalker = @import("../TreeWalker.zig"); const NodeLive = @import("node_live.zig").NodeLive; +const Execution = js.Execution; const Mode = enum { tag, @@ -77,7 +78,7 @@ pub fn getByName(self: *HTMLCollection, name: []const u8, page: *Page) ?*Element }; } -pub fn iterator(self: *HTMLCollection, page: *Page) !*Iterator { +pub fn iterator(self: *HTMLCollection, exec: *const Execution) !*Iterator { return Iterator.init(.{ .list = self, .tw = switch (self._data) { @@ -94,7 +95,7 @@ pub fn iterator(self: *HTMLCollection, page: *Page) !*Iterator { .form => |*impl| .{ .form = impl._tw.clone() }, .empty => .empty, }, - }, page); + }, exec); } const GenericIterator = @import("iterator.zig").Entry; @@ -115,7 +116,7 @@ pub const Iterator = GenericIterator(struct { empty: void, }, - pub fn next(self: *@This(), _: *Page) ?*Element { + pub fn next(self: *@This(), _: *const Execution) ?*Element { return switch (self.list._data) { .tag => |*impl| impl.nextTw(&self.tw.tag), .tag_name => |*impl| impl.nextTw(&self.tw.tag_name), diff --git a/src/browser/webapi/collections/NodeList.zig b/src/browser/webapi/collections/NodeList.zig index 3d298de2..716c12d8 100644 --- a/src/browser/webapi/collections/NodeList.zig +++ b/src/browser/webapi/collections/NodeList.zig @@ -24,6 +24,7 @@ const js = @import("../../js/js.zig"); const Page = @import("../../Page.zig"); const Session = @import("../../Session.zig"); const Node = @import("../Node.zig"); +const Execution = js.Execution; const ChildNodes = @import("ChildNodes.zig"); const RadioNodeList = @import("RadioNodeList.zig"); @@ -78,28 +79,28 @@ pub fn getAtIndex(self: *NodeList, index: usize, page: *Page) !?*Node { }; } -pub fn keys(self: *NodeList, page: *Page) !*KeyIterator { - return .init(.{ .list = self }, page); +pub fn keys(self: *NodeList, exec: *const Execution) !*KeyIterator { + return .init(.{ .list = self }, exec); } -pub fn values(self: *NodeList, page: *Page) !*ValueIterator { - return .init(.{ .list = self }, page); +pub fn values(self: *NodeList, exec: *const Execution) !*ValueIterator { + return .init(.{ .list = self }, exec); } -pub fn entries(self: *NodeList, page: *Page) !*EntryIterator { - return .init(.{ .list = self }, page); +pub fn entries(self: *NodeList, exec: *const Execution) !*EntryIterator { + return .init(.{ .list = self }, exec); } -pub fn forEach(self: *NodeList, cb: js.Function, page: *Page) !void { +pub fn forEach(self: *NodeList, cb: js.Function, exec: *const Execution) !void { var i: i32 = 0; - var it = try self.values(page); + var it = try self.values(exec); // the iterator takes a reference against our list - defer self.releaseRef(page._session); + defer self.releaseRef(exec.context.page._session); while (true) : (i += 1) { - const next = try it.next(page); + const next = try it.next(exec); if (next.done) { return; } @@ -135,9 +136,9 @@ const Iterator = struct { self.list.acquireRef(); } - pub fn next(self: *Iterator, page: *Page) !?Entry { + pub fn next(self: *Iterator, exec: *const Execution) !?Entry { const index = self.index; - const node = try self.list.getAtIndex(index, page) orelse return null; + const node = try self.list.getAtIndex(index, exec.context.page) orelse return null; self.index = index + 1; return .{ index, node }; } diff --git a/src/browser/webapi/collections/iterator.zig b/src/browser/webapi/collections/iterator.zig index ba3c4ddc..8d0d6df7 100644 --- a/src/browser/webapi/collections/iterator.zig +++ b/src/browser/webapi/collections/iterator.zig @@ -21,6 +21,7 @@ const lp = @import("lightpanda"); const js = @import("../../js/js.zig"); const Page = @import("../../Page.zig"); const Session = @import("../../Session.zig"); +const Execution = js.Execution; pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type { const R = reflect(Inner, field); @@ -38,8 +39,8 @@ pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type { pub const js_as_object = true; }; - pub fn init(inner: Inner, page: *Page) !*Self { - const self = try page._factory.create(Self{ ._inner = inner }); + pub fn init(inner: Inner, executor: R.Executor) !*Self { + const self = try executor._factory.create(Self{ ._inner = inner }); if (@hasDecl(Inner, "acquireRef")) { self._inner.acquireRef(); @@ -62,8 +63,8 @@ pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type { self._rc.acquire(); } - pub fn next(self: *Self, page: *Page) if (R.has_error_return) anyerror!Result else Result { - const entry = (if (comptime R.has_error_return) try self._inner.next(page) else self._inner.next(page)) orelse { + pub fn next(self: *Self, executor: R.Executor) if (R.has_error_return) anyerror!Result else Result { + const entry = (if (comptime R.has_error_return) try self._inner.next(executor) else self._inner.next(executor)) orelse { return .{ .done = true, .value = null }; }; @@ -92,17 +93,22 @@ pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type { } fn reflect(comptime Inner: type, comptime field: ?[]const u8) Reflect { - const R = @typeInfo(@TypeOf(Inner.next)).@"fn".return_type.?; + const fn_info = @typeInfo(@TypeOf(Inner.next)).@"fn"; + const R = fn_info.return_type.?; const has_error_return = @typeInfo(R) == .error_union; + // The executor type is the last parameter of inner.next (after self) + const Executor = fn_info.params[1].type.?; return .{ .has_error_return = has_error_return, .ValueType = ValueType(unwrapOptional(unwrapError(R)), field), + .Executor = Executor, }; } const Reflect = struct { has_error_return: bool, ValueType: type, + Executor: type, }; fn unwrapError(comptime T: type) type { diff --git a/src/browser/webapi/net/FormData.zig b/src/browser/webapi/net/FormData.zig index 78baa587..644eeb6b 100644 --- a/src/browser/webapi/net/FormData.zig +++ b/src/browser/webapi/net/FormData.zig @@ -57,7 +57,7 @@ pub fn get(self: *const FormData, name: []const u8) ?[]const u8 { } pub fn getAll(self: *const FormData, name: []const u8, page: *Page) ![]const []const u8 { - return self._list.getAll(name, page); + return self._list.getAll(page.call_arena, name); } pub fn has(self: *const FormData, name: []const u8) bool { @@ -76,16 +76,16 @@ pub fn delete(self: *FormData, name: []const u8) void { self._list.delete(name, null); } -pub fn keys(self: *FormData, page: *Page) !*KeyValueList.KeyIterator { - return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._list }, page); +pub fn keys(self: *FormData, exec: *const js.Execution) !*KeyValueList.KeyIterator { + return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._list }, exec); } -pub fn values(self: *FormData, page: *Page) !*KeyValueList.ValueIterator { - return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._list }, page); +pub fn values(self: *FormData, exec: *const js.Execution) !*KeyValueList.ValueIterator { + return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._list }, exec); } -pub fn entries(self: *FormData, page: *Page) !*KeyValueList.EntryIterator { - return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._list }, page); +pub fn entries(self: *FormData, exec: *const js.Execution) !*KeyValueList.EntryIterator { + return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._list }, exec); } pub fn forEach(self: *FormData, cb_: js.Function, js_this_: ?js.Object) !void { diff --git a/src/browser/webapi/net/Headers.zig b/src/browser/webapi/net/Headers.zig index 62c505c6..615cc255 100644 --- a/src/browser/webapi/net/Headers.zig +++ b/src/browser/webapi/net/Headers.zig @@ -41,7 +41,7 @@ pub fn delete(self: *Headers, name: []const u8, page: *Page) void { pub fn get(self: *const Headers, name: []const u8, page: *Page) !?[]const u8 { const normalized_name = normalizeHeaderName(name, page); - const all_values = try self._list.getAll(normalized_name, page); + const all_values = try self._list.getAll(page.call_arena, normalized_name); if (all_values.len == 0) { return null; @@ -62,16 +62,16 @@ pub fn set(self: *Headers, name: []const u8, value: []const u8, page: *Page) !vo try self._list.set(page.arena, normalized_name, value); } -pub fn keys(self: *Headers, page: *Page) !*KeyValueList.KeyIterator { - return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._list }, page); +pub fn keys(self: *Headers, exec: *const js.Execution) !*KeyValueList.KeyIterator { + return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._list }, exec); } -pub fn values(self: *Headers, page: *Page) !*KeyValueList.ValueIterator { - return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._list }, page); +pub fn values(self: *Headers, exec: *const js.Execution) !*KeyValueList.ValueIterator { + return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._list }, exec); } -pub fn entries(self: *Headers, page: *Page) !*KeyValueList.EntryIterator { - return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._list }, page); +pub fn entries(self: *Headers, exec: *const js.Execution) !*KeyValueList.EntryIterator { + return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._list }, exec); } pub fn forEach(self: *Headers, cb_: js.Function, js_this_: ?js.Object) !void { diff --git a/src/browser/webapi/net/URLSearchParams.zig b/src/browser/webapi/net/URLSearchParams.zig index a4de0f82..101c82e3 100644 --- a/src/browser/webapi/net/URLSearchParams.zig +++ b/src/browser/webapi/net/URLSearchParams.zig @@ -26,6 +26,7 @@ const Allocator = std.mem.Allocator; const Page = @import("../../Page.zig"); const FormData = @import("FormData.zig"); const KeyValueList = @import("../KeyValueList.zig"); +const Execution = js.Execution; const URLSearchParams = @This(); @@ -38,12 +39,12 @@ const InitOpts = union(enum) { query_string: []const u8, }; -pub fn init(opts_: ?InitOpts, page: *Page) !*URLSearchParams { - const arena = page.arena; +pub fn init(opts_: ?InitOpts, exec: *const Execution) !*URLSearchParams { + const arena = exec.arena; const params: KeyValueList = blk: { const opts = opts_ orelse break :blk .empty; switch (opts) { - .query_string => |qs| break :blk try paramsFromString(arena, qs, &page.buf), + .query_string => |qs| break :blk try paramsFromString(arena, qs, exec.buf), .form_data => |fd| break :blk try KeyValueList.copy(arena, fd._list), .value => |js_val| { // Order matters here; Array is also an Object. @@ -51,24 +52,25 @@ pub fn init(opts_: ?InitOpts, page: *Page) !*URLSearchParams { break :blk try paramsFromArray(arena, js_val.toArray()); } if (js_val.isObject()) { - break :blk try KeyValueList.fromJsObject(arena, js_val.toObject(), null, page); + // normalizer is null, so page won't be used + break :blk try KeyValueList.fromJsObject(arena, js_val.toObject(), null, exec.context.page); } if (js_val.isString()) |js_str| { - break :blk try paramsFromString(arena, try js_str.toSliceWithAlloc(arena), &page.buf); + break :blk try paramsFromString(arena, try js_str.toSliceWithAlloc(arena), exec.buf); } return error.InvalidArgument; }, } }; - return page._factory.create(URLSearchParams{ + return exec._factory.create(URLSearchParams{ ._arena = arena, ._params = params, }); } -pub fn updateFromString(self: *URLSearchParams, query_string: []const u8, page: *Page) !void { - self._params = try paramsFromString(self._arena, query_string, &page.buf); +pub fn updateFromString(self: *URLSearchParams, query_string: []const u8, exec: *const Execution) !void { + self._params = try paramsFromString(self._arena, query_string, exec.buf); } pub fn getSize(self: *const URLSearchParams) usize { @@ -79,8 +81,8 @@ pub fn get(self: *const URLSearchParams, name: []const u8) ?[]const u8 { return self._params.get(name); } -pub fn getAll(self: *const URLSearchParams, name: []const u8, page: *Page) ![]const []const u8 { - return self._params.getAll(name, page); +pub fn getAll(self: *const URLSearchParams, name: []const u8, exec: *const Execution) ![]const []const u8 { + return self._params.getAll(exec.call_arena, name); } pub fn has(self: *const URLSearchParams, name: []const u8) bool { @@ -99,16 +101,16 @@ pub fn delete(self: *URLSearchParams, name: []const u8, value: ?[]const u8) void self._params.delete(name, value); } -pub fn keys(self: *URLSearchParams, page: *Page) !*KeyValueList.KeyIterator { - return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._params }, page); +pub fn keys(self: *URLSearchParams, exec: *const Execution) !*KeyValueList.KeyIterator { + return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._params }, exec); } -pub fn values(self: *URLSearchParams, page: *Page) !*KeyValueList.ValueIterator { - return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._params }, page); +pub fn values(self: *URLSearchParams, exec: *const Execution) !*KeyValueList.ValueIterator { + return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._params }, exec); } -pub fn entries(self: *URLSearchParams, page: *Page) !*KeyValueList.EntryIterator { - return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._params }, page); +pub fn entries(self: *URLSearchParams, exec: *const Execution) !*KeyValueList.EntryIterator { + return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._params }, exec); } pub fn toString(self: *const URLSearchParams, writer: *std.Io.Writer) !void { @@ -314,7 +316,7 @@ pub const Iterator = struct { const Entry = struct { []const u8, []const u8 }; - pub fn next(self: *Iterator, _: *Page) !?Iterator.Entry { + pub fn next(self: *Iterator, _: *const Execution) !?Iterator.Entry { const index = self.index; const items = self.list._params.items; if (index >= items.len) { @@ -352,8 +354,8 @@ pub const JsApi = struct { pub const sort = bridge.function(URLSearchParams.sort, .{}); pub const toString = bridge.function(_toString, .{}); - fn _toString(self: *const URLSearchParams, page: *Page) ![]const u8 { - var buf = std.Io.Writer.Allocating.init(page.call_arena); + fn _toString(self: *const URLSearchParams, exec: *const Execution) ![]const u8 { + var buf = std.Io.Writer.Allocating.init(exec.call_arena); try self.toString(&buf.writer); return buf.written(); }