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.
This commit is contained in:
Karl Seguin
2026-04-03 09:36:40 +08:00
parent b6020e4770
commit 226d1ff183
17 changed files with 194 additions and 101 deletions

View File

@@ -434,6 +434,10 @@ fn isPage(comptime T: type) bool {
return T == *Page or T == *const Page; 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. // These wrap the raw v8 C API to provide a cleaner interface.
pub const FunctionCallbackInfo = struct { pub const FunctionCallbackInfo = struct {
handle: *const v8.FunctionCallbackInfo, 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]; 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. // bound to a JavaScript value.
break :blk params; break :blk params;
}; };
@@ -759,7 +769,9 @@ fn getArgs(comptime F: type, comptime offset: usize, local: *const Local, info:
} }
if (comptime isPage(param.type.?)) { 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) { } else if (i >= js_parameter_count) {
if (@typeInfo(param.type.?) != .optional) { if (@typeInfo(param.type.?) != .optional) {
return error.InvalidArgument; return error.InvalidArgument;

View File

@@ -25,6 +25,7 @@ const bridge = @import("bridge.zig");
const Env = @import("Env.zig"); const Env = @import("Env.zig");
const Origin = @import("Origin.zig"); const Origin = @import("Origin.zig");
const Scheduler = @import("Scheduler.zig"); const Scheduler = @import("Scheduler.zig");
const Execution = @import("Execution.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const Session = @import("../Session.zig"); const Session = @import("../Session.zig");
@@ -111,6 +112,10 @@ script_manager: ?*ScriptManager,
// Our macrotasks // Our macrotasks
scheduler: Scheduler, 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 {}, unknown_properties: (if (IS_DEBUG) std.StringHashMapUnmanaged(UnknownPropertyStat) else void) = if (IS_DEBUG) .{} else {},
const ModuleEntry = struct { const ModuleEntry = struct {

View File

@@ -330,7 +330,10 @@ pub fn createContext(self: *Env, page: *Page, params: ContextParams) !*Context {
.scheduler = .init(context_arena), .scheduler = .init(context_arena),
.identity = params.identity, .identity = params.identity,
.identity_arena = params.identity_arena, .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 // Multiple contexts can be created for the same Window (via CDP). We only

View File

@@ -0,0 +1,56 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.
//! 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,
};
}

View File

@@ -27,6 +27,7 @@ pub const Caller = @import("Caller.zig");
pub const Origin = @import("Origin.zig"); pub const Origin = @import("Origin.zig");
pub const Identity = @import("Identity.zig"); pub const Identity = @import("Identity.zig");
pub const Context = @import("Context.zig"); pub const Context = @import("Context.zig");
pub const Execution = @import("Execution.zig");
pub const Local = @import("Local.zig"); pub const Local = @import("Local.zig");
pub const Inspector = @import("Inspector.zig"); pub const Inspector = @import("Inspector.zig");
pub const Snapshot = @import("Snapshot.zig"); pub const Snapshot = @import("Snapshot.zig");

View File

@@ -22,6 +22,7 @@ const String = @import("../../string.zig").String;
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const Execution = js.Execution;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
@@ -111,12 +112,11 @@ pub fn get(self: *const KeyValueList, name: []const u8) ?[]const u8 {
return null; return null;
} }
pub fn getAll(self: *const KeyValueList, name: []const u8, page: *Page) ![]const []const u8 { pub fn getAll(self: *const KeyValueList, allocator: Allocator, name: []const u8) ![]const []const u8 {
const arena = page.call_arena;
var arr: std.ArrayList([]const u8) = .empty; var arr: std.ArrayList([]const u8) = .empty;
for (self._entries.items) |*entry| { for (self._entries.items) |*entry| {
if (entry.name.eqlSlice(name)) { if (entry.name.eqlSlice(name)) {
try arr.append(arena, entry.value.str()); try arr.append(allocator, entry.value.str());
} }
} }
return arr.items; return arr.items;
@@ -260,7 +260,7 @@ pub const Iterator = struct {
pub const Entry = struct { []const u8, []const u8 }; 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 index = self.index;
const entries = self.kv._entries.items; const entries = self.kv._entries.items;
if (index >= entries.len) { if (index >= entries.len) {

View File

@@ -27,7 +27,7 @@ const Location = @This();
_url: *URL, _url: *URL,
pub fn init(raw_url: [:0]const u8, page: *Page) !*Location { 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{ return page._factory.create(Location{
._url = url, ._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 { 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 { 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 { 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 { 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 { pub const JsApi = struct {

View File

@@ -23,6 +23,7 @@ const U = @import("../URL.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const URLSearchParams = @import("net/URLSearchParams.zig"); const URLSearchParams = @import("net/URLSearchParams.zig");
const Blob = @import("Blob.zig"); const Blob = @import("Blob.zig");
const Execution = js.Execution;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
@@ -36,11 +37,12 @@ _search_params: ?*URLSearchParams = null,
pub const resolve = @import("../URL.zig").resolve; pub const resolve = @import("../URL.zig").resolve;
pub const eqlDocument = @import("../URL.zig").eqlDocument; pub const eqlDocument = @import("../URL.zig").eqlDocument;
pub fn init(url: [:0]const u8, base_: ?[:0]const u8, page: *Page) !*URL { pub fn init(url: [:0]const u8, base_: ?[:0]const u8, exec: *const Execution) !*URL {
const arena = page.arena; const arena = exec.arena;
const page = exec.context.page;
if (std.mem.eql(u8, url, "about:blank")) { if (std.mem.eql(u8, url, "about:blank")) {
return page._factory.create(URL{ return exec._factory.create(URL{
._raw = "about:blank", ._raw = "about:blank",
._arena = arena, ._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 }); const raw = try resolve(arena, base, url, .{ .always_dupe = true });
return page._factory.create(URL{ return exec._factory.create(URL{
._raw = raw, ._raw = raw,
._arena = arena, ._arena = arena,
}); });
@@ -107,20 +109,20 @@ pub fn getPort(self: *const URL) []const u8 {
return U.getPort(self._raw); return U.getPort(self._raw);
} }
pub fn getOrigin(self: *const URL, page: *const Page) ![]const u8 { pub fn getOrigin(self: *const URL, exec: *const Execution) ![]const u8 {
return (try U.getOrigin(page.call_arena, self._raw)) orelse { return (try U.getOrigin(exec.call_arena, self._raw)) orelse {
// yes, a null string, that's what the spec wants // yes, a null string, that's what the spec wants
return "null"; 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 searchParams has been accessed, generate search from it
if (self._search_params) |sp| { if (self._search_params) |sp| {
if (sp.getSize() == 0) { if (sp.getSize() == 0) {
return ""; 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 buf.writer.writeByte('?');
try sp.toString(&buf.writer); try sp.toString(&buf.writer);
return buf.written(); return buf.written();
@@ -132,30 +134,31 @@ pub fn getHash(self: *const URL) []const u8 {
return U.getHash(self._raw); 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| { if (self._search_params) |sp| {
return sp; return sp;
} }
// Get current search string (without the '?') // 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 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; self._search_params = params;
return 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 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; self._raw = raw;
// Update existing searchParams if it exists // Update existing searchParams if it exists
if (self._search_params) |sp| { if (self._search_params) |sp| {
const search = U.getSearch(raw); const search = U.getSearch(raw);
const search_value = if (search.len > 0) search[1..] else ""; 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); 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; const allocator = self._arena orelse return error.NoAllocator;
self._raw = try U.setSearch(self._raw, value, allocator); 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| { if (self._search_params) |sp| {
const search = U.getSearch(self._raw); const search = U.getSearch(self._raw);
const search_value = if (search.len > 0) search[1..] else ""; 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); 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 { const sp = self._search_params orelse {
return self._raw; return self._raw;
}; };
@@ -217,7 +220,7 @@ pub fn toString(self: *const URL, page: *const Page) ![:0]const u8 {
const hash = self.getHash(); const hash = self.getHash();
// Build the new URL string // 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); try buf.writer.writeAll(base);
// Add / if missing (e.g., "https://example.com" -> "https://example.com/") // Add / if missing (e.g., "https://example.com" -> "https://example.com/")

View File

@@ -18,10 +18,12 @@
const std = @import("std"); const std = @import("std");
const js = @import("../../js/js.zig");
const Node = @import("../Node.zig"); const Node = @import("../Node.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig"); const Session = @import("../../Session.zig");
const GenericIterator = @import("iterator.zig").Entry; const GenericIterator = @import("iterator.zig").Entry;
const Execution = js.Execution;
// Optimized for node.childNodes, which has to be a live list. // Optimized for node.childNodes, which has to be a live list.
// No need to go through a TreeWalker or add any filtering. // No need to go through a TreeWalker or add any filtering.
@@ -136,9 +138,9 @@ const Iterator = struct {
const Entry = struct { u32, *Node }; 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 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; self.index = index + 1;
return .{ index, node }; return .{ index, node };
} }

View File

@@ -24,6 +24,7 @@ const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Element = @import("../Element.zig"); const Element = @import("../Element.zig");
const GenericIterator = @import("iterator.zig").Entry; const GenericIterator = @import("iterator.zig").Entry;
const Execution = js.Execution;
pub const DOMTokenList = @This(); pub const DOMTokenList = @This();
@@ -43,7 +44,7 @@ const Lookup = std.StringArrayHashMapUnmanaged(void);
const WHITESPACE = " \t\n\r\x0C"; const WHITESPACE = " \t\n\r\x0C";
pub fn length(self: *const DOMTokenList, page: *Page) !u32 { 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()); return @intCast(tokens.count());
} }
@@ -82,8 +83,8 @@ pub fn add(self: *DOMTokenList, tokens: []const []const u8, page: *Page) !void {
try validateToken(token); try validateToken(token);
} }
var lookup = try self.getTokens(page);
const allocator = page.call_arena; const allocator = page.call_arena;
var lookup = try self.getTokens(allocator);
try lookup.ensureUnusedCapacity(allocator, tokens.len); try lookup.ensureUnusedCapacity(allocator, tokens.len);
for (tokens) |token| { for (tokens) |token| {
@@ -98,7 +99,7 @@ pub fn remove(self: *DOMTokenList, tokens: []const []const u8, page: *Page) !voi
try validateToken(token); try validateToken(token);
} }
var lookup = try self.getTokens(page); var lookup = try self.getTokens(page.call_arena);
for (tokens) |token| { for (tokens) |token| {
_ = lookup.orderedRemove(token); _ = lookup.orderedRemove(token);
} }
@@ -149,7 +150,8 @@ pub fn replace(self: *DOMTokenList, old_token: []const u8, new_token: []const u8
return error.InvalidCharacterError; 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 // Check if old_token exists
if (!lookup.contains(old_token)) { if (!lookup.contains(old_token)) {
@@ -162,7 +164,6 @@ pub fn replace(self: *DOMTokenList, old_token: []const u8, new_token: []const u8
return true; return true;
} }
const allocator = page.call_arena;
// Build new token list preserving order but replacing old with new // Build new token list preserving order but replacing old with new
var new_tokens = try std.ArrayList([]const u8).initCapacity(allocator, lookup.count()); var new_tokens = try std.ArrayList([]const u8).initCapacity(allocator, lookup.count());
var replaced_old = false; 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); try self._element.setAttribute(self._attribute_name, value, page);
} }
pub fn keys(self: *DOMTokenList, page: *Page) !*KeyIterator { pub fn keys(self: *DOMTokenList, exec: *const Execution) !*KeyIterator {
return .init(.{ .list = self }, page); return .init(.{ .list = self }, exec);
} }
pub fn values(self: *DOMTokenList, page: *Page) !*ValueIterator { pub fn values(self: *DOMTokenList, exec: *const Execution) !*ValueIterator {
return .init(.{ .list = self }, page); return .init(.{ .list = self }, exec);
} }
pub fn entries(self: *DOMTokenList, page: *Page) !*EntryIterator { pub fn entries(self: *DOMTokenList, exec: *const Execution) !*EntryIterator {
return .init(.{ .list = self }, page); return .init(.{ .list = self }, exec);
} }
pub fn forEach(self: *DOMTokenList, cb_: js.Function, js_this_: ?js.Object, page: *Page) !void { 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(); const value = self.getValue();
if (value.len == 0) { if (value.len == 0) {
return .empty; return .empty;
} }
var list: Lookup = .empty; var list: Lookup = .empty;
const allocator = page.call_arena;
try list.ensureTotalCapacity(allocator, 4); try list.ensureTotalCapacity(allocator, 4);
var it = std.mem.tokenizeAny(u8, value, WHITESPACE); var it = std.mem.tokenizeAny(u8, value, WHITESPACE);
@@ -282,9 +282,9 @@ const Iterator = struct {
const Entry = struct { u32, []const u8 }; 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 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; self.index = index + 1;
return .{ index, node }; return .{ index, node };
} }

View File

@@ -24,6 +24,7 @@ const Page = @import("../../Page.zig");
const Node = @import("../Node.zig"); const Node = @import("../Node.zig");
const Element = @import("../Element.zig"); const Element = @import("../Element.zig");
const TreeWalker = @import("../TreeWalker.zig"); const TreeWalker = @import("../TreeWalker.zig");
const Execution = js.Execution;
const HTMLAllCollection = @This(); 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(.{ return Iterator.init(.{
.list = self, .list = self,
.tw = self._tw.clone(), .tw = self._tw.clone(),
}, page); }, exec);
} }
const GenericIterator = @import("iterator.zig").Entry; const GenericIterator = @import("iterator.zig").Entry;
@@ -145,7 +146,7 @@ pub const Iterator = GenericIterator(struct {
list: *HTMLAllCollection, list: *HTMLAllCollection,
tw: TreeWalker.FullExcludeSelf, tw: TreeWalker.FullExcludeSelf,
pub fn next(self: *@This(), _: *Page) ?*Element { pub fn next(self: *@This(), _: *const Execution) ?*Element {
while (self.tw.next()) |node| { while (self.tw.next()) |node| {
if (node.is(Element)) |el| { if (node.is(Element)) |el| {
return el; return el;

View File

@@ -23,6 +23,7 @@ const Page = @import("../../Page.zig");
const Element = @import("../Element.zig"); const Element = @import("../Element.zig");
const TreeWalker = @import("../TreeWalker.zig"); const TreeWalker = @import("../TreeWalker.zig");
const NodeLive = @import("node_live.zig").NodeLive; const NodeLive = @import("node_live.zig").NodeLive;
const Execution = js.Execution;
const Mode = enum { const Mode = enum {
tag, 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(.{ return Iterator.init(.{
.list = self, .list = self,
.tw = switch (self._data) { .tw = switch (self._data) {
@@ -94,7 +95,7 @@ pub fn iterator(self: *HTMLCollection, page: *Page) !*Iterator {
.form => |*impl| .{ .form = impl._tw.clone() }, .form => |*impl| .{ .form = impl._tw.clone() },
.empty => .empty, .empty => .empty,
}, },
}, page); }, exec);
} }
const GenericIterator = @import("iterator.zig").Entry; const GenericIterator = @import("iterator.zig").Entry;
@@ -115,7 +116,7 @@ pub const Iterator = GenericIterator(struct {
empty: void, empty: void,
}, },
pub fn next(self: *@This(), _: *Page) ?*Element { pub fn next(self: *@This(), _: *const Execution) ?*Element {
return switch (self.list._data) { return switch (self.list._data) {
.tag => |*impl| impl.nextTw(&self.tw.tag), .tag => |*impl| impl.nextTw(&self.tw.tag),
.tag_name => |*impl| impl.nextTw(&self.tw.tag_name), .tag_name => |*impl| impl.nextTw(&self.tw.tag_name),

View File

@@ -24,6 +24,7 @@ const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig"); const Session = @import("../../Session.zig");
const Node = @import("../Node.zig"); const Node = @import("../Node.zig");
const Execution = js.Execution;
const ChildNodes = @import("ChildNodes.zig"); const ChildNodes = @import("ChildNodes.zig");
const RadioNodeList = @import("RadioNodeList.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 { pub fn keys(self: *NodeList, exec: *const Execution) !*KeyIterator {
return .init(.{ .list = self }, page); return .init(.{ .list = self }, exec);
} }
pub fn values(self: *NodeList, page: *Page) !*ValueIterator { pub fn values(self: *NodeList, exec: *const Execution) !*ValueIterator {
return .init(.{ .list = self }, page); return .init(.{ .list = self }, exec);
} }
pub fn entries(self: *NodeList, page: *Page) !*EntryIterator { pub fn entries(self: *NodeList, exec: *const Execution) !*EntryIterator {
return .init(.{ .list = self }, page); 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 i: i32 = 0;
var it = try self.values(page); var it = try self.values(exec);
// the iterator takes a reference against our list // the iterator takes a reference against our list
defer self.releaseRef(page._session); defer self.releaseRef(exec.context.page._session);
while (true) : (i += 1) { while (true) : (i += 1) {
const next = try it.next(page); const next = try it.next(exec);
if (next.done) { if (next.done) {
return; return;
} }
@@ -135,9 +136,9 @@ const Iterator = struct {
self.list.acquireRef(); 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 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; self.index = index + 1;
return .{ index, node }; return .{ index, node };
} }

View File

@@ -21,6 +21,7 @@ const lp = @import("lightpanda");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig"); const Session = @import("../../Session.zig");
const Execution = js.Execution;
pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type { pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type {
const R = reflect(Inner, field); 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 const js_as_object = true;
}; };
pub fn init(inner: Inner, page: *Page) !*Self { pub fn init(inner: Inner, executor: R.Executor) !*Self {
const self = try page._factory.create(Self{ ._inner = inner }); const self = try executor._factory.create(Self{ ._inner = inner });
if (@hasDecl(Inner, "acquireRef")) { if (@hasDecl(Inner, "acquireRef")) {
self._inner.acquireRef(); self._inner.acquireRef();
@@ -62,8 +63,8 @@ pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type {
self._rc.acquire(); self._rc.acquire();
} }
pub fn next(self: *Self, page: *Page) if (R.has_error_return) anyerror!Result else Result { 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(page) else self._inner.next(page)) orelse { const entry = (if (comptime R.has_error_return) try self._inner.next(executor) else self._inner.next(executor)) orelse {
return .{ .done = true, .value = null }; 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 { 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; 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 .{ return .{
.has_error_return = has_error_return, .has_error_return = has_error_return,
.ValueType = ValueType(unwrapOptional(unwrapError(R)), field), .ValueType = ValueType(unwrapOptional(unwrapError(R)), field),
.Executor = Executor,
}; };
} }
const Reflect = struct { const Reflect = struct {
has_error_return: bool, has_error_return: bool,
ValueType: type, ValueType: type,
Executor: type,
}; };
fn unwrapError(comptime T: type) type { fn unwrapError(comptime T: type) type {

View File

@@ -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 { 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 { 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); self._list.delete(name, null);
} }
pub fn keys(self: *FormData, page: *Page) !*KeyValueList.KeyIterator { pub fn keys(self: *FormData, exec: *const js.Execution) !*KeyValueList.KeyIterator {
return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._list }, page); return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._list }, exec);
} }
pub fn values(self: *FormData, page: *Page) !*KeyValueList.ValueIterator { pub fn values(self: *FormData, exec: *const js.Execution) !*KeyValueList.ValueIterator {
return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._list }, page); return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._list }, exec);
} }
pub fn entries(self: *FormData, page: *Page) !*KeyValueList.EntryIterator { pub fn entries(self: *FormData, exec: *const js.Execution) !*KeyValueList.EntryIterator {
return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._list }, page); return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._list }, exec);
} }
pub fn forEach(self: *FormData, cb_: js.Function, js_this_: ?js.Object) !void { pub fn forEach(self: *FormData, cb_: js.Function, js_this_: ?js.Object) !void {

View File

@@ -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 { pub fn get(self: *const Headers, name: []const u8, page: *Page) !?[]const u8 {
const normalized_name = normalizeHeaderName(name, page); 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) { if (all_values.len == 0) {
return null; 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); try self._list.set(page.arena, normalized_name, value);
} }
pub fn keys(self: *Headers, page: *Page) !*KeyValueList.KeyIterator { pub fn keys(self: *Headers, exec: *const js.Execution) !*KeyValueList.KeyIterator {
return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._list }, page); return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._list }, exec);
} }
pub fn values(self: *Headers, page: *Page) !*KeyValueList.ValueIterator { pub fn values(self: *Headers, exec: *const js.Execution) !*KeyValueList.ValueIterator {
return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._list }, page); return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._list }, exec);
} }
pub fn entries(self: *Headers, page: *Page) !*KeyValueList.EntryIterator { pub fn entries(self: *Headers, exec: *const js.Execution) !*KeyValueList.EntryIterator {
return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._list }, page); return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._list }, exec);
} }
pub fn forEach(self: *Headers, cb_: js.Function, js_this_: ?js.Object) !void { pub fn forEach(self: *Headers, cb_: js.Function, js_this_: ?js.Object) !void {

View File

@@ -26,6 +26,7 @@ const Allocator = std.mem.Allocator;
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const FormData = @import("FormData.zig"); const FormData = @import("FormData.zig");
const KeyValueList = @import("../KeyValueList.zig"); const KeyValueList = @import("../KeyValueList.zig");
const Execution = js.Execution;
const URLSearchParams = @This(); const URLSearchParams = @This();
@@ -38,12 +39,12 @@ const InitOpts = union(enum) {
query_string: []const u8, query_string: []const u8,
}; };
pub fn init(opts_: ?InitOpts, page: *Page) !*URLSearchParams { pub fn init(opts_: ?InitOpts, exec: *const Execution) !*URLSearchParams {
const arena = page.arena; const arena = exec.arena;
const params: KeyValueList = blk: { const params: KeyValueList = blk: {
const opts = opts_ orelse break :blk .empty; const opts = opts_ orelse break :blk .empty;
switch (opts) { 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), .form_data => |fd| break :blk try KeyValueList.copy(arena, fd._list),
.value => |js_val| { .value => |js_val| {
// Order matters here; Array is also an Object. // 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()); break :blk try paramsFromArray(arena, js_val.toArray());
} }
if (js_val.isObject()) { 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| { 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 error.InvalidArgument;
}, },
} }
}; };
return page._factory.create(URLSearchParams{ return exec._factory.create(URLSearchParams{
._arena = arena, ._arena = arena,
._params = params, ._params = params,
}); });
} }
pub fn updateFromString(self: *URLSearchParams, query_string: []const u8, page: *Page) !void { pub fn updateFromString(self: *URLSearchParams, query_string: []const u8, exec: *const Execution) !void {
self._params = try paramsFromString(self._arena, query_string, &page.buf); self._params = try paramsFromString(self._arena, query_string, exec.buf);
} }
pub fn getSize(self: *const URLSearchParams) usize { 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); return self._params.get(name);
} }
pub fn getAll(self: *const URLSearchParams, name: []const u8, page: *Page) ![]const []const u8 { pub fn getAll(self: *const URLSearchParams, name: []const u8, exec: *const Execution) ![]const []const u8 {
return self._params.getAll(name, page); return self._params.getAll(exec.call_arena, name);
} }
pub fn has(self: *const URLSearchParams, name: []const u8) bool { 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); self._params.delete(name, value);
} }
pub fn keys(self: *URLSearchParams, page: *Page) !*KeyValueList.KeyIterator { pub fn keys(self: *URLSearchParams, exec: *const Execution) !*KeyValueList.KeyIterator {
return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._params }, page); return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._params }, exec);
} }
pub fn values(self: *URLSearchParams, page: *Page) !*KeyValueList.ValueIterator { pub fn values(self: *URLSearchParams, exec: *const Execution) !*KeyValueList.ValueIterator {
return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._params }, page); return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._params }, exec);
} }
pub fn entries(self: *URLSearchParams, page: *Page) !*KeyValueList.EntryIterator { pub fn entries(self: *URLSearchParams, exec: *const Execution) !*KeyValueList.EntryIterator {
return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._params }, page); return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._params }, exec);
} }
pub fn toString(self: *const URLSearchParams, writer: *std.Io.Writer) !void { 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 }; 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 index = self.index;
const items = self.list._params.items; const items = self.list._params.items;
if (index >= items.len) { if (index >= items.len) {
@@ -352,8 +354,8 @@ pub const JsApi = struct {
pub const sort = bridge.function(URLSearchParams.sort, .{}); pub const sort = bridge.function(URLSearchParams.sort, .{});
pub const toString = bridge.function(_toString, .{}); pub const toString = bridge.function(_toString, .{});
fn _toString(self: *const URLSearchParams, page: *Page) ![]const u8 { fn _toString(self: *const URLSearchParams, exec: *const Execution) ![]const u8 {
var buf = std.Io.Writer.Allocating.init(page.call_arena); var buf = std.Io.Writer.Allocating.init(exec.call_arena);
try self.toString(&buf.writer); try self.toString(&buf.writer);
return buf.written(); return buf.written();
} }