Merge pull request #1444 from lightpanda-io/inspector_rework

Rework Inspector usage
This commit is contained in:
Karl Seguin
2026-01-31 07:06:11 +08:00
committed by GitHub
11 changed files with 216 additions and 273 deletions

View File

@@ -50,10 +50,14 @@ session_arena: ArenaAllocator,
transfer_arena: ArenaAllocator, transfer_arena: ArenaAllocator,
notification: *Notification, notification: *Notification,
pub fn init(app: *App) !Browser { const InitOpts = struct {
env: js.Env.InitOpts = .{},
};
pub fn init(app: *App, opts: InitOpts) !Browser {
const allocator = app.allocator; const allocator = app.allocator;
var env = try js.Env.init(app); var env = try js.Env.init(app, opts.env);
errdefer env.deinit(); errdefer env.deinit();
const notification = try Notification.init(allocator, app.notification); const notification = try Notification.init(allocator, app.notification);

View File

@@ -68,7 +68,14 @@ templates: []*const v8.FunctionTemplate,
// Global template created once per isolate and reused across all contexts // Global template created once per isolate and reused across all contexts
global_template: v8.Eternal, global_template: v8.Eternal,
pub fn init(app: *App) !Env { // Inspector associated with the Isolate. Exists when CDP is being used.
inspector: ?*Inspector,
pub const InitOpts = struct {
with_inspector: bool = false,
};
pub fn init(app: *App, opts: InitOpts) !Env {
const allocator = app.allocator; const allocator = app.allocator;
const snapshot = &app.snapshot; const snapshot = &app.snapshot;
@@ -84,17 +91,18 @@ pub fn init(app: *App) !Env {
var isolate = js.Isolate.init(params); var isolate = js.Isolate.init(params);
errdefer isolate.deinit(); errdefer isolate.deinit();
const isolate_handle = isolate.handle;
v8.v8__Isolate__SetHostImportModuleDynamicallyCallback(isolate.handle, Context.dynamicModuleCallback); v8.v8__Isolate__SetHostImportModuleDynamicallyCallback(isolate_handle, Context.dynamicModuleCallback);
v8.v8__Isolate__SetPromiseRejectCallback(isolate.handle, promiseRejectCallback); v8.v8__Isolate__SetPromiseRejectCallback(isolate_handle, promiseRejectCallback);
v8.v8__Isolate__SetMicrotasksPolicy(isolate.handle, v8.kExplicit); v8.v8__Isolate__SetMicrotasksPolicy(isolate_handle, v8.kExplicit);
v8.v8__Isolate__SetFatalErrorHandler(isolate.handle, fatalCallback); v8.v8__Isolate__SetFatalErrorHandler(isolate_handle, fatalCallback);
v8.v8__Isolate__SetOOMErrorHandler(isolate.handle, oomCallback); v8.v8__Isolate__SetOOMErrorHandler(isolate_handle, oomCallback);
isolate.enter(); isolate.enter();
errdefer isolate.exit(); errdefer isolate.exit();
v8.v8__Isolate__SetHostInitializeImportMetaObjectCallback(isolate.handle, Context.metaObjectCallback); v8.v8__Isolate__SetHostInitializeImportMetaObjectCallback(isolate_handle, Context.metaObjectCallback);
// Allocate arrays dynamically to avoid comptime dependency on JsApis.len // Allocate arrays dynamically to avoid comptime dependency on JsApis.len
const eternal_function_templates = try allocator.alloc(v8.Eternal, JsApis.len); const eternal_function_templates = try allocator.alloc(v8.Eternal, JsApis.len);
@@ -111,19 +119,19 @@ pub fn init(app: *App) !Env {
inline for (JsApis, 0..) |JsApi, i| { inline for (JsApis, 0..) |JsApi, i| {
JsApi.Meta.class_id = i; JsApi.Meta.class_id = i;
const data = v8.v8__Isolate__GetDataFromSnapshotOnce(isolate.handle, snapshot.data_start + i); const data = v8.v8__Isolate__GetDataFromSnapshotOnce(isolate_handle, snapshot.data_start + i);
const function_handle: *const v8.FunctionTemplate = @ptrCast(data); const function_handle: *const v8.FunctionTemplate = @ptrCast(data);
// Make function template eternal // Make function template eternal
v8.v8__Eternal__New(isolate.handle, @ptrCast(function_handle), &eternal_function_templates[i]); v8.v8__Eternal__New(isolate_handle, @ptrCast(function_handle), &eternal_function_templates[i]);
// Extract the local handle from the global for easy access // Extract the local handle from the global for easy access
const eternal_ptr = v8.v8__Eternal__Get(&eternal_function_templates[i], isolate.handle); const eternal_ptr = v8.v8__Eternal__Get(&eternal_function_templates[i], isolate_handle);
templates[i] = @ptrCast(@alignCast(eternal_ptr.?)); templates[i] = @ptrCast(@alignCast(eternal_ptr.?));
} }
// Create global template once per isolate // Create global template once per isolate
const js_global = v8.v8__FunctionTemplate__New__DEFAULT(isolate.handle); const js_global = v8.v8__FunctionTemplate__New__DEFAULT(isolate_handle);
const window_name = v8.v8__String__NewFromUtf8(isolate.handle, "Window", v8.kNormal, 6); const window_name = v8.v8__String__NewFromUtf8(isolate_handle, "Window", v8.kNormal, 6);
v8.v8__FunctionTemplate__SetClassName(js_global, window_name); v8.v8__FunctionTemplate__SetClassName(js_global, window_name);
// Find Window in JsApis by name (avoids circular import) // Find Window in JsApis by name (avoids circular import)
@@ -142,7 +150,12 @@ pub fn init(app: *App) !Env {
.data = null, .data = null,
.flags = v8.kOnlyInterceptStrings | v8.kNonMasking, .flags = v8.kOnlyInterceptStrings | v8.kNonMasking,
}); });
v8.v8__Eternal__New(isolate.handle, @ptrCast(global_template_local), &global_eternal); v8.v8__Eternal__New(isolate_handle, @ptrCast(global_template_local), &global_eternal);
}
var inspector: ?*js.Inspector = null;
if (opts.with_inspector) {
inspector = try Inspector.init(allocator, isolate_handle);
} }
return .{ return .{
@@ -153,6 +166,7 @@ pub fn init(app: *App) !Env {
.platform = &app.platform, .platform = &app.platform,
.templates = templates, .templates = templates,
.isolate_params = params, .isolate_params = params,
.inspector = inspector,
.eternal_function_templates = eternal_function_templates, .eternal_function_templates = eternal_function_templates,
.global_template = global_eternal, .global_template = global_eternal,
}; };
@@ -167,6 +181,10 @@ pub fn deinit(self: *Env) void {
} }
const allocator = self.app.allocator; const allocator = self.app.allocator;
if (self.inspector) |i| {
i.deinit(allocator);
}
self.contexts.deinit(allocator); self.contexts.deinit(allocator);
allocator.free(self.templates); allocator.free(self.templates);
@@ -220,8 +238,8 @@ pub fn createContext(self: *Env, page: *Page, enter: bool) !*Context {
.arena = context_arena, .arena = context_arena,
.handle = context_global, .handle = context_global,
.templates = self.templates, .templates = self.templates,
.script_manager = &page._script_manager,
.call_arena = page.call_arena, .call_arena = page.call_arena,
.script_manager = &page._script_manager,
}; };
try context.identity_map.putNoClobber(context_arena, @intFromPtr(page.window), global_global); try context.identity_map.putNoClobber(context_arena, @intFromPtr(page.window), global_global);
@@ -249,12 +267,6 @@ pub fn destroyContext(self: *Env, context: *Context) void {
self.isolate.notifyContextDisposed(); self.isolate.notifyContextDisposed();
} }
pub fn newInspector(self: *Env, arena: Allocator, ctx: anytype) !*Inspector {
const inspector = try arena.create(Inspector);
try Inspector.init(inspector, self.isolate.handle, ctx);
return inspector;
}
pub fn runMicrotasks(self: *const Env) void { pub fn runMicrotasks(self: *const Env) void {
self.isolate.performMicrotasksCheckpoint(); self.isolate.performMicrotasksCheckpoint();
} }

