Merge pull request #1117 from lightpanda-io/cleanup_js

This is the last of the big changes to the js code
This commit is contained in:
Karl Seguin
2025-10-06 15:33:21 +08:00
committed by GitHub
31 changed files with 882 additions and 944 deletions

View File

@@ -663,7 +663,7 @@ const Script = struct {
.cacheable = cacheable,
});
const js_context = page.main_context;
const js_context = page.js;
var try_catch: js.TryCatch = undefined;
try_catch.init(js_context);
defer try_catch.deinit();
@@ -707,10 +707,10 @@ const Script = struct {
switch (callback) {
.string => |str| {
var try_catch: js.TryCatch = undefined;
try_catch.init(page.main_context);
try_catch.init(page.js);
defer try_catch.deinit();
_ = page.main_context.exec(str, typ) catch |err| {
_ = page.js.exec(str, typ) catch |err| {
const msg = try_catch.err(page.arena) catch @errorName(err) orelse "unknown";
log.warn(.user_script, "script callback", .{
.url = self.url,

View File

@@ -79,9 +79,7 @@ pub fn _replace(self: *CSSStyleSheet, text: []const u8, page: *Page) !js.Promise
// TODO: clear self.css_rules
// parse text and re-populate self.css_rules
const resolver = page.main_context.createPromiseResolver();
try resolver.resolve({});
return resolver.promise();
return page.js.resolvePromise({});
}
pub fn _replaceSync(self: *CSSStyleSheet, text: []const u8) !void {

View File

@@ -49,7 +49,7 @@ pub fn get_pending(self: *const Animation) bool {
pub fn get_finished(self: *Animation, page: *Page) !js.Promise {
if (self.finished_resolver == null) {
const resolver = page.main_context.createPromiseResolver();
const resolver = page.js.createPromiseResolver(.none);
try resolver.resolve(self);
self.finished_resolver = resolver;
}
@@ -59,7 +59,7 @@ pub fn get_finished(self: *Animation, page: *Page) !js.Promise {
pub fn get_ready(self: *Animation, page: *Page) !js.Promise {
// never resolved, because we're always "finished"
if (self.ready_resolver == null) {
const resolver = page.main_context.createPromiseResolver();
const resolver = page.js.createPromiseResolver(.none);
self.ready_resolver = resolver;
}
return self.ready_resolver.?.promise();

View File

@@ -304,7 +304,7 @@ pub const Document = struct {
return obj;
}
const obj = try page.main_context.newArray(0).persist();
const obj = try page.js.createArray(0).persist();
state.adopted_style_sheets = obj;
return obj;
}

View File

@@ -50,7 +50,7 @@ pub const ShadowRoot = struct {
return obj;
}
const obj = try page.main_context.newArray(0).persist();
const obj = try page.js.createArray(0).persist();
self.adopted_style_sheets = obj;
return obj;
}

View File

@@ -24,7 +24,6 @@ const Page = @import("../page.zig").Page;
const iterator = @import("../iterator/iterator.zig");
const v8 = @import("v8");
// https://developer.mozilla.org/en-US/docs/Web/API/Headers
const Headers = @This();

View File

@@ -27,8 +27,6 @@ const Response = @import("./Response.zig");
const Http = @import("../../http/Http.zig");
const ReadableStream = @import("../streams/ReadableStream.zig");
const v8 = @import("v8");
const Headers = @import("Headers.zig");
const HeadersInit = @import("Headers.zig").HeadersInit;
@@ -245,20 +243,15 @@ pub fn _bytes(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}
const resolver = page.main_context.createPromiseResolver();
try resolver.resolve(self.body);
self.body_used = true;
return resolver.promise();
return page.js.resolvePromise(self.body);
}
pub fn _json(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}
const resolver = page.main_context.createPromiseResolver();
self.body_used = true;
if (self.body) |body| {
const p = std.json.parseFromSliceLeaky(
@@ -271,25 +264,17 @@ pub fn _json(self: *Response, page: *Page) !js.Promise {
return error.SyntaxError;
};
try resolver.resolve(p);
} else {
try resolver.resolve(null);
return page.js.resolvePromise(p);
}
self.body_used = true;
return resolver.promise();
return page.js.resolvePromise(null);
}
pub fn _text(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}
const resolver = page.main_context.createPromiseResolver();
try resolver.resolve(self.body);
self.body_used = true;
return resolver.promise();
return page.js.resolvePromise(self.body);
}
const testing = @import("../../testing.zig");

View File

@@ -20,8 +20,6 @@ const std = @import("std");
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const v8 = @import("v8");
const HttpClient = @import("../../http/Client.zig");
const Http = @import("../../http/Http.zig");
const URL = @import("../../url.zig").URL;
@@ -170,14 +168,8 @@ pub fn _bytes(self: *Response, page: *Page) !js.Promise {
return error.TypeError;
}
const resolver = js.PromiseResolver{
.context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
};
try resolver.resolve(self.body);
self.body_used = true;
return resolver.promise();
return page.js.resolvePromise(self.body);
}
pub fn _json(self: *Response, page: *Page) !js.Promise {
@@ -185,9 +177,8 @@ pub fn _json(self: *Response, page: *Page) !js.Promise {
return error.TypeError;
}
const resolver = page.main_context.createPromiseResolver();
if (self.body) |body| {
self.body_used = true;
const p = std.json.parseFromSliceLeaky(
std.json.Value,
page.call_arena,
@@ -198,25 +189,18 @@ pub fn _json(self: *Response, page: *Page) !js.Promise {
return error.SyntaxError;
};
try resolver.resolve(p);
} else {
try resolver.resolve(null);
return page.js.resolvePromise(p);
}
self.body_used = true;
return resolver.promise();
return page.js.resolvePromise(null);
}
pub fn _text(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}
const resolver = page.main_context.createPromiseResolver();
try resolver.resolve(self.body);
self.body_used = true;
return resolver.promise();
return page.js.resolvePromise(self.body);
}
const testing = @import("../../testing.zig");

View File

@@ -131,7 +131,7 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !js.Promis
try page.requestCookie(.{}).headersForRequest(arena, req.url, &headers);
const resolver = try page.main_context.createPersistentPromiseResolver(.page);
const resolver = try page.js.createPromiseResolver(.page);
const fetch_ctx = try arena.create(FetchContext);
fetch_ctx.* = .{

View File

@@ -118,7 +118,7 @@ pub const AbortSignal = struct {
};
pub fn _throwIfAborted(self: *const AbortSignal, page: *Page) ThrowIfAborted {
if (self.aborted) {
const ex = page.main_context.throw(self.reason orelse DEFAULT_REASON);
const ex = page.js.throw(self.reason orelse DEFAULT_REASON);
return .{ .exception = ex };
}
return .{ .undefined = {} };

View File

@@ -71,7 +71,7 @@ pub fn get_state(self: *History, page: *Page) !?js.Value {
if (self.current) |curr| {
const entry = self.stack.items[curr];
if (entry.state) |state| {
const value = try js.Value.fromJson(page.main_context, state);
const value = try js.Value.fromJson(page.js, state);
return value;
} else {
return null;
@@ -201,7 +201,7 @@ pub const PopStateEvent = struct {
pub fn get_state(self: *const PopStateEvent, page: *Page) !?js.Value {
if (self.state) |state| {
const value = try js.Value.fromJson(page.main_context, state);
const value = try js.Value.fromJson(page.js, state);
return value;
} else {
return null;

View File

@@ -37,7 +37,6 @@ const domcss = @import("../dom/css.zig");
const Css = @import("../css/css.zig").Css;
const EventHandler = @import("../events/event.zig").EventHandler;
const v8 = @import("v8");
const Request = @import("../fetch/Request.zig");
const fetchFn = @import("../fetch/fetch.zig").fetch;

View File

@@ -77,9 +77,9 @@ pub fn constructor(self: *Caller, comptime Struct: type, comptime named_function
const this = info.getThis();
if (@typeInfo(ReturnType) == .error_union) {
const non_error_res = res catch |err| return err;
_ = try Context.mapZigInstanceToJs(self.v8_context, this, non_error_res);
_ = try self.context.mapZigInstanceToJs(this, non_error_res);
} else {
_ = try Context.mapZigInstanceToJs(self.v8_context, this, res);
_ = try self.context.mapZigInstanceToJs(this, res);
}
info.getReturnValue().set(this);
}
@@ -209,7 +209,7 @@ fn namedSetOrDeleteCall(res: anytype, has_value: bool) !u8 {
}
fn nameToString(self: *Caller, name: v8.Name) ![]const u8 {
return js.valueToString(self.call_arena, .{ .handle = name.handle }, self.isolate, self.v8_context);
return self.context.valueToString(.{ .handle = name.handle }, .{});
}
fn isSelfReceiver(comptime Struct: type, comptime named_function: NamedFunction) bool {
@@ -258,7 +258,7 @@ pub fn handleError(self: *Caller, comptime Struct: type, comptime named_function
if (comptime @import("builtin").mode == .Debug and @hasDecl(@TypeOf(info), "length")) {
if (log.enabled(.js, .warn)) {
logFunctionCallError(self.call_arena, self.isolate, self.v8_context, err, named_function.full_name, info);
self.logFunctionCallError(err, named_function.full_name, info);
}
}
@@ -461,6 +461,38 @@ fn getArgs(self: *const Caller, comptime Struct: type, comptime named_function:
return args;
}
// This is extracted to speed up compilation. When left inlined in handleError,
// this can add as much as 10 seconds of compilation time.
fn logFunctionCallError(self: *Caller, err: anyerror, function_name: []const u8, info: v8.FunctionCallbackInfo) void {
const args_dump = self.serializeFunctionArgs(info) catch "failed to serialize args";
log.info(.js, "function call error", .{
.name = function_name,
.err = err,
.args = args_dump,
.stack = self.context.stackTrace() catch |err1| @errorName(err1),
});
}
fn serializeFunctionArgs(self: *Caller, info: v8.FunctionCallbackInfo) ![]const u8 {
const separator = log.separator();
const js_parameter_count = info.length();
const context = self.context;
var arr: std.ArrayListUnmanaged(u8) = .{};
for (0..js_parameter_count) |i| {
const js_value = info.getArg(@intCast(i));
const value_string = try context.valueToDetailString(js_value);
const value_type = try context.jsStringToZig(try js_value.typeOf(self.isolate), .{});
try std.fmt.format(arr.writer(context.call_arena), "{s}{d}: {s} ({s})", .{
separator,
i + 1,
value_string,
value_type,
});
}
return arr.items;
}
// We want the function name, or more precisely, the "Struct.function" for
// displaying helpful @compileError.
// However, there's no way to get the name from a std.Builtin.Fn, so we create
@@ -524,37 +556,6 @@ fn isPage(comptime T: type) bool {
return T == *Page or T == *const Page;
}
// This is extracted to speed up compilation. When left inlined in handleError,
// this can add as much as 10 seconds of compilation time.
fn logFunctionCallError(arena: Allocator, isolate: v8.Isolate, context: v8.Context, err: anyerror, function_name: []const u8, info: v8.FunctionCallbackInfo) void {
const args_dump = serializeFunctionArgs(arena, isolate, context, info) catch "failed to serialize args";
log.info(.js, "function call error", .{
.name = function_name,
.err = err,
.args = args_dump,
.stack = Context.stackForLogs(arena, isolate) catch |err1| @errorName(err1),
});
}
fn serializeFunctionArgs(arena: Allocator, isolate: v8.Isolate, context: v8.Context, info: v8.FunctionCallbackInfo) ![]const u8 {
const separator = log.separator();
const js_parameter_count = info.length();
var arr: std.ArrayListUnmanaged(u8) = .{};
for (0..js_parameter_count) |i| {
const js_value = info.getArg(@intCast(i));
const value_string = try js.valueToDetailString(arena, js_value, isolate, context);
const value_type = try js.stringToZig(arena, try js_value.typeOf(isolate), isolate);
try std.fmt.format(arr.writer(arena), "{s}{d}: {s} ({s})", .{
separator,
i + 1,
value_string,
value_type,
});
}
return arr.items;
}
fn createTypeException(isolate: v8.Isolate, msg: []const u8) v8.Value {
return v8.Exception.initTypeError(v8.String.initUtf8(isolate, msg));
}

File diff suppressed because it is too large Load Diff

View File

@@ -74,15 +74,7 @@ pub fn init(allocator: Allocator, platform: *const Platform, _: Opts) !*Env {
isolate.enter();
errdefer isolate.exit();
isolate.setHostInitializeImportMetaObjectCallback(struct {
fn callback(c_context: ?*v8.C_Context, c_module: ?*v8.C_Module, c_meta: ?*v8.C_Value) callconv(.c) void {
const v8_context = v8.Context{ .handle = c_context.? };
const context: *Context = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
context.initializeImportMeta(v8.Module{ .handle = c_module.? }, v8.Object{ .handle = c_meta.? }) catch |err| {
log.err(.js, "import meta", .{ .err = err });
};
}
}.callback);
isolate.setHostInitializeImportMetaObjectCallback(Context.metaObjectCallback);
var temp_scope: v8.HandleScope = undefined;
v8.HandleScope.init(&temp_scope, isolate);
@@ -226,11 +218,10 @@ pub fn dumpMemoryStats(self: *Env) void {
fn promiseRejectCallback(v8_msg: v8.C_PromiseRejectMessage) callconv(.c) void {
const msg = v8.PromiseRejectMessage.initFromC(v8_msg);
const isolate = msg.getPromise().toObject().getIsolate();
const v8_context = isolate.getCurrentContext();
const context: *Context = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
const context = Context.fromIsolate(isolate);
const value =
if (msg.getValue()) |v8_value| js.valueToString(context.call_arena, v8_value, isolate, v8_context) catch |err| @errorName(err) else "no value";
if (msg.getValue()) |v8_value| context.valueToString(v8_value, .{}) catch |err| @errorName(err) else "no value";
log.debug(.js, "unhandled rejection", .{ .value = value });
}

View File

@@ -58,12 +58,12 @@ pub fn deinit(self: *ExecutionWorld) void {
// when the handle_scope is freed.
// We also maintain our own "context_arena" which allows us to have
// all page related memory easily managed.
pub fn createContext(self: *ExecutionWorld, global: anytype, page: *Page, script_manager: ?*ScriptManager, enter: bool, global_callback: ?js.GlobalMissingCallback) !*Context {
pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool, global_callback: ?js.GlobalMissingCallback) !*Context {
std.debug.assert(self.context == null);
const env = self.env;
const isolate = env.isolate;
const Global = @TypeOf(global.*);
const Global = @TypeOf(page.window);
const templates = &self.env.templates;
var v8_context: v8.Context = blk: {
@@ -84,12 +84,9 @@ pub fn createContext(self: *ExecutionWorld, global: anytype, page: *Page, script
.getter = struct {
fn callback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
const _isolate = info.getIsolate();
const v8_context = _isolate.getCurrentContext();
const context = Context.fromIsolate(info.getIsolate());
const context: *Context = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
const property = js.valueToString(context.call_arena, .{ .handle = c_name.? }, _isolate, v8_context) catch "???";
const property = context.valueToString(.{ .handle = c_name.? }, .{}) catch "???";
if (context.global_callback.?.missing(property, context)) {
return v8.Intercepted.Yes;
}
@@ -180,7 +177,7 @@ pub fn createContext(self: *ExecutionWorld, global: anytype, page: *Page, script
.templates = &env.templates,
.meta_lookup = &env.meta_lookup,
.handle_scope = handle_scope,
.script_manager = script_manager,
.script_manager = &page.script_manager,
.call_arena = self.call_arena.allocator(),
.context_arena = self.context_arena.allocator(),
.global_callback = global_callback,
@@ -188,9 +185,8 @@ pub fn createContext(self: *ExecutionWorld, global: anytype, page: *Page, script
var context = &self.context.?;
{
// Given a context, we can get our executor.
// (we store a pointer to our executor in the context's
// embeddeder data)
// Store a pointer to our context inside the v8 context so that, given
// a v8 context, we can get our context out
const data = isolate.initBigIntU64(@intCast(@intFromPtr(context)));
v8_context.setEmbedderData(1, data);
}
@@ -240,7 +236,7 @@ pub fn createContext(self: *ExecutionWorld, global: anytype, page: *Page, script
}
}
_ = try context._mapZigInstanceToJs(v8_context.getGlobal(), global);
try context.setupGlobal();
return context;
}

View File

@@ -22,7 +22,7 @@ pub const Result = struct {
pub fn getName(self: *const Function, allocator: Allocator) ![]const u8 {
const name = self.func.castToFunction().getName();
return js.valueToString(allocator, name, self.context.isolate, self.context.v8_context);
return self.context.valueToString(name, .{ .allocator = allocator });
}
pub fn setName(self: *const Function, name: []const u8) void {

View File

@@ -80,7 +80,7 @@ pub fn contextCreated(
// we'll create it and track it for cleanup when the context ends.
pub fn getRemoteObject(
self: *const Inspector,
context: *const Context,
context: *Context,
group: []const u8,
value: anytype,
) !RemoteObject {

View File

@@ -52,24 +52,22 @@ pub fn isTruthy(self: Object) bool {
}
pub fn toString(self: Object) ![]const u8 {
const context = self.context;
const js_value = self.js_obj.toValue();
return js.valueToString(context.call_arena, js_value, context.isolate, context.v8_context);
return self.context.valueToString(js_value, .{});
}
pub fn toDetailString(self: Object) ![]const u8 {
const context = self.context;
const js_value = self.js_obj.toValue();
return js.valueToDetailString(context.call_arena, js_value, context.isolate, context.v8_context);
return self.context.valueToDetailString(js_value);
}
pub fn format(self: Object, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
return writer.writeAll(try self.toString());
}
pub fn toJson(self: Object, allocator: std.mem.Allocator) ![]u8 {
pub fn toJson(self: Object, allocator: Allocator) ![]u8 {
const json_string = try v8.Json.stringify(self.context.v8_context, self.js_obj.toValue(), null);
const str = try js.stringToZig(allocator, json_string, self.context.isolate);
const str = try self.context.jsStringToZig(json_string, .{ .allocator = allocator });
return str;
}
@@ -137,11 +135,6 @@ pub fn nameIterator(self: Object) js.ValueIterator {
};
}
pub fn constructorName(self: Object, allocator: Allocator) ![]const u8 {
const str = try self.js_obj.getConstructorName();
return js.StringToZig(allocator, str, self.context.isolate);
}
pub fn toZig(self: Object, comptime Struct: type, comptime name: []const u8, comptime T: type) !T {
const named_function = comptime Caller.NamedFunction.init(Struct, name);
return self.context.jsValueToZig(named_function, T, self.js_obj.toValue());

View File

@@ -23,7 +23,3 @@ pub fn setIndex(self: This, index: u32, value: anytype, opts: js.Object.SetOpts)
pub fn set(self: This, key: []const u8, value: anytype, opts: js.Object.SetOpts) !void {
return self.obj.set(key, value, opts);
}
pub fn constructorName(self: This, allocator: Allocator) ![]const u8 {
return try self.obj.constructorName(allocator);
}

View File

@@ -21,15 +21,14 @@ pub fn hasCaught(self: TryCatch) bool {
// the caller needs to deinit the string returned
pub fn exception(self: TryCatch, allocator: Allocator) !?[]const u8 {
const msg = self.inner.getException() orelse return null;
const context = self.context;
return try js.valueToString(allocator, msg, context.isolate, context.v8_context);
return try self.context.valueToString(msg, .{ .allocator = allocator });
}
// the caller needs to deinit the string returned
pub fn stack(self: TryCatch, allocator: Allocator) !?[]const u8 {
const context = self.context;
const s = self.inner.getStackTrace(context.v8_context) orelse return null;
return try js.valueToString(allocator, s, context.isolate, context.v8_context);
return try context.valueToString(s, .{ .allocator = allocator });
}
// the caller needs to deinit the string returned
@@ -37,7 +36,7 @@ pub fn sourceLine(self: TryCatch, allocator: Allocator) !?[]const u8 {
const context = self.context;
const msg = self.inner.getMessage() orelse return null;
const sl = msg.getSourceLine(context.v8_context) orelse return null;
return try js.stringToZig(allocator, sl, context.isolate);
return try context.jsStringToZig(sl, .{ .allocator = allocator });
}
pub fn sourceLineNumber(self: TryCatch) ?u32 {

View File

@@ -17,7 +17,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const builtin = @import("builtin");
pub const v8 = @import("v8");
const types = @import("types.zig");
@@ -64,9 +63,7 @@ pub const PromiseResolver = struct {
resolver: v8.PromiseResolver,
pub fn promise(self: PromiseResolver) Promise {
return .{
.promise = self.resolver.getPromise(),
};
return self.resolver.getPromise();
}
pub fn resolve(self: PromiseResolver, value: anytype) !void {
@@ -101,9 +98,7 @@ pub const PersistentPromiseResolver = struct {
}
pub fn promise(self: PersistentPromiseResolver) Promise {
return .{
.promise = self.resolver.castToPromiseResolver().getPromise(),
};
return self.resolver.castToPromiseResolver().getPromise();
}
pub fn resolve(self: PersistentPromiseResolver, value: anytype) !void {
@@ -129,9 +124,7 @@ pub const PersistentPromiseResolver = struct {
}
};
pub const Promise = struct {
promise: v8.Promise,
};
pub const Promise = v8.Promise;
// When doing jsValueToZig, string ([]const u8) are managed by the
// call_arena. That means that if the API wants to persist the string
@@ -148,8 +141,7 @@ pub const Exception = struct {
// the caller needs to deinit the string returned
pub fn exception(self: Exception, allocator: Allocator) ![]const u8 {
const context = self.context;
return try valueToString(allocator, self.inner, context.isolate, context.v8_context);
return self.context.valueToString(self.inner, .{ .allocator = allocator });
}
};
@@ -159,8 +151,7 @@ pub const Value = struct {
// the caller needs to deinit the string returned
pub fn toString(self: Value, allocator: Allocator) ![]const u8 {
const context = self.context;
return valueToString(allocator, self.value, context.isolate, context.v8_context);
return self.context.valueToString(self.value, .{ .allocator = allocator });
}
pub fn fromJson(ctx: *Context, json: []const u8) !Value {
@@ -476,93 +467,6 @@ pub const TaggedAnyOpaque = struct {
subtype: ?types.Sub,
};
pub fn valueToDetailString(arena: Allocator, value: v8.Value, isolate: v8.Isolate, v8_context: v8.Context) ![]u8 {
var str: ?v8.String = null;
if (value.isObject() and !value.isFunction()) blk: {
str = v8.Json.stringify(v8_context, value, null) catch break :blk;
if (str.?.lenUtf8(isolate) == 2) {
// {} isn't useful, null this so that we can get the toDetailString
// (which might also be useless, but maybe not)
str = null;
}
}
if (str == null) {
str = try value.toDetailString(v8_context);
}
const s = try stringToZig(arena, str.?, isolate);
if (comptime builtin.mode == .Debug) {
if (std.mem.eql(u8, s, "[object Object]")) {
if (debugValueToString(arena, value.castTo(v8.Object), isolate, v8_context)) |ds| {
return ds;
} else |err| {
log.err(.js, "debug serialize value", .{ .err = err });
}
}
}
return s;
}
pub fn valueToString(allocator: Allocator, value: v8.Value, isolate: v8.Isolate, v8_context: v8.Context) ![]u8 {
if (value.isSymbol()) {
// symbol's can't be converted to a string
return allocator.dupe(u8, "$Symbol");
}
const str = try value.toString(v8_context);
return stringToZig(allocator, str, isolate);
}
pub fn valueToStringZ(allocator: Allocator, value: v8.Value, isolate: v8.Isolate, v8_context: v8.Context) ![:0]u8 {
const str = try value.toString(v8_context);
const len = str.lenUtf8(isolate);
const buf = try allocator.allocSentinel(u8, len, 0);
const n = str.writeUtf8(isolate, buf);
std.debug.assert(n == len);
return buf;
}
pub fn stringToZig(allocator: Allocator, str: v8.String, isolate: v8.Isolate) ![]u8 {
const len = str.lenUtf8(isolate);
const buf = try allocator.alloc(u8, len);
const n = str.writeUtf8(isolate, buf);
std.debug.assert(n == len);
return buf;
}
fn debugValueToString(arena: Allocator, js_obj: v8.Object, isolate: v8.Isolate, v8_context: v8.Context) ![]u8 {
if (comptime builtin.mode != .Debug) {
@compileError("debugValue can only be called in debug mode");
}
var arr: std.ArrayListUnmanaged(u8) = .empty;
var writer = arr.writer(arena);
const names_arr = js_obj.getOwnPropertyNames(v8_context);
const names_obj = names_arr.castTo(v8.Object);
const len = names_arr.length();
try writer.writeAll("(JSON.stringify failed, dumping top-level fields)\n");
for (0..len) |i| {
const field_name = try names_obj.getAtIndex(v8_context, @intCast(i));
const field_value = try js_obj.getValue(v8_context, field_name);
const name = try valueToString(arena, field_name, isolate, v8_context);
const value = try valueToString(arena, field_value, isolate, v8_context);
try writer.writeAll(name);
try writer.writeAll(": ");
if (std.mem.indexOfAny(u8, value, &std.ascii.whitespace) == null) {
try writer.writeAll(value);
} else {
try writer.writeByte('"');
try writer.writeAll(value);
try writer.writeByte('"');
}
try writer.writeByte(' ');
}
return arr.items;
}
// These are here, and not in Inspector.zig, because Inspector.zig isn't always
// included (e.g. in the wpt build).

View File

@@ -74,7 +74,7 @@ pub const Page = struct {
// Our JavaScript context for this specific page. This is what we use to
// execute any JavaScript
main_context: *js.Context,
js: *js.Context,
// indicates intention to navigate to another page on the next loop execution.
delayed_navigation: bool = false,
@@ -140,11 +140,11 @@ pub const Page = struct {
.scheduler = Scheduler.init(arena),
.keydown_event_node = .{ .func = keydownCallback },
.window_clicked_event_node = .{ .func = windowClicked },
.main_context = undefined,
.js = undefined,
};
self.main_context = try session.executor.createContext(&self.window, self, &self.script_manager, true, js.GlobalMissingCallback.init(&self.polyfill_loader));
try polyfill.preload(self.arena, self.main_context);
self.js = try session.executor.createContext(self, true, js.GlobalMissingCallback.init(&self.polyfill_loader));
try polyfill.preload(self.arena, self.js);
try self.scheduler.add(self, runMicrotasks, 5, .{ .name = "page.microtasks" });
// message loop must run only non-test env
@@ -277,7 +277,7 @@ pub const Page = struct {
var ms_remaining = wait_ms;
var try_catch: js.TryCatch = undefined;
try_catch.init(self.main_context);
try_catch.init(self.js);
defer try_catch.deinit();
var scheduler = &self.scheduler;
@@ -1116,7 +1116,7 @@ pub const Page = struct {
pub fn stackTrace(self: *Page) !?[]const u8 {
if (comptime builtin.mode == .Debug) {
return self.main_context.stackTrace();
return self.js.stackTrace();
}
return null;
}

View File

@@ -105,8 +105,8 @@ const QueueingStrategy = struct {
pub fn constructor(underlying: ?UnderlyingSource, _strategy: ?QueueingStrategy, page: *Page) !*ReadableStream {
const strategy: QueueingStrategy = _strategy orelse .{};
const cancel_resolver = try page.main_context.createPersistentPromiseResolver(.self);
const closed_resolver = try page.main_context.createPersistentPromiseResolver(.self);
const cancel_resolver = try page.js.createPromiseResolver(.self);
const closed_resolver = try page.js.createPromiseResolver(.self);
const stream = try page.arena.create(ReadableStream);
stream.* = ReadableStream{

View File

@@ -47,43 +47,26 @@ pub fn _read(self: *const ReadableStreamDefaultReader, page: *Page) !js.Promise
.readable => {
if (stream.queue.items.len > 0) {
const data = self.stream.queue.orderedRemove(0);
const resolver = page.main_context.createPromiseResolver();
try resolver.resolve(ReadableStreamReadResult.init(data, false));
const promise = page.js.resolvePromise(ReadableStreamReadResult.init(data, false));
try self.stream.pullIf();
return resolver.promise();
} else {
if (self.stream.reader_resolver) |rr| {
return rr.promise();
} else {
const persistent_resolver = try page.main_context.createPersistentPromiseResolver(.page);
self.stream.reader_resolver = persistent_resolver;
return persistent_resolver.promise();
}
return promise;
}
if (self.stream.reader_resolver) |rr| {
return rr.promise();
}
const persistent_resolver = try page.js.createPromiseResolver(.page);
self.stream.reader_resolver = persistent_resolver;
return persistent_resolver.promise();
},
.closed => |_| {
const resolver = page.main_context.createPromiseResolver();
if (stream.queue.items.len > 0) {
const data = self.stream.queue.orderedRemove(0);
try resolver.resolve(ReadableStreamReadResult.init(data, false));
} else {
try resolver.resolve(ReadableStreamReadResult{ .done = true });
return page.js.resolvePromise(ReadableStreamReadResult.init(data, false));
}
return resolver.promise();
},
.cancelled => |_| {
const resolver = page.main_context.createPromiseResolver();
try resolver.resolve(ReadableStreamReadResult{ .value = .empty, .done = true });
return resolver.promise();
},
.errored => |err| {
const resolver = page.main_context.createPromiseResolver();
try resolver.reject(err);
return resolver.promise();
return page.js.resolvePromise(ReadableStreamReadResult{ .done = true });
},
.cancelled => |_| return page.js.resolvePromise(ReadableStreamReadResult{ .value = .empty, .done = true }),
.errored => |err| return page.js.rejectPromise(err),
}
}

View File

@@ -690,9 +690,7 @@ const IsolatedWorld = struct {
return;
}
_ = try self.executor.createContext(
&page.window,
page,
null,
false,
js.GlobalMissingCallback.init(&self.polyfill_loader),
);

View File

@@ -274,7 +274,7 @@ fn resolveNode(cmd: anytype) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
var js_context = page.main_context;
var js_context = page.js;
if (params.executionContextId) |context_id| {
if (js_context.v8_context.debugContextId() != context_id) {
for (bc.isolated_worlds.items) |*isolated_world| {

View File

@@ -251,7 +251,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
const aux_data = try std.fmt.allocPrint(arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});
bc.inspector.contextCreated(
page.main_context,
page.js,
"",
try page.origin(arena),
aux_data,

View File

@@ -147,7 +147,7 @@ fn createTarget(cmd: anytype) !void {
{
const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});
bc.inspector.contextCreated(
page.main_context,
page.js,
"",
try page.origin(cmd.arena),
aux_data,

View File

@@ -123,7 +123,7 @@ fn run(
_ = page.wait(2000);
const js_context = page.main_context;
const js_context = page.js;
var try_catch: js.TryCatch = undefined;
try_catch.init(js_context);
defer try_catch.deinit();

View File

@@ -395,7 +395,7 @@ pub fn htmlRunner(file: []const u8) !void {
page.arena = @import("root").tracking_allocator;
const js_context = page.main_context;
const js_context = page.js;
var try_catch: js.TryCatch = undefined;
try_catch.init(js_context);
defer try_catch.deinit();
@@ -409,7 +409,7 @@ pub fn htmlRunner(file: []const u8) !void {
page.session.browser.runMessageLoop();
const needs_second_wait = try js_context.exec("testing._onPageWait.length > 0", "check_onPageWait");
if (needs_second_wait.value.toBool(page.main_context.isolate)) {
if (needs_second_wait.value.toBool(page.js.isolate)) {
// sets the isSecondWait flag in testing.
_ = js_context.exec("testing._isSecondWait = true", "set_second_wait_flag") catch {};
_ = page.wait(2000);