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 string = @import("../../string.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const WorkerGlobalScope = @import("../webapi/WorkerGlobalScope.zig");
const js = @import("js.zig"); const js = @import("js.zig");
const Local = @import("Local.zig"); const Local = @import("Local.zig");
@@ -54,9 +55,15 @@ fn initWithContext(self: *Caller, ctx: *Context, v8_context: *const v8.Context)
.isolate = ctx.isolate, .isolate = ctx.isolate,
}, },
.prev_local = ctx.local, .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; ctx.local = &self.local;
} }
@@ -87,7 +94,10 @@ pub fn deinit(self: *Caller) void {
ctx.call_depth = call_depth; ctx.call_depth = call_depth;
ctx.local = self.prev_local; 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 { 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, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
@field(args, "1") = idx; @field(args, "1") = idx;
if (@typeInfo(F).@"fn".params.len == 3) { 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); const ret = @call(.auto, func, args);
return handleIndexedReturn(T, F, true, local, ret, info, opts); 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, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
@field(args, "1") = try nameToString(local, @TypeOf(args.@"1"), name); @field(args, "1") = try nameToString(local, @TypeOf(args.@"1"), name);
if (@typeInfo(F).@"fn".params.len == 3) { 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); const ret = @call(.auto, func, args);
return handleIndexedReturn(T, F, true, local, ret, info, opts); 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, "1") = try nameToString(local, @TypeOf(args.@"1"), name);
@field(args, "2") = try local.jsValueToZig(@TypeOf(@field(args, "2")), js_value); @field(args, "2") = try local.jsValueToZig(@TypeOf(@field(args, "2")), js_value);
if (@typeInfo(F).@"fn".params.len == 4) { 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); const ret = @call(.auto, func, args);
return handleIndexedReturn(T, F, false, local, ret, info, opts); 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, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
@field(args, "1") = try nameToString(local, @TypeOf(args.@"1"), name); @field(args, "1") = try nameToString(local, @TypeOf(args.@"1"), name);
if (@typeInfo(F).@"fn".params.len == 3) { 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); const ret = @call(.auto, func, args);
return handleIndexedReturn(T, F, false, local, ret, info, opts); 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; var args: ParameterTypes(F) = undefined;
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis()); @field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
if (@typeInfo(F).@"fn".params.len == 2) { 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); const ret = @call(.auto, func, args);
return handleIndexedReturn(T, F, true, local, ret, info, opts); 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; 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 { fn isExecution(comptime T: type) bool {
return T == *js.Execution or T == *const js.Execution; 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. // 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,
@@ -706,16 +744,17 @@ fn getArgs(comptime F: type, comptime offset: usize, local: *const Local, info:
return args; 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 // from our params slice, because we don't want to bind it to
// a JS argument // a JS argument
if (comptime isPage(params[params.len - 1].type.?)) { const LastParamType = params[params.len - 1].type.?;
@field(args, tupleFieldName(params.len - 1 + offset)) = local.ctx.page; 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]; break :blk params[0 .. params.len - 1];
} }
// If the last parameter is Execution, set it from the context // 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; @field(args, tupleFieldName(params.len - 1 + offset)) = &local.ctx.execution;
break :blk params[0 .. params.len - 1]; break :blk params[0 .. params.len - 1];
} }

View File

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

View File