View File

@@ -23,99 +23,76 @@ const v8 = js.v8;
const TaggedOpaque = @import("TaggedOpaque.zig"); const TaggedOpaque = @import("TaggedOpaque.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const RndGen = std.Random.DefaultPrng;
const CONTEXT_GROUP_ID = 1; const CONTEXT_GROUP_ID = 1;
const CLIENT_TRUST_LEVEL = 1; const CLIENT_TRUST_LEVEL = 1;
const IS_DEBUG = @import("builtin").mode == .Debug;
// Inspector exists for the lifetime of the Isolate/Env. 1 Isolate = 1 Inspector.
// It combines the v8.Inspector and the v8.InspectorClientImpl. The v8.InspectorClientImpl
// is our own implementation that fulfills the InspectorClient API, i.e. it's the
// mechanism v8 provides to let us tweak how the inspector works. For example, it
// Below, you'll find a few pub export fn v8_inspector__Client__IMPL__XYZ functions
// which is our implementation of what the v8::Inspector requires of our Client
// (not much at all)
const Inspector = @This(); const Inspector = @This();
handle: *v8.Inspector, unique_id: i64,
isolate: *v8.Isolate, isolate: *v8.Isolate,
client: Client, handle: *v8.Inspector,
channel: Channel, client: *v8.InspectorClientImpl,
session: Session,
rnd: RndGen = RndGen.init(0),
default_context: ?v8.Global, default_context: ?v8.Global,
session: ?Session,
// We expect allocator to be an arena pub fn init(allocator: Allocator, isolate: *v8.Isolate) !*Inspector {
// Note: This initializes the pre-allocated inspector in-place const self = try allocator.create(Inspector);
pub fn init(self: *Inspector, isolate: *v8.Isolate, ctx: anytype) !void { errdefer allocator.destroy(self);
const ContextT = @TypeOf(ctx);
const Container = switch (@typeInfo(ContextT)) {
.@"struct" => ContextT,
.pointer => |ptr| ptr.child,
.void => NoopInspector,
else => @compileError("invalid context type"),
};
// If necessary, turn a void context into something we can safely ptrCast
const safe_context: *anyopaque = if (ContextT == void) @ptrCast(@constCast(&{})) else ctx;
// Initialize the fields that callbacks need first
self.* = .{ self.* = .{
.handle = undefined, .unique_id = 1,
.session = null,
.isolate = isolate, .isolate = isolate,
.client = undefined, .client = undefined,
.channel = undefined, .handle = undefined,
.rnd = RndGen.init(0),
.default_context = null, .default_context = null,
.session = undefined,
}; };
// Create client and set inspector data BEFORE creating the inspector self.client = v8.v8_inspector__Client__IMPL__CREATE();
// because V8 will call generateUniqueId during inspector creation errdefer v8.v8_inspector__Client__IMPL__DELETE(self.client);
const client = Client.init(); v8.v8_inspector__Client__IMPL__SET_DATA(self.client, self);
self.client = client;
client.setInspector(self);
// Now create the inspector - generateUniqueId will work because data is set self.handle = v8.v8_inspector__Inspector__Create(isolate, self.client).?;
const handle = v8.v8_inspector__Inspector__Create(isolate, client.handle).?; errdefer v8.v8_inspector__Inspector__DELETE(self.handle);
self.handle = handle;
// Create the channel return self;
const channel = Channel.init(
safe_context,
Container.onInspectorResponse,
Container.onInspectorEvent,
Container.onRunMessageLoopOnPause,
Container.onQuitMessageLoopOnPause,
isolate,
);
self.channel = channel;
channel.setInspector(self);
// Create the session
const session_handle = v8.v8_inspector__Inspector__Connect(
handle,
CONTEXT_GROUP_ID,
channel.handle,
CLIENT_TRUST_LEVEL,
).?;
self.session = .{ .handle = session_handle };
} }
pub fn deinit(self: *const Inspector) void { pub fn deinit(self: *const Inspector, allocator: Allocator) void {
var hs: v8.HandleScope = undefined; var hs: v8.HandleScope = undefined;
v8.v8__HandleScope__CONSTRUCT(&hs, self.isolate); v8.v8__HandleScope__CONSTRUCT(&hs, self.isolate);
defer v8.v8__HandleScope__DESTRUCT(&hs); defer v8.v8__HandleScope__DESTRUCT(&hs);
self.session.deinit(); if (self.session) |*s| {
self.client.deinit(); s.deinit();
self.channel.deinit(); }
v8.v8_inspector__Client__IMPL__DELETE(self.client);
v8.v8_inspector__Inspector__DELETE(self.handle); v8.v8_inspector__Inspector__DELETE(self.handle);
allocator.destroy(self);
} }
pub fn send(self: *const Inspector, msg: []const u8) void { pub fn startSession(self: *Inspector, ctx: anytype) *Session {
// Can't assume the main Context exists (with its HandleScope) if (comptime IS_DEBUG) {
// available when doing this. Pages (and thus the HandleScope) std.debug.assert(self.session == null);
// comes and goes, but CDP can keep sending messages. }
const isolate = self.isolate;
var temp_scope: v8.HandleScope = undefined;
v8.v8__HandleScope__CONSTRUCT(&temp_scope, isolate);
defer v8.v8__HandleScope__DESTRUCT(&temp_scope);
self.session.dispatchProtocolMessage(isolate, msg); self.session = undefined;
Session.init(&self.session.?, self, ctx);
return &self.session.?;
}
pub fn stopSession(self: *Inspector) void {
self.session.?.deinit();
self.session = null;
} }
// From CDP docs // From CDP docs
@@ -163,51 +140,6 @@ pub fn resetContextGroup(self: *const Inspector) void {
v8.v8_inspector__Inspector__ResetContextGroup(self.handle, CONTEXT_GROUP_ID); v8.v8_inspector__Inspector__ResetContextGroup(self.handle, CONTEXT_GROUP_ID);
} }
// Retrieves the RemoteObject for a given value.
// The value is loaded through the ExecutionWorld's mapZigInstanceToJs function,
// just like a method return value. Therefore, if we've mapped this
// value before, we'll get the existing js.Global(js.Object) and if not
// we'll create it and track it for cleanup when the context ends.
pub fn getRemoteObject(
self: *const Inspector,
local: *const js.Local,
group: []const u8,
value: anytype,
) !RemoteObject {
const js_val = try local.zigValueToJs(value, .{});
// We do not want to expose this as a parameter for now
const generate_preview = false;
return self.session.wrapObject(
local.isolate.handle,
local.handle,
js_val.handle,
group,
generate_preview,
);
}
// Gets a value by object ID regardless of which context it is in.
// Our TaggedOpaque stores the "resolved" ptr value (the most specific _type,
// e.g. we store the ptr to the Div not the EventTarget). But, this is asking for
// the pointer to the Node, so we need to use the same resolution mechanism which
// is used when we're calling a function to turn the Div into a Node, which is
// what TaggedOpaque.fromJS does.
pub fn getNodePtr(self: *const Inspector, allocator: Allocator, object_id: []const u8, local: *js.Local) !*anyopaque {
// just to indicate that the caller is responsible for ensure there's a local environment
_ = local;
const unwrapped = try self.session.unwrapObject(allocator, object_id);
// The values context and groupId are not used here
const js_val = unwrapped.value;
if (!v8.v8__Value__IsObject(js_val)) {
return error.ObjectIdIsNotANode;
}
const Node = @import("../webapi/Node.zig");
// Cast to *const v8.Object for typeTaggedAnyOpaque
return TaggedOpaque.fromJS(*Node, @ptrCast(js_val)) catch return error.ObjectIdIsNotANode;
}
pub const RemoteObject = struct { pub const RemoteObject = struct {
handle: *v8.RemoteObject, handle: *v8.RemoteObject,
@@ -254,20 +186,109 @@ pub const RemoteObject = struct {
} }
}; };
const Session = struct { // Combines a v8::InspectorSession and a v8::InspectorChannelImpl. The
// InspectorSession is for zig -> v8 (sending messages to the inspector). The
// Channel is for v8 -> zig, getting events from the Inspector (that we'll pass
// back ot some opaque context, i.e the CDP BrowserContext).
// The channel callbacks are defined below, as:
// pub export fn v8_inspector__Channel__IMPL__XYZ
pub const Session = struct {
inspector: *Inspector,
handle: *v8.InspectorSession, handle: *v8.InspectorSession,
channel: *v8.InspectorChannelImpl,
fn deinit(self: Session) void { // callbacks
v8.v8_inspector__Session__DELETE(self.handle); ctx: *anyopaque,
onNotif: *const fn (ctx: *anyopaque, msg: []const u8) void,
onResp: *const fn (ctx: *anyopaque, call_id: u32, msg: []const u8) void,
fn init(self: *Session, inspector: *Inspector, ctx: anytype) void {
const Container = @typeInfo(@TypeOf(ctx)).pointer.child;
const channel = v8.v8_inspector__Channel__IMPL__CREATE(inspector.isolate);
const handle = v8.v8_inspector__Inspector__Connect(
inspector.handle,
CONTEXT_GROUP_ID,
channel,
CLIENT_TRUST_LEVEL,
).?;
v8.v8_inspector__Channel__IMPL__SET_DATA(channel, self);
self.* = .{
.ctx = ctx,
.handle = handle,
.channel = channel,
.inspector = inspector,
.onResp = Container.onInspectorResponse,
.onNotif = Container.onInspectorEvent,
};
} }
fn dispatchProtocolMessage(self: Session, isolate: *v8.Isolate, msg: []const u8) void { fn deinit(self: *const Session) void {
v8.v8_inspector__Session__DELETE(self.handle);
v8.v8_inspector__Channel__IMPL__DELETE(self.channel);
}
pub fn send(self: *const Session, msg: []const u8) void {
const isolate = self.inspector.isolate;
var hs: v8.HandleScope = undefined;
v8.v8__HandleScope__CONSTRUCT(&hs, isolate);
defer v8.v8__HandleScope__DESTRUCT(&hs);
v8.v8_inspector__Session__dispatchProtocolMessage( v8.v8_inspector__Session__dispatchProtocolMessage(
self.handle, self.handle,
isolate, isolate,
msg.ptr, msg.ptr,
msg.len, msg.len,
); );
v8.v8__Isolate__PerformMicrotaskCheckpoint(isolate);
}
// Gets a value by object ID regardless of which context it is in.
// Our TaggedOpaque stores the "resolved" ptr value (the most specific _type,
// e.g. we store the ptr to the Div not the EventTarget). But, this is asking for
// the pointer to the Node, so we need to use the same resolution mechanism which
// is used when we're calling a function to turn the Div into a Node, which is
// what TaggedOpaque.fromJS does.
pub fn getNodePtr(self: *const Session, allocator: Allocator, object_id: []const u8, local: *js.Local) !*anyopaque {
// just to indicate that the caller is responsible for ensuring there's a local environment
_ = local;
const unwrapped = try self.unwrapObject(allocator, object_id);
// The values context and groupId are not used here
const js_val = unwrapped.value;
if (!v8.v8__Value__IsObject(js_val)) {
return error.ObjectIdIsNotANode;
}
const Node = @import("../webapi/Node.zig");
// Cast to *const v8.Object for typeTaggedAnyOpaque
return TaggedOpaque.fromJS(*Node, @ptrCast(js_val)) catch return error.ObjectIdIsNotANode;
}
// Retrieves the RemoteObject for a given value.
// The value is loaded through the ExecutionWorld's mapZigInstanceToJs function,
// just like a method return value. Therefore, if we've mapped this
// value before, we'll get the existing js.Global(js.Object) and if not
// we'll create it and track it for cleanup when the context ends.
pub fn getRemoteObject(
self: *const Session,
local: *const js.Local,
group: []const u8,
value: anytype,
) !RemoteObject {
const js_val = try local.zigValueToJs(value, .{});
// We do not want to expose this as a parameter for now
const generate_preview = false;
return self.wrapObject(
local.isolate.handle,
local.handle,
js_val.handle,
group,
generate_preview,
);
} }
fn wrapObject( fn wrapObject(
@@ -334,84 +355,6 @@ const UnwrappedObject = struct {
object_group: ?[]const u8, object_group: ?[]const u8,
}; };
const Channel = struct {
handle: *v8.InspectorChannelImpl,
// callbacks
ctx: *anyopaque,
onNotif: onNotifFn = undefined,
onResp: onRespFn = undefined,
onRunMessageLoopOnPause: onRunMessageLoopOnPauseFn = undefined,
onQuitMessageLoopOnPause: onQuitMessageLoopOnPauseFn = undefined,
pub const onNotifFn = *const fn (ctx: *anyopaque, msg: []const u8) void;
pub const onRespFn = *const fn (ctx: *anyopaque, call_id: u32, msg: []const u8) void;
pub const onRunMessageLoopOnPauseFn = *const fn (ctx: *anyopaque, context_group_id: u32) void;
pub const onQuitMessageLoopOnPauseFn = *const fn (ctx: *anyopaque) void;
fn init(
ctx: *anyopaque,
onResp: onRespFn,
onNotif: onNotifFn,
onRunMessageLoopOnPause: onRunMessageLoopOnPauseFn,
onQuitMessageLoopOnPause: onQuitMessageLoopOnPauseFn,
isolate: *v8.Isolate,
) Channel {
const handle = v8.v8_inspector__Channel__IMPL__CREATE(isolate);
return .{
.handle = handle,
.ctx = ctx,
.onResp = onResp,
.onNotif = onNotif,
.onRunMessageLoopOnPause = onRunMessageLoopOnPause,
.onQuitMessageLoopOnPause = onQuitMessageLoopOnPause,
};
}
fn deinit(self: Channel) void {
v8.v8_inspector__Channel__IMPL__DELETE(self.handle);
}
fn setInspector(self: Channel, inspector: *anyopaque) void {
v8.v8_inspector__Channel__IMPL__SET_DATA(self.handle, inspector);
}
fn resp(self: Channel, call_id: u32, msg: []const u8) void {
self.onResp(self.ctx, call_id, msg);
}
fn notif(self: Channel, msg: []const u8) void {
self.onNotif(self.ctx, msg);
}
};
const Client = struct {
handle: *v8.InspectorClientImpl,
fn init() Client {
return .{ .handle = v8.v8_inspector__Client__IMPL__CREATE() };
}
fn deinit(self: Client) void {
v8.v8_inspector__Client__IMPL__DELETE(self.handle);
}
fn setInspector(self: Client, inspector: *anyopaque) void {
v8.v8_inspector__Client__IMPL__SET_DATA(self.handle, inspector);
}
};
const NoopInspector = struct {
pub fn onInspectorResponse(_: *anyopaque, _: u32, _: []const u8) void {}
pub fn onInspectorEvent(_: *anyopaque, _: []const u8) void {}
pub fn onRunMessageLoopOnPause(_: *anyopaque, _: u32) void {}
pub fn onQuitMessageLoopOnPause(_: *anyopaque) void {}
};
fn fromData(data: *anyopaque) *Inspector {
return @ptrCast(@alignCast(data));
}
pub fn getTaggedOpaque(value: *const v8.Value) ?*TaggedOpaque { pub fn getTaggedOpaque(value: *const v8.Value) ?*TaggedOpaque {
if (!v8.v8__Value__IsObject(value)) { if (!v8.v8__Value__IsObject(value)) {
return null; return null;
@@ -437,24 +380,25 @@ pub export fn v8_inspector__Client__IMPL__generateUniqueId(
data: *anyopaque, data: *anyopaque,
) callconv(.c) i64 { ) callconv(.c) i64 {
const inspector: *Inspector = @ptrCast(@alignCast(data)); const inspector: *Inspector = @ptrCast(@alignCast(data));
return inspector.rnd.random().int(i64); const unique_id = inspector.unique_id + 1;
inspector.unique_id = unique_id;
return unique_id;
} }
pub export fn v8_inspector__Client__IMPL__runMessageLoopOnPause( pub export fn v8_inspector__Client__IMPL__runMessageLoopOnPause(
_: *v8.InspectorClientImpl, _: *v8.InspectorClientImpl,
data: *anyopaque, data: *anyopaque,
ctx_group_id: c_int, context_group_id: c_int,
) callconv(.c) void { ) callconv(.c) void {
const inspector: *Inspector = @ptrCast(@alignCast(data)); _ = data;
inspector.channel.onRunMessageLoopOnPause(inspector.channel.ctx, @intCast(ctx_group_id)); _ = context_group_id;
} }
pub export fn v8_inspector__Client__IMPL__quitMessageLoopOnPause( pub export fn v8_inspector__Client__IMPL__quitMessageLoopOnPause(
_: *v8.InspectorClientImpl, _: *v8.InspectorClientImpl,
data: *anyopaque, data: *anyopaque,
) callconv(.c) void { ) callconv(.c) void {
const inspector: *Inspector = @ptrCast(@alignCast(data)); _ = data;
inspector.channel.onQuitMessageLoopOnPause(inspector.channel.ctx);
} }
pub export fn v8_inspector__Client__IMPL__runIfWaitingForDebugger( pub export fn v8_inspector__Client__IMPL__runIfWaitingForDebugger(
@@ -493,8 +437,8 @@ pub export fn v8_inspector__Channel__IMPL__sendResponse(
msg: [*c]u8, msg: [*c]u8,
length: usize, length: usize,
) callconv(.c) void { ) callconv(.c) void {
const inspector: *Inspector = @ptrCast(@alignCast(data)); const session: *Session = @ptrCast(@alignCast(data));
inspector.channel.resp(@as(u32, @intCast(call_id)), msg[0..length]); session.onResp(session.ctx, @intCast(call_id), msg[0..length]);
} }
pub export fn v8_inspector__Channel__IMPL__sendNotification( pub export fn v8_inspector__Channel__IMPL__sendNotification(
@@ -503,8 +447,8 @@ pub export fn v8_inspector__Channel__IMPL__sendNotification(
msg: [*c]u8, msg: [*c]u8,
length: usize, length: usize,
) callconv(.c) void { ) callconv(.c) void {
const inspector: *Inspector = @ptrCast(@alignCast(data)); const session: *Session = @ptrCast(@alignCast(data));
inspector.channel.notif(msg[0..length]); session.onNotif(session.ctx, msg[0..length]);
} }
pub export fn v8_inspector__Channel__IMPL__flushProtocolNotifications( pub export fn v8_inspector__Channel__IMPL__flushProtocolNotifications(

View File

@@ -80,7 +80,9 @@ pub fn CDPT(comptime TypeProvider: type) type {
pub fn init(app: *App, client: TypeProvider.Client) !Self { pub fn init(app: *App, client: TypeProvider.Client) !Self {
const allocator = app.allocator; const allocator = app.allocator;
const browser = try Browser.init(app); const browser = try Browser.init(app, .{
.env = .{ .with_inspector = true },
});
errdefer browser.deinit(); errdefer browser.deinit();
return .{ return .{
@@ -352,7 +354,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
node_registry: Node.Registry, node_registry: Node.Registry,
node_search_list: Node.Search.List, node_search_list: Node.Search.List,
inspector: *js.Inspector, inspector_session: *js.Inspector.Session,
isolated_worlds: std.ArrayListUnmanaged(IsolatedWorld), isolated_worlds: std.ArrayListUnmanaged(IsolatedWorld),
http_proxy_changed: bool = false, http_proxy_changed: bool = false,
@@ -381,7 +383,9 @@ pub fn BrowserContext(comptime CDP_T: type) type {
const session = try cdp.browser.newSession(); const session = try cdp.browser.newSession();
const arena = session.arena; const arena = session.arena;
const inspector = try cdp.browser.env.newInspector(arena, self); const browser = &cdp.browser;
const inspector_session = browser.env.inspector.?.startSession(self);
errdefer browser.env.inspector.?.stopSession();
var registry = Node.Registry.init(allocator); var registry = Node.Registry.init(allocator);
errdefer registry.deinit(); errdefer registry.deinit();
@@ -400,7 +404,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
.node_registry = registry, .node_registry = registry,
.node_search_list = undefined, .node_search_list = undefined,
.isolated_worlds = .empty, .isolated_worlds = .empty,
.inspector = inspector, .inspector_session = inspector_session,
.notification_arena = cdp.notification_arena.allocator(), .notification_arena = cdp.notification_arena.allocator(),
.intercept_state = try InterceptState.init(allocator), .intercept_state = try InterceptState.init(allocator),
.captured_responses = .empty, .captured_responses = .empty,
@@ -409,27 +413,28 @@ pub fn BrowserContext(comptime CDP_T: type) type {
self.node_search_list = Node.Search.List.init(allocator, &self.node_registry); self.node_search_list = Node.Search.List.init(allocator, &self.node_registry);
errdefer self.deinit(); errdefer self.deinit();
try cdp.browser.notification.register(.page_remove, self, onPageRemove); try browser.notification.register(.page_remove, self, onPageRemove);
try cdp.browser.notification.register(.page_created, self, onPageCreated); try browser.notification.register(.page_created, self, onPageCreated);
try cdp.browser.notification.register(.page_navigate, self, onPageNavigate); try browser.notification.register(.page_navigate, self, onPageNavigate);
try cdp.browser.notification.register(.page_navigated, self, onPageNavigated); try browser.notification.register(.page_navigated, self, onPageNavigated);
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
// safe to call even if never registered // safe to call even if never registered
log.unregisterInterceptor(); log.unregisterInterceptor();
self.log_interceptor.deinit(); self.log_interceptor.deinit();
const browser = &self.cdp.browser;
// Drain microtasks makes sure we don't have inspector's callback // Drain microtasks makes sure we don't have inspector's callback
// in progress before deinit. // in progress before deinit.
self.cdp.browser.env.runMicrotasks(); browser.env.runMicrotasks();
// resetContextGroup detach the inspector from all contexts. // resetContextGroup detach the inspector from all contexts.
// It append async tasks, so we make sure we run the message loop // It append async tasks, so we make sure we run the message loop
// before deinit it. // before deinit it.
self.inspector.resetContextGroup(); browser.env.inspector.?.resetContextGroup();
self.session.browser.runMessageLoop(); browser.runMessageLoop();
self.inspector.deinit(); browser.env.inspector.?.stopSession();
// abort all intercepted requests before closing the sesion/page // abort all intercepted requests before closing the sesion/page
// since some of these might callback into the page/scriptmanager // since some of these might callback into the page/scriptmanager
@@ -445,16 +450,16 @@ pub fn BrowserContext(comptime CDP_T: type) type {
// If the session has a page, we need to clear it first. The page // If the session has a page, we need to clear it first. The page
// context is always nested inside of the isolated world context, // context is always nested inside of the isolated world context,
// so we need to shutdown the page one first. // so we need to shutdown the page one first.
self.cdp.browser.closeSession(); browser.closeSession();
self.node_registry.deinit(); self.node_registry.deinit();
self.node_search_list.deinit(); self.node_search_list.deinit();
self.cdp.browser.notification.unregisterAll(self); browser.notification.unregisterAll(self);
if (self.http_proxy_changed) { if (self.http_proxy_changed) {
// has to be called after browser.closeSession, since it won't // has to be called after browser.closeSession, since it won't
// work if there are active connections. // work if there are active connections.
self.cdp.browser.http_client.restoreOriginalProxy() catch |err| { browser.http_client.restoreOriginalProxy() catch |err| {
log.warn(.http, "restoreOriginalProxy", .{ .err = err }); log.warn(.http, "restoreOriginalProxy", .{ .err = err });
}; };
} }
@@ -650,9 +655,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
} }
pub fn callInspector(self: *const Self, msg: []const u8) void { pub fn callInspector(self: *const Self, msg: []const u8) void {
self.inspector.send(msg); self.inspector_session.send(msg);
// force running micro tasks after send input to the inspector.
self.cdp.browser.runMicrotasks();
} }
pub fn onInspectorResponse(ctx: *anyopaque, _: u32, msg: []const u8) void { pub fn onInspectorResponse(ctx: *anyopaque, _: u32, msg: []const u8) void {
@@ -678,18 +681,6 @@ pub fn BrowserContext(comptime CDP_T: type) type {
}; };
} }
// debugger events
pub fn onRunMessageLoopOnPause(_: *anyopaque, _: u32) void {
// onRunMessageLoopOnPause is called when a breakpoint is hit.
// Until quit pause, we must continue to run a nested message loop
// to interact with the the debugger ony (ie. Chrome DevTools).
}
pub fn onQuitMessageLoopOnPause(_: *anyopaque) void {
// Quit breakpoint pause.
}
// This is hacky x 2. First, we create the JSON payload by gluing our // This is hacky x 2. First, we create the JSON payload by gluing our
// session_id onto it. Second, we're much more client/websocket aware than // session_id onto it. Second, we're much more client/websocket aware than
// we should be. // we should be.

View File

@@ -305,7 +305,7 @@ fn resolveNode(cmd: anytype) !void {
// node._node is a *DOMNode we need this to be able to find its most derived type e.g. Node -> Element -> HTMLElement // node._node is a *DOMNode we need this to be able to find its most derived type e.g. Node -> Element -> HTMLElement
// So we use the Node.Union when retrieve the value from the environment // So we use the Node.Union when retrieve the value from the environment
const remote_object = try bc.inspector.getRemoteObject( const remote_object = try bc.inspector_session.getRemoteObject(
&ls.?.local, &ls.?.local,
params.objectGroup orelse "", params.objectGroup orelse "",
node.dom, node.dom,
@@ -404,7 +404,7 @@ fn getNode(arena: Allocator, bc: anytype, node_id: ?Node.Id, backend_node_id: ?N
defer ls.deinit(); defer ls.deinit();
// Retrieve the object from which ever context it is in. // Retrieve the object from which ever context it is in.
const parser_node = try bc.inspector.getNodePtr(arena, object_id_, &ls.local); const parser_node = try bc.inspector_session.getNodePtr(arena, object_id_, &ls.local);
return try bc.node_registry.register(@ptrCast(@alignCast(parser_node))); return try bc.node_registry.register(@ptrCast(@alignCast(parser_node)));
} }
return error.MissingParams; return error.MissingParams;

View File

@@ -189,17 +189,9 @@ fn createIsolatedWorld(cmd: anytype) !void {
const world = try bc.createIsolatedWorld(params.worldName, params.grantUniveralAccess); const world = try bc.createIsolatedWorld(params.worldName, params.grantUniveralAccess);
const page = bc.session.currentPage() orelse return error.PageNotLoaded; const page = bc.session.currentPage() orelse return error.PageNotLoaded;
const js_context = try world.createContext(page); const js_context = try world.createContext(page);
return cmd.sendResult(.{ .executionContextId = js_context.id }, .{});
// Create the auxdata json for the contextCreated event
// Calling contextCreated will assign a Id to the context and send the contextCreated event
const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{params.frameId});
var ls: js.Local.Scope = undefined;
js_context.localScope(&ls);
defer ls.deinit();
bc.inspector.contextCreated(&ls.local, world.name, "", aux_data, false);
return cmd.sendResult(.{ .executionContextId = ls.local.debugContextId() }, .{});
} }
fn navigate(cmd: anytype) !void { fn navigate(cmd: anytype) !void {
@@ -359,7 +351,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
page.js.localScope(&ls); page.js.localScope(&ls);
defer ls.deinit(); defer ls.deinit();
bc.inspector.contextCreated( bc.inspector_session.inspector.contextCreated(
&ls.local, &ls.local,
"", "",
try page.getOrigin(arena) orelse "", try page.getOrigin(arena) orelse "",
@@ -376,7 +368,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
(isolated_world.context orelse continue).localScope(&ls); (isolated_world.context orelse continue).localScope(&ls);
defer ls.deinit(); defer ls.deinit();
bc.inspector.contextCreated( bc.inspector_session.inspector.contextCreated(
&ls.local, &ls.local,
isolated_world.name, isolated_world.name,
"://", "://",

View File

@@ -182,7 +182,7 @@ fn createTarget(cmd: anytype) !void {
defer ls.deinit(); defer ls.deinit();
const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id}); const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});
bc.inspector.contextCreated( bc.inspector_session.inspector.contextCreated(
&ls.local, &ls.local,
"", "",
"", // @ZIGDOM "", // @ZIGDOM

View File

@@ -37,7 +37,7 @@ pub const FetchOpts = struct {
writer: ?*std.Io.Writer = null, writer: ?*std.Io.Writer = null,
}; };
pub fn fetch(app: *App, url: [:0]const u8, opts: FetchOpts) !void { pub fn fetch(app: *App, url: [:0]const u8, opts: FetchOpts) !void {
var browser = try Browser.init(app); var browser = try Browser.init(app, .{});
defer browser.deinit(); defer browser.deinit();
var session = try browser.newSession(); var session = try browser.newSession();

View File

@@ -43,7 +43,7 @@ pub fn main() !void {
var test_arena = std.heap.ArenaAllocator.init(allocator); var test_arena = std.heap.ArenaAllocator.init(allocator);
defer test_arena.deinit(); defer test_arena.deinit();
var browser = try lp.Browser.init(app); var browser = try lp.Browser.init(app, .{});
defer browser.deinit(); defer browser.deinit();
const session = try browser.newSession(); const session = try browser.newSession();

View File

@@ -65,7 +65,7 @@ pub fn main() !void {
}); });
defer app.deinit(); defer app.deinit();
var browser = try lp.Browser.init(app); var browser = try lp.Browser.init(app, .{});
defer browser.deinit(); defer browser.deinit();
// An arena for running each tests. Is reset after every test. // An arena for running each tests. Is reset after every test.

View File

@@ -460,7 +460,7 @@ test "tests:beforeAll" {
}); });
errdefer test_app.deinit(); errdefer test_app.deinit();
test_browser = try Browser.init(test_app); test_browser = try Browser.init(test_app, .{});
errdefer test_browser.deinit(); errdefer test_browser.deinit();
test_session = try test_browser.newSession(); test_session = try test_browser.newSession();