Make context work with Page of WGS

A context can be created for either a Page or a Worker. This removes the
Context.page field and replaces it with a Context.global union.
This commit is contained in:
Karl Seguin
2026-04-03 15:56:19 +08:00
parent 224a7ca0fe
commit 4dd014de41
14 changed files with 270 additions and 120 deletions

View File

@@ -21,6 +21,7 @@ const log = @import("../../log.zig");
const string = @import("../../string.zig");
const Page = @import("../Page.zig");
const WorkerGlobalScope = @import("../webapi/WorkerGlobalScope.zig");
const js = @import("js.zig");
const Local = @import("Local.zig");
@@ -54,9 +55,15 @@ fn initWithContext(self: *Caller, ctx: *Context, v8_context: *const v8.Context)
.isolate = ctx.isolate,
},
.prev_local = ctx.local,
.prev_context = ctx.page.js,
.prev_context = switch (ctx.global) {
.page => |page| page.js,
.worker => |worker| worker.js,
},
};
ctx.page.js = ctx;
switch (ctx.global) {
.page => |page| page.js = ctx,
.worker => |worker| worker.js = ctx,
}
ctx.local = &self.local;
}
@@ -87,7 +94,10 @@ pub fn deinit(self: *Caller) void {
ctx.call_depth = call_depth;
ctx.local = self.prev_local;
ctx.page.js = self.prev_context;
switch (ctx.global) {
.page => |page| page.js = self.prev_context,
.worker => |worker| worker.js = self.prev_context,
}
}
pub const CallOpts = struct {
@@ -169,7 +179,7 @@ fn _getIndex(comptime T: type, local: *const Local, func: anytype, idx: u32, inf
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
@field(args, "1") = idx;
if (@typeInfo(F).@"fn".params.len == 3) {
@field(args, "2") = local.ctx.page;
@field(args, "2") = getGlobalArg(@TypeOf(args.@"2"), local.ctx);
}
const ret = @call(.auto, func, args);
return handleIndexedReturn(T, F, true, local, ret, info, opts);
@@ -196,7 +206,7 @@ fn _getNamedIndex(comptime T: type, local: *const Local, func: anytype, name: *c
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
@field(args, "1") = try nameToString(local, @TypeOf(args.@"1"), name);
if (@typeInfo(F).@"fn".params.len == 3) {
@field(args, "2") = local.ctx.page;
@field(args, "2") = getGlobalArg(@TypeOf(args.@"2"), local.ctx);
}
const ret = @call(.auto, func, args);
return handleIndexedReturn(T, F, true, local, ret, info, opts);
@@ -224,7 +234,7 @@ fn _setNamedIndex(comptime T: type, local: *const Local, func: anytype, name: *c
@field(args, "1") = try nameToString(local, @TypeOf(args.@"1"), name);
@field(args, "2") = try local.jsValueToZig(@TypeOf(@field(args, "2")), js_value);
if (@typeInfo(F).@"fn".params.len == 4) {
@field(args, "3") = local.ctx.page;
@field(args, "3") = getGlobalArg(@TypeOf(args.@"3"), local.ctx);
}
const ret = @call(.auto, func, args);
return handleIndexedReturn(T, F, false, local, ret, info, opts);
@@ -250,7 +260,7 @@ fn _deleteNamedIndex(comptime T: type, local: *const Local, func: anytype, name:
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
@field(args, "1") = try nameToString(local, @TypeOf(args.@"1"), name);
if (@typeInfo(F).@"fn".params.len == 3) {
@field(args, "2") = local.ctx.page;
@field(args, "2") = getGlobalArg(@TypeOf(args.@"2"), local.ctx);
}
const ret = @call(.auto, func, args);
return handleIndexedReturn(T, F, false, local, ret, info, opts);
@@ -276,7 +286,7 @@ fn _getEnumerator(comptime T: type, local: *const Local, func: anytype, info: Pr
var args: ParameterTypes(F) = undefined;
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
if (@typeInfo(F).@"fn".params.len == 2) {
@field(args, "1") = local.ctx.page;
@field(args, "1") = getGlobalArg(@TypeOf(args.@"1"), local.ctx);
}
const ret = @call(.auto, func, args);
return handleIndexedReturn(T, F, true, local, ret, info, opts);
@@ -434,10 +444,38 @@ fn isPage(comptime T: type) bool {
return T == *Page or T == *const Page;
}
fn isWorker(comptime T: type) bool {
return T == *WorkerGlobalScope or T == *const WorkerGlobalScope;
}
fn isExecution(comptime T: type) bool {
return T == *js.Execution or T == *const js.Execution;
}
fn getGlobalArg(comptime T: type, ctx: *Context) T {
if (comptime isPage(T)) {
return switch (ctx.global) {
.page => |page| page,
.worker => {
if (comptime IS_DEBUG) std.debug.assert(false);
unreachable;
},
};
}
if (comptime isWorker(T)) {
return switch (ctx.global) {
.page => {
if (comptime IS_DEBUG) std.debug.assert(false);
unreachable;
},
.worker => |worker| worker,
};
}
@compileError("Unsupported global arg type: " ++ @typeName(T));
}
// These wrap the raw v8 C API to provide a cleaner interface.
pub const FunctionCallbackInfo = struct {
handle: *const v8.FunctionCallbackInfo,
@@ -706,16 +744,17 @@ fn getArgs(comptime F: type, comptime offset: usize, local: *const Local, info:
return args;
}
// If the last parameter is the Page, set it, and exclude it
// If the last parameter is the Page or Worker, set it, and exclude it
// from our params slice, because we don't want to bind it to
// a JS argument
if (comptime isPage(params[params.len - 1].type.?)) {
@field(args, tupleFieldName(params.len - 1 + offset)) = local.ctx.page;
const LastParamType = params[params.len - 1].type.?;
if (comptime isPage(LastParamType) or isWorker(LastParamType)) {
@field(args, tupleFieldName(params.len - 1 + offset)) = getGlobalArg(LastParamType, local.ctx);
break :blk params[0 .. params.len - 1];
}
// If the last parameter is Execution, set it from the context
if (comptime isExecution(params[params.len - 1].type.?)) {
if (comptime isExecution(LastParamType)) {
@field(args, tupleFieldName(params.len - 1 + offset)) = &local.ctx.execution;
break :blk params[0 .. params.len - 1];
}

View File

@@ -30,6 +30,7 @@ const Execution = @import("Execution.zig");
const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
const ScriptManager = @import("../ScriptManager.zig");
const WorkerGlobalScope = @import("../webapi/WorkerGlobalScope.zig");
const v8 = js.v8;
const Caller = js.Caller;
@@ -38,12 +39,17 @@ const Allocator = std.mem.Allocator;
const IS_DEBUG = @import("builtin").mode == .Debug;
// Loosely maps to a Browser Page.
// Loosely maps to a Browser Page or Worker.
const Context = @This();
pub const GlobalScope = union(enum) {
page: *Page,
worker: *WorkerGlobalScope,
};
id: usize,
env: *Env,
page: *Page,
global: GlobalScope,
session: *Session,
isolate: js.Isolate,
@@ -262,7 +268,16 @@ pub fn toLocal(self: *Context, global: anytype) js.Local.ToLocalReturnType(@Type
}
pub fn getIncumbent(self: *Context) *Page {
return fromC(v8.v8__Isolate__GetIncumbentContext(self.env.isolate.handle).?).?.page;
const ctx = fromC(v8.v8__Isolate__GetIncumbentContext(self.env.isolate.handle).?).?;
return switch (ctx.global) {
.page => |page| page,
.worker => {
if (comptime IS_DEBUG) {
std.debug.assert(false);
}
unreachable;
},
};
}
pub fn stringToPersistedFunction(
@@ -532,7 +547,10 @@ pub fn dynamicModuleCallback(
if (resource_value.isNullOrUndefined()) {
// will only be null / undefined in extreme cases (e.g. WPT tests)
// where you're
break :blk self.page.base();
break :blk switch (self.global) {
.page => |page| page.base(),
.worker => |worker| worker.base(),
};
}
break :blk js.String.toSliceZ(.{ .local = &local, .handle = resource_name.? }) catch |err| {
@@ -872,17 +890,26 @@ pub fn enter(self: *Context, hs: *js.HandleScope) Entered {
const isolate = self.isolate;
js.HandleScope.init(hs, isolate);
const page = self.page;
const original = page.js;
page.js = self;
const original = switch (self.global) {
.page => |page| blk: {
const orig = page.js;
page.js = self;
break :blk orig;
},
.worker => |worker| blk: {
const orig = worker.js;
worker.js = self;
break :blk orig;
},
};
const handle: *const v8.Context = @ptrCast(v8.v8__Global__Get(&self.handle, isolate.handle));
v8.v8__Context__Enter(handle);
return .{ .original = original, .handle = handle, .handle_scope = hs };
return .{ .original = original, .handle = handle, .handle_scope = hs, .global = self.global };
}
const Entered = struct {
// the context we should restore on the page
// the context we should restore on the page/worker
original: *Context,
// the handle of the entered context
@@ -890,8 +917,13 @@ const Entered = struct {
handle_scope: *js.HandleScope,
global: GlobalScope,
pub fn exit(self: Entered) void {
self.original.page.js = self.original;
switch (self.global) {
.page => |page| page.js = self.original,
.worker => |worker| worker.js = self.original,
}
v8.v8__Context__Exit(self.handle);
self.handle_scope.deinit();
}
@@ -900,7 +932,15 @@ const Entered = struct {
pub fn queueMutationDelivery(self: *Context) !void {
self.enqueueMicrotask(struct {
fn run(ctx: *Context) void {
ctx.page.deliverMutations();
switch (ctx.global) {
.page => |page| page.deliverMutations(),
.worker => {
if (comptime IS_DEBUG) {
std.debug.assert(false);
}
unreachable;
},
}
}
}.run);
}
@@ -908,7 +948,15 @@ pub fn queueMutationDelivery(self: *Context) !void {
pub fn queueIntersectionChecks(self: *Context) !void {
self.enqueueMicrotask(struct {
fn run(ctx: *Context) void {
ctx.page.performScheduledIntersectionChecks();
switch (ctx.global) {
.page => |page| page.performScheduledIntersectionChecks(),
.worker => {
if (comptime IS_DEBUG) {
std.debug.assert(false);
}
unreachable;
},
}
}
}.run);
}
@@ -916,7 +964,15 @@ pub fn queueIntersectionChecks(self: *Context) !void {
pub fn queueIntersectionDelivery(self: *Context) !void {
self.enqueueMicrotask(struct {
fn run(ctx: *Context) void {
ctx.page.deliverIntersections();
switch (ctx.global) {
.page => |page| page.deliverIntersections(),
.worker => {
if (comptime IS_DEBUG) {
std.debug.assert(false);
}
unreachable;
},
}
}
}.run);
}
@@ -924,7 +980,15 @@ pub fn queueIntersectionDelivery(self: *Context) !void {
pub fn queueSlotchangeDelivery(self: *Context) !void {
self.enqueueMicrotask(struct {
fn run(ctx: *Context) void {
ctx.page.deliverSlotchangeEvents();
switch (ctx.global) {
.page => |page| page.deliverSlotchangeEvents(),
.worker => {
if (comptime IS_DEBUG) {
std.debug.assert(false);
}
unreachable;
},
}
}
}.run);
}

View File

@@ -316,7 +316,7 @@ pub fn createContext(self: *Env, page: *Page, params: ContextParams) !*Context {
const context = try context_arena.create(Context);
context.* = .{
.env = self,
.page = page,
.global = .{ .page = page },
.origin = origin,
.id = context_id,
.session = session,
@@ -332,8 +332,16 @@ pub fn createContext(self: *Env, page: *Page, params: ContextParams) !*Context {
.identity_arena = params.identity_arena,
.execution = undefined,
};
// Initialize execution after context is created since it contains self-references
context.execution = js.Execution.fromContext(context);
context.execution = .{
.buf = &page.buf,
.context = context,
.arena = page.arena,
.call_arena = params.call_arena,
._factory = page._factory,
._scheduler = &context.scheduler,
.url = &page.url,
};
{
// Multiple contexts can be created for the same Window (via CDP). We only
@@ -531,13 +539,19 @@ fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) v
.call_arena = ctx.call_arena,
};
const page = ctx.page;
page.window.unhandledPromiseRejection(promise_event == v8.kPromiseRejectWithNoHandler, .{
.local = &local,
.handle = &message_handle,
}, page) catch |err| {
log.warn(.browser, "unhandled rejection handler", .{ .err = err });
};
switch (ctx.global) {
.page => |page| {
page.window.unhandledPromiseRejection(promise_event == v8.kPromiseRejectWithNoHandler, .{
.local = &local,
.handle = &message_handle,
}, page) catch |err| {
log.warn(.browser, "unhandled rejection handler", .{ .err = err });
};
},
.worker => {
// TODO: Worker promise rejection handling
},
}
}
fn fatalCallback(c_location: [*c]const u8, c_message: [*c]const u8) callconv(.c) void {

View File

@@ -43,14 +43,5 @@ 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,
};
}
// Pointer to the url field (Page or WorkerGlobalScope) - allows access to current url even after navigation
url: *[:0]const u8,

View File

@@ -332,7 +332,18 @@ pub fn zigValueToJs(self: *const Local, value: anytype, comptime opts: CallOpts)
}
if (@typeInfo(ptr.child) == .@"struct" and @hasDecl(ptr.child, "runtimeGenericWrap")) {
const wrap = try value.runtimeGenericWrap(self.ctx.page);
const page = switch (self.ctx.global) {
.page => |p| p,
.worker => {
// No Worker-related API currently uses this, so haven't
// added support for it
if (comptime IS_DEBUG) {
std.debug.assert(false);
}
unreachable;
},
};
const wrap = try value.runtimeGenericWrap(page);
return self.zigValueToJs(wrap, opts);
}
@@ -409,7 +420,18 @@ pub fn zigValueToJs(self: *const Local, value: anytype, comptime opts: CallOpts)
// zig fmt: on
if (@hasDecl(T, "runtimeGenericWrap")) {
const wrap = try value.runtimeGenericWrap(self.ctx.page);
const page = switch (self.ctx.global) {
.page => |p| p,
.worker => {
// No Worker-related API currently uses this, so haven't
// added support for it
if (comptime IS_DEBUG) {
std.debug.assert(false);
}
unreachable;
},
};
const wrap = try value.runtimeGenericWrap(page);
return self.zigValueToJs(wrap, opts);
}

View File

@@ -400,14 +400,18 @@ pub fn unknownWindowPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8
return 0;
};
const page = local.ctx.page;
const document = page.document;
if (document.getElementById(property, page)) |el| {
const js_val = local.zigValueToJs(el, .{}) catch return 0;
var pc = Caller.PropertyCallbackInfo{ .handle = handle.? };
pc.getReturnValue().set(js_val);
return 1;
// Only Page contexts have document.getElementById lookup
switch (local.ctx.global) {
.page => |page| {
const document = page.document;
if (document.getElementById(property, page)) |el| {
const js_val = local.zigValueToJs(el, .{}) catch return 0;
var pc = Caller.PropertyCallbackInfo{ .handle = handle.? };
pc.getReturnValue().set(js_val);
return 1;
}
},
.worker => {}, // no global lookup in a worker
}
if (comptime IS_DEBUG) {
@@ -445,7 +449,8 @@ pub fn unknownWindowPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8
.{ "ApplePaySession", {} },
});
if (!ignored.has(property)) {
const key = std.fmt.bufPrint(&local.ctx.page.buf, "Window:{s}", .{property}) catch return 0;
var buf: [2048]u8 = undefined;
const key = std.fmt.bufPrint(&buf, "Window:{s}", .{property}) catch return 0;
logUnknownProperty(local, key) catch return 0;
}
}
@@ -508,7 +513,8 @@ pub fn unknownObjectPropertyCallback(comptime JsApi: type) *const fn (?*const v8
const ignored = std.StaticStringMap(void).initComptime(.{});
if (!ignored.has(property)) {
const key = std.fmt.bufPrint(&local.ctx.page.buf, "{s}:{s}", .{ if (@hasDecl(JsApi.Meta, "name")) JsApi.Meta.name else @typeName(JsApi), property }) catch return 0;
var buf: [2048]u8 = undefined;
const key = std.fmt.bufPrint(&buf, "{s}:{s}", .{ if (@hasDecl(JsApi.Meta, "name")) JsApi.Meta.name else @typeName(JsApi), property }) catch return 0;
logUnknownProperty(local, key) catch return 0;
}
// not intercepted

View File

@@ -34,7 +34,7 @@ pub fn registerTypes() []const type {
};
}
const Normalizer = *const fn ([]const u8, *Page) []const u8;
const Normalizer = *const fn ([]const u8, []u8) []const u8;
pub const Entry = struct {
name: String,
@@ -62,14 +62,14 @@ pub fn copy(arena: Allocator, original: KeyValueList) !KeyValueList {
return list;
}
pub fn fromJsObject(arena: Allocator, js_obj: js.Object, comptime normalizer: ?Normalizer, page: *Page) !KeyValueList {
pub fn fromJsObject(arena: Allocator, js_obj: js.Object, comptime normalizer: ?Normalizer, buf: []u8) !KeyValueList {
var it = try js_obj.nameIterator();
var list = KeyValueList.init();
try list.ensureTotalCapacity(arena, it.count);
while (try it.next()) |name| {
const js_value = try js_obj.get(name);
const normalized = if (comptime normalizer) |n| n(name, page) else name;
const normalized = if (comptime normalizer) |n| n(name, buf) else name;
list._entries.appendAssumeCapacity(.{
.name = try String.init(arena, normalized, .{}),
@@ -80,12 +80,12 @@ pub fn fromJsObject(arena: Allocator, js_obj: js.Object, comptime normalizer: ?N
return list;
}
pub fn fromArray(arena: Allocator, kvs: []const [2][]const u8, comptime normalizer: ?Normalizer, page: *Page) !KeyValueList {
pub fn fromArray(arena: Allocator, kvs: []const [2][]const u8, comptime normalizer: ?Normalizer, buf: []u8) !KeyValueList {
var list = KeyValueList.init();
try list.ensureTotalCapacity(arena, kvs.len);
for (kvs) |pair| {
const normalized = if (comptime normalizer) |n| n(pair[0], page) else pair[0];
const normalized = if (comptime normalizer) |n| n(pair[0], buf) else pair[0];
list._entries.appendAssumeCapacity(.{
.name = try String.init(arena, normalized, .{}),

View File

@@ -39,7 +39,7 @@ pub const eqlDocument = @import("../URL.zig").eqlDocument;
pub fn init(url: [:0]const u8, base_: ?[:0]const u8, exec: *const Execution) !*URL {
const arena = exec.arena;
const page = exec.context.page;
const context_url = exec.url.*;
if (std.mem.eql(u8, url, "about:blank")) {
return exec._factory.create(URL{
@@ -50,9 +50,9 @@ pub fn init(url: [:0]const u8, base_: ?[:0]const u8, exec: *const Execution) !*U
const url_is_absolute = @import("../URL.zig").isCompleteHTTPUrl(url);
const base = if (base_) |b| blk: {
// If URL is absolute, base is ignored (but we still use page.url internally)
// If URL is absolute, base is ignored (but we still use context url internally)
if (url_is_absolute) {
break :blk page.url;
break :blk context_url;
}
// For relative URLs, base must be a valid absolute URL
if (!@import("../URL.zig").isCompleteHTTPUrl(b)) {
@@ -61,7 +61,7 @@ pub fn init(url: [:0]const u8, base_: ?[:0]const u8, exec: *const Execution) !*U
break :blk b;
} else if (!url_is_absolute) {
return error.TypeError;
} else page.url;
} else context_url;
const raw = try resolve(arena, base, url, .{ .always_dupe = true });
@@ -149,8 +149,7 @@ pub fn getSearchParams(self: *URL, exec: *const Execution) !*URLSearchParams {
}
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)) exec.url.* else self._raw;
const raw = try U.resolve(self._arena orelse exec.arena, base, value, .{ .always_dupe = true });
self._raw = raw;

View File

@@ -16,23 +16,41 @@
// 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/>.
const js = @import("../js/js.zig");
const std = @import("std");
const JS = @import("../js/js.zig");
const base64 = @import("encoding/base64.zig");
const Console = @import("Console.zig");
const Crypto = @import("Crypto.zig");
const EventTarget = @import("EventTarget.zig");
const Factory = @import("../Factory.zig");
const Performance = @import("Performance.zig");
const Session = @import("../Session.zig");
const Allocator = std.mem.Allocator;
const WorkerGlobalScope = @This();
// Infrastructure fields (similar to Page)
_session: *Session,
_factory: *Factory,
arena: Allocator,
url: [:0]const u8,
buf: [1024]u8 = undefined, // same size as page.buf
js: *JS.Context = undefined,
// WebAPI fields
_proto: *EventTarget,
_console: Console = .init,
_crypto: Crypto = .init,
_performance: Performance,
_on_error: ?js.Function.Global = null,
_on_rejection_handled: ?js.Function.Global = null,
_on_unhandled_rejection: ?js.Function.Global = null,
_on_error: ?JS.Function.Global = null,
_on_rejection_handled: ?JS.Function.Global = null,
_on_unhandled_rejection: ?JS.Function.Global = null,
pub fn base(self: *const WorkerGlobalScope) [:0]const u8 {
return self.url;
}
pub fn asEventTarget(self: *WorkerGlobalScope) *EventTarget {
return self._proto;
@@ -54,7 +72,7 @@ pub fn getPerformance(self: *WorkerGlobalScope) *Performance {
return &self._performance;
}
pub fn getOnError(self: *const WorkerGlobalScope) ?js.Function.Global {
pub fn getOnError(self: *const WorkerGlobalScope) ?JS.Function.Global {
return self._on_error;
}
@@ -62,7 +80,7 @@ pub fn setOnError(self: *WorkerGlobalScope, setter: ?FunctionSetter) void {
self._on_error = getFunctionFromSetter(setter);
}
pub fn getOnRejectionHandled(self: *const WorkerGlobalScope) ?js.Function.Global {
pub fn getOnRejectionHandled(self: *const WorkerGlobalScope) ?JS.Function.Global {
return self._on_rejection_handled;
}
@@ -70,7 +88,7 @@ pub fn setOnRejectionHandled(self: *WorkerGlobalScope, setter: ?FunctionSetter)
self._on_rejection_handled = getFunctionFromSetter(setter);
}
pub fn getOnUnhandledRejection(self: *const WorkerGlobalScope) ?js.Function.Global {
pub fn getOnUnhandledRejection(self: *const WorkerGlobalScope) ?JS.Function.Global {
return self._on_unhandled_rejection;
}
@@ -78,15 +96,15 @@ pub fn setOnUnhandledRejection(self: *WorkerGlobalScope, setter: ?FunctionSetter
self._on_unhandled_rejection = getFunctionFromSetter(setter);
}
pub fn btoa(_: *const WorkerGlobalScope, input: []const u8, exec: *js.Execution) ![]const u8 {
pub fn btoa(_: *const WorkerGlobalScope, input: []const u8, exec: *JS.Execution) ![]const u8 {
return base64.encode(exec.call_arena, input);
}
pub fn atob(_: *const WorkerGlobalScope, input: []const u8, exec: *js.Execution) ![]const u8 {
pub fn atob(_: *const WorkerGlobalScope, input: []const u8, exec: *JS.Execution) ![]const u8 {
return base64.decode(exec.call_arena, input);
}
pub fn structuredClone(_: *const WorkerGlobalScope, value: js.Value) !js.Value {
pub fn structuredClone(_: *const WorkerGlobalScope, value: JS.Value) !JS.Value {
return value.structuredClone();
}
@@ -96,11 +114,11 @@ pub fn structuredClone(_: *const WorkerGlobalScope, value: js.Value) !js.Value {
// TODO: Timer functions - need scheduler integration
const FunctionSetter = union(enum) {
func: js.Function.Global,
anything: js.Value,
func: JS.Function.Global,
anything: JS.Value,
};
fn getFunctionFromSetter(setter_: ?FunctionSetter) ?js.Function.Global {
fn getFunctionFromSetter(setter_: ?FunctionSetter) ?JS.Function.Global {
const setter = setter_ orelse return null;
return switch (setter) {
.func => |func| func,
@@ -109,7 +127,7 @@ fn getFunctionFromSetter(setter_: ?FunctionSetter) ?js.Function.Global {
}
pub const JsApi = struct {
pub const bridge = js.Bridge(WorkerGlobalScope);
pub const bridge = JS.Bridge(WorkerGlobalScope);
pub const Meta = struct {
pub const name = "WorkerGlobalScope";

View File

@@ -23,7 +23,6 @@ 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.
@@ -138,9 +137,9 @@ const Iterator = struct {
const Entry = struct { u32, *Node };
pub fn next(self: *Iterator, exec: *const Execution) !?Entry {
pub fn next(self: *Iterator, page: *const Page) !?Entry {
const index = self.index;
const node = try self.list.getAtIndex(index, exec.context.page) orelse return null;
const node = try self.list.getAtIndex(index, page) orelse return null;
self.index = index + 1;
return .{ index, node };
}

View File

@@ -24,7 +24,6 @@ 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();
@@ -203,16 +202,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, exec: *const Execution) !*KeyIterator {
return .init(.{ .list = self }, exec);
pub fn keys(self: *DOMTokenList, page: *Page) !*KeyIterator {
return .init(.{ .list = self }, page);
}
pub fn values(self: *DOMTokenList, exec: *const Execution) !*ValueIterator {
return .init(.{ .list = self }, exec);
pub fn values(self: *DOMTokenList, page: *Page) !*ValueIterator {
return .init(.{ .list = self }, page);
}
pub fn entries(self: *DOMTokenList, exec: *const Execution) !*EntryIterator {
return .init(.{ .list = self }, exec);
pub fn entries(self: *DOMTokenList, page: *Page) !*EntryIterator {
return .init(.{ .list = self }, page);
}
pub fn forEach(self: *DOMTokenList, cb_: js.Function, js_this_: ?js.Object, page: *Page) !void {
@@ -282,9 +281,9 @@ const Iterator = struct {
const Entry = struct { u32, []const u8 };
pub fn next(self: *Iterator, exec: *const Execution) !?Entry {
pub fn next(self: *Iterator, page: *Page) !?Entry {
const index = self.index;
const node = try self.list.item(index, exec.context.page) orelse return null;
const node = try self.list.item(index, page) orelse return null;
self.index = index + 1;
return .{ index, node };
}

View File

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

View File

@@ -20,8 +20,8 @@ pub const InitOpts = union(enum) {
pub fn init(opts_: ?InitOpts, page: *Page) !*Headers {
const list = if (opts_) |opts| switch (opts) {
.obj => |obj| try KeyValueList.copy(page.arena, obj._list),
.js_obj => |js_obj| try KeyValueList.fromJsObject(page.arena, js_obj, normalizeHeaderName, page),
.strings => |kvs| try KeyValueList.fromArray(page.arena, kvs, normalizeHeaderName, page),
.js_obj => |js_obj| try KeyValueList.fromJsObject(page.arena, js_obj, normalizeHeaderName, &page.buf),
.strings => |kvs| try KeyValueList.fromArray(page.arena, kvs, normalizeHeaderName, &page.buf),
} else KeyValueList.init();
return page._factory.create(Headers{
@@ -30,17 +30,17 @@ pub fn init(opts_: ?InitOpts, page: *Page) !*Headers {
}
pub fn append(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void {
const normalized_name = normalizeHeaderName(name, page);
const normalized_name = normalizeHeaderName(name, &page.buf);
try self._list.append(page.arena, normalized_name, value);
}
pub fn delete(self: *Headers, name: []const u8, page: *Page) void {
const normalized_name = normalizeHeaderName(name, page);
const normalized_name = normalizeHeaderName(name, &page.buf);
self._list.delete(normalized_name, null);
}
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.buf);
const all_values = try self._list.getAll(page.call_arena, normalized_name);
if (all_values.len == 0) {
@@ -53,12 +53,12 @@ pub fn get(self: *const Headers, name: []const u8, page: *Page) !?[]const u8 {
}
pub fn has(self: *const Headers, name: []const u8, page: *Page) bool {
const normalized_name = normalizeHeaderName(name, page);
const normalized_name = normalizeHeaderName(name, &page.buf);
return self._list.has(normalized_name);
}
pub fn set(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void {
const normalized_name = normalizeHeaderName(name, page);
const normalized_name = normalizeHeaderName(name, &page.buf);
try self._list.set(page.arena, normalized_name, value);
}
@@ -94,11 +94,11 @@ pub fn populateHttpHeader(self: *Headers, allocator: Allocator, http_headers: *h
}
}
fn normalizeHeaderName(name: []const u8, page: *Page) []const u8 {
if (name.len > page.buf.len) {
fn normalizeHeaderName(name: []const u8, buf: []u8) []const u8 {
if (name.len > buf.len) {
return name;
}
return std.ascii.lowerString(&page.buf, name);
return std.ascii.lowerString(buf, name);
}
pub const JsApi = struct {

View File

@@ -53,7 +53,7 @@ pub fn init(opts_: ?InitOpts, exec: *const Execution) !*URLSearchParams {
}
if (js_val.isObject()) {
// normalizer is null, so page won't be used
break :blk try KeyValueList.fromJsObject(arena, js_val.toObject(), null, exec.context.page);
break :blk try KeyValueList.fromJsObject(arena, js_val.toObject(), null, exec.buf);
}
if (js_val.isString()) |js_str| {
break :blk try paramsFromString(arena, try js_str.toSliceWithAlloc(arena), exec.buf);