@@ -316,7 +316,7 @@ pub fn createContext(self: *Env, page: *Page, params: ContextParams) !*Context {
const context = try context_arena.create(Context); const context = try context_arena.create(Context);
context.* = .{ context.* = .{
.env = self, .env = self,
.page = page, .global = .{ .page = page },
.origin = origin, .origin = origin,
.id = context_id, .id = context_id,
.session = session, .session = session,
@@ -332,8 +332,16 @@ pub fn createContext(self: *Env, page: *Page, params: ContextParams) !*Context {
.identity_arena = params.identity_arena, .identity_arena = params.identity_arena,
.execution = undefined, .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 // 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, .call_arena = ctx.call_arena,
}; };
const page = ctx.page; switch (ctx.global) {
page.window.unhandledPromiseRejection(promise_event == v8.kPromiseRejectWithNoHandler, .{ .page => |page| {
.local = &local, page.window.unhandledPromiseRejection(promise_event == v8.kPromiseRejectWithNoHandler, .{
.handle = &message_handle, .local = &local,
}, page) catch |err| { .handle = &message_handle,
log.warn(.browser, "unhandled rejection handler", .{ .err = err }); }, 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 { 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, _scheduler: *Scheduler,
buf: []u8, buf: []u8,
pub fn fromContext(ctx: *Context) Execution { // Pointer to the url field (Page or WorkerGlobalScope) - allows access to current url even after navigation
const page = ctx.page; url: *[:0]const u8,
return .{
.context = ctx,
._factory = page._factory,
.arena = page.arena,
.call_arena = ctx.call_arena,
._scheduler = &ctx.scheduler,
.buf = &page.buf,
};
}

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")) { 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); return self.zigValueToJs(wrap, opts);
} }
@@ -409,7 +420,18 @@ pub fn zigValueToJs(self: *const Local, value: anytype, comptime opts: CallOpts)
// zig fmt: on // zig fmt: on
if (@hasDecl(T, "runtimeGenericWrap")) { 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); return self.zigValueToJs(wrap, opts);
} }

View File

@@ -400,14 +400,18 @@ pub fn unknownWindowPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8
return 0; return 0;
}; };
const page = local.ctx.page; // Only Page contexts have document.getElementById lookup
const document = page.document; switch (local.ctx.global) {
.page => |page| {
if (document.getElementById(property, page)) |el| { const document = page.document;
const js_val = local.zigValueToJs(el, .{}) catch return 0; if (document.getElementById(property, page)) |el| {
var pc = Caller.PropertyCallbackInfo{ .handle = handle.? }; const js_val = local.zigValueToJs(el, .{}) catch return 0;
pc.getReturnValue().set(js_val); var pc = Caller.PropertyCallbackInfo{ .handle = handle.? };
return 1; pc.getReturnValue().set(js_val);
return 1;
}
},
.worker => {}, // no global lookup in a worker
} }
if (comptime IS_DEBUG) { if (comptime IS_DEBUG) {
@@ -445,7 +449,8 @@ pub fn unknownWindowPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8
.{ "ApplePaySession", {} }, .{ "ApplePaySession", {} },
}); });
if (!ignored.has(property)) { 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; 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(.{}); const ignored = std.StaticStringMap(void).initComptime(.{});
if (!ignored.has(property)) { 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; logUnknownProperty(local, key) catch return 0;
} }
// not intercepted // 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 { pub const Entry = struct {
name: String, name: String,
@@ -62,14 +62,14 @@ pub fn copy(arena: Allocator, original: KeyValueList) !KeyValueList {
return list; 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 it = try js_obj.nameIterator();
var list = KeyValueList.init(); var list = KeyValueList.init();
try list.ensureTotalCapacity(arena, it.count); try list.ensureTotalCapacity(arena, it.count);
while (try it.next()) |name| { while (try it.next()) |name| {
const js_value = try js_obj.get(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(.{ list._entries.appendAssumeCapacity(.{
.name = try String.init(arena, normalized, .{}), .name = try String.init(arena, normalized, .{}),
@@ -80,12 +80,12 @@ pub fn fromJsObject(arena: Allocator, js_obj: js.Object, comptime normalizer: ?N
return list; 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(); var list = KeyValueList.init();
try list.ensureTotalCapacity(arena, kvs.len); try list.ensureTotalCapacity(arena, kvs.len);
for (kvs) |pair| { 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(.{ list._entries.appendAssumeCapacity(.{
.name = try String.init(arena, normalized, .{}), .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 { pub fn init(url: [:0]const u8, base_: ?[:0]const u8, exec: *const Execution) !*URL {
const arena = exec.arena; const arena = exec.arena;
const page = exec.context.page; const context_url = exec.url.*;
if (std.mem.eql(u8, url, "about:blank")) { if (std.mem.eql(u8, url, "about:blank")) {
return exec._factory.create(URL{ 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 url_is_absolute = @import("../URL.zig").isCompleteHTTPUrl(url);
const base = if (base_) |b| blk: { 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) { if (url_is_absolute) {
break :blk page.url; break :blk context_url;
} }
// For relative URLs, base must be a valid absolute URL // For relative URLs, base must be a valid absolute URL
if (!@import("../URL.zig").isCompleteHTTPUrl(b)) { 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; break :blk b;
} else if (!url_is_absolute) { } else if (!url_is_absolute) {
return error.TypeError; return error.TypeError;
} else page.url; } else context_url;
const raw = try resolve(arena, base, url, .{ .always_dupe = true }); 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 { pub fn setHref(self: *URL, value: []const u8, exec: *const Execution) !void {
const page = exec.context.page; const base = if (U.isCompleteHTTPUrl(value)) exec.url.* else self._raw;
const base = if (U.isCompleteHTTPUrl(value)) page.url else self._raw;
const raw = try U.resolve(self._arena orelse exec.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;

View File

@@ -16,23 +16,41 @@
// You should have received a copy of the GNU Affero General Public License // 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/>. // 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 base64 = @import("encoding/base64.zig");
const Console = @import("Console.zig"); const Console = @import("Console.zig");
const Crypto = @import("Crypto.zig"); const Crypto = @import("Crypto.zig");
const EventTarget = @import("EventTarget.zig"); const EventTarget = @import("EventTarget.zig");
const Factory = @import("../Factory.zig");
const Performance = @import("Performance.zig"); const Performance = @import("Performance.zig");
const Session = @import("../Session.zig");
const Allocator = std.mem.Allocator;
const WorkerGlobalScope = @This(); 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, _proto: *EventTarget,
_console: Console = .init, _console: Console = .init,
_crypto: Crypto = .init, _crypto: Crypto = .init,
_performance: Performance, _performance: Performance,
_on_error: ?js.Function.Global = null, _on_error: ?JS.Function.Global = null,
_on_rejection_handled: ?js.Function.Global = null, _on_rejection_handled: ?JS.Function.Global = null,
_on_unhandled_rejection: ?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 { pub fn asEventTarget(self: *WorkerGlobalScope) *EventTarget {
return self._proto; return self._proto;
@@ -54,7 +72,7 @@ pub fn getPerformance(self: *WorkerGlobalScope) *Performance {
return &self._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; return self._on_error;
} }
@@ -62,7 +80,7 @@ pub fn setOnError(self: *WorkerGlobalScope, setter: ?FunctionSetter) void {
self._on_error = getFunctionFromSetter(setter); 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; return self._on_rejection_handled;
} }
@@ -70,7 +88,7 @@ pub fn setOnRejectionHandled(self: *WorkerGlobalScope, setter: ?FunctionSetter)
self._on_rejection_handled = getFunctionFromSetter(setter); 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; return self._on_unhandled_rejection;
} }
@@ -78,15 +96,15 @@ pub fn setOnUnhandledRejection(self: *WorkerGlobalScope, setter: ?FunctionSetter
self._on_unhandled_rejection = getFunctionFromSetter(setter); 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); 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); 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(); return value.structuredClone();
} }
@@ -96,11 +114,11 @@ pub fn structuredClone(_: *const WorkerGlobalScope, value: js.Value) !js.Value {
// TODO: Timer functions - need scheduler integration // TODO: Timer functions - need scheduler integration
const FunctionSetter = union(enum) { const FunctionSetter = union(enum) {
func: js.Function.Global, func: JS.Function.Global,
anything: js.Value, anything: JS.Value,
}; };
fn getFunctionFromSetter(setter_: ?FunctionSetter) ?js.Function.Global { fn getFunctionFromSetter(setter_: ?FunctionSetter) ?JS.Function.Global {
const setter = setter_ orelse return null; const setter = setter_ orelse return null;
return switch (setter) { return switch (setter) {
.func => |func| func, .func => |func| func,
@@ -109,7 +127,7 @@ fn getFunctionFromSetter(setter_: ?FunctionSetter) ?js.Function.Global {
} }
pub const JsApi = struct { pub const JsApi = struct {
pub const bridge = js.Bridge(WorkerGlobalScope); pub const bridge = JS.Bridge(WorkerGlobalScope);
pub const Meta = struct { pub const Meta = struct {
pub const name = "WorkerGlobalScope"; pub const name = "WorkerGlobalScope";

View File

@@ -23,7 +23,6 @@ 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.
@@ -138,9 +137,9 @@ const Iterator = struct {
const Entry = struct { u32, *Node }; 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 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; self.index = index + 1;
return .{ index, node }; return .{ index, node };
} }

View File

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

View File

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

View File

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

View File

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