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(); }