diff --git a/src/browser/js/Env.zig b/src/browser/js/Env.zig index fd09044d..c025234d 100644 --- a/src/browser/js/Env.zig +++ b/src/browser/js/Env.zig @@ -119,8 +119,10 @@ pub fn deinit(self: *Env) void { self.allocator.free(self.templates); } -pub fn newInspector(self: *Env, arena: Allocator, ctx: anytype) !Inspector { - return Inspector.init(arena, self.isolate, ctx); +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 { @@ -128,11 +130,11 @@ pub fn runMicrotasks(self: *const Env) void { } pub fn pumpMessageLoop(self: *const Env) bool { - return self.platform.inner.pumpMessageLoop(self.isolate, false); + return v8.c.v8__Platform__PumpMessageLoop(self.platform.handle, self.isolate.handle, false); } pub fn runIdleTasks(self: *const Env) void { - return self.platform.inner.runIdleTasks(self.isolate, 1); + v8.c.v8__Platform__RunIdleTasks(self.platform.handle, self.isolate.handle, 1); } pub fn newExecutionWorld(self: *Env) !ExecutionWorld { return .{ diff --git a/src/browser/js/Function.zig b/src/browser/js/Function.zig index 21d45014..bafb1e40 100644 --- a/src/browser/js/Function.zig +++ b/src/browser/js/Function.zig @@ -37,16 +37,6 @@ pub fn id(self: *const Function) u32 { return @as(u32, @bitCast(v8.c.v8__Object__GetIdentityHash(@ptrCast(self.handle)))); } -pub fn getName(self: *const Function, allocator: Allocator) ![]const u8 { - const name = v8.c.v8__Function__GetName(self.handle).?; - return self.context.valueToString(name, .{ .allocator = allocator }); -} - -pub fn setName(self: *const Function, name: []const u8) void { - const v8_name = v8.String.initUtf8(self.context.isolate, name); - self.func.castToFunction().setName(v8_name); -} - pub fn withThis(self: *const Function, value: anytype) !Function { const this_obj = if (@TypeOf(value) == js.Object) value.js_obj @@ -181,8 +171,7 @@ fn getThis(self: *const Function) v8.Object { } pub fn src(self: *const Function) ![]const u8 { - const value = self.func.castToFunction().toValue(); - return self.context.valueToString(value, .{}); + return self.context.valueToString(.{ .handle = @ptrCast(self.handle) }, .{}); } pub fn getPropertyValue(self: *const Function, name: []const u8) !?js.Value { diff --git a/src/browser/js/Inspector.zig b/src/browser/js/Inspector.zig index cb8eb2bd..edf62a58 100644 --- a/src/browser/js/Inspector.zig +++ b/src/browser/js/Inspector.zig @@ -23,43 +23,76 @@ const v8 = js.v8; const Context = @import("Context.zig"); const Allocator = std.mem.Allocator; +const RndGen = std.Random.DefaultPrng; + +const CONTEST_GROUP_ID = 1; +const CLIENT_TRUST_LEVEL = 1; const Inspector = @This(); -pub const RemoteObject = v8.RemoteObject; - -isolate: v8.Isolate, -inner: *v8.Inspector, -session: v8.InspectorSession, +handle: *v8.c.Inspector, +isolate: *v8.c.Isolate, +client: Client, +channel: Channel, +session: Session, +rnd: RndGen = RndGen.init(0), +ctx_handle: ?*const v8.c.Context = null, // We expect allocator to be an arena -pub fn init(allocator: Allocator, isolate: v8.Isolate, ctx: anytype) !Inspector { +// Note: This initializes the pre-allocated inspector in-place +pub fn init(self: *Inspector, isolate: *v8.c.Isolate, ctx: anytype) !void { const ContextT = @TypeOf(ctx); - const InspectorContainer = switch (@typeInfo(ContextT)) { + 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; - const channel = v8.InspectorChannel.init( + // Initialize the fields that callbacks need first + self.* = .{ + .handle = undefined, + .isolate = isolate, + .client = undefined, + .channel = undefined, + .rnd = RndGen.init(0), + .ctx_handle = null, + .session = undefined, + }; + + // Create client and set inspector data BEFORE creating the inspector + // because V8 will call generateUniqueId during inspector creation + const client = Client.init(); + self.client = client; + client.setInspector(self); + + // Now create the inspector - generateUniqueId will work because data is set + const handle = v8.c.v8_inspector__Inspector__Create(isolate, client.handle).?; + self.handle = handle; + + // Create the channel + const channel = Channel.init( safe_context, - InspectorContainer.onInspectorResponse, - InspectorContainer.onInspectorEvent, - InspectorContainer.onRunMessageLoopOnPause, - InspectorContainer.onQuitMessageLoopOnPause, + Container.onInspectorResponse, + Container.onInspectorEvent, + Container.onRunMessageLoopOnPause, + Container.onQuitMessageLoopOnPause, isolate, ); + self.channel = channel; + channel.setInspector(self); - const client = v8.InspectorClient.init(); - - const inner = try allocator.create(v8.Inspector); - v8.Inspector.init(inner, client, channel, isolate); - return .{ .inner = inner, .isolate = isolate, .session = inner.connect() }; + // Create the session + const session_handle = v8.c.v8_inspector__Inspector__Connect( + handle, + CONTEST_GROUP_ID, + channel.handle, + CLIENT_TRUST_LEVEL, + ).?; + self.session = .{ .handle = session_handle }; } pub fn deinit(self: *const Inspector) void { @@ -68,7 +101,9 @@ pub fn deinit(self: *const Inspector) void { defer temp_scope.deinit(); self.session.deinit(); - self.inner.deinit(); + self.client.deinit(); + self.channel.deinit(); + v8.c.v8_inspector__Inspector__DELETE(self.handle); } pub fn send(self: *const Inspector, msg: []const u8) void { @@ -76,9 +111,9 @@ pub fn send(self: *const Inspector, msg: []const u8) void { // available when doing this. Pages (and thus the HandleScope) // comes and goes, but CDP can keep sending messages. const isolate = self.isolate; - var temp_scope: v8.HandleScope = undefined; - v8.HandleScope.init(&temp_scope, isolate); - defer temp_scope.deinit(); + var temp_scope: v8.c.HandleScope = undefined; + v8.c.v8__HandleScope__CONSTRUCT(&temp_scope, isolate); + defer v8.c.v8__HandleScope__DESTRUCT(&temp_scope); self.session.dispatchProtocolMessage(isolate, msg); } @@ -99,7 +134,27 @@ pub fn contextCreated( aux_data: ?[]const u8, is_default_context: bool, ) void { - self.inner.contextCreated(context.v8_context, name, origin, aux_data, is_default_context); + _ = is_default_context; // TODO: Should this be passed to the C API? + var auxData_ptr: [*c]const u8 = undefined; + var auxData_len: usize = undefined; + if (aux_data) |data| { + auxData_ptr = data.ptr; + auxData_len = data.len; + } else { + auxData_ptr = null; + auxData_len = 0; + } + v8.c.v8_inspector__Inspector__ContextCreated( + self.handle, + name.ptr, + name.len, + origin.ptr, + origin.len, + auxData_ptr, + auxData_len, + CONTEST_GROUP_ID, + context.v8_context.handle, + ); } // Retrieves the RemoteObject for a given value. @@ -118,9 +173,9 @@ pub fn getRemoteObject( // We do not want to expose this as a parameter for now const generate_preview = false; return self.session.wrapObject( - context.isolate, - context.v8_context, - js_value, + context.isolate.handle, + context.v8_context.handle, + js_value.handle, group, generate_preview, ); @@ -136,15 +191,210 @@ pub fn getNodePtr(self: *const Inspector, allocator: Allocator, object_id: []con const unwrapped = try self.session.unwrapObject(allocator, object_id); // The values context and groupId are not used here const js_val = unwrapped.value; - if (js_val.isObject() == false) { + if (!v8.c.v8__Value__IsObject(js_val)) { return error.ObjectIdIsNotANode; } const Node = @import("../webapi/Node.zig"); - return Context.typeTaggedAnyOpaque(*Node, js_val.castTo(v8.Object)) catch { + // Wrap the C handle in a v8.Object for typeTaggedAnyOpaque + const js_obj = v8.Object{ .handle = js_val }; + return Context.typeTaggedAnyOpaque(*Node, js_obj) catch { return error.ObjectIdIsNotANode; }; } +pub const RemoteObject = struct { + handle: *v8.c.RemoteObject, + + pub fn deinit(self: RemoteObject) void { + v8.c.v8_inspector__RemoteObject__DELETE(self.handle); + } + + pub fn getType(self: RemoteObject, allocator: Allocator) ![]const u8 { + var ctype_: v8.c.CZigString = .{ .ptr = null, .len = 0 }; + if (!v8.c.v8_inspector__RemoteObject__getType(self.handle, &allocator, &ctype_)) return error.V8AllocFailed; + return cZigStringToString(ctype_) orelse return error.InvalidType; + } + + pub fn getSubtype(self: RemoteObject, allocator: Allocator) !?[]const u8 { + if (!v8.c.v8_inspector__RemoteObject__hasSubtype(self.handle)) return null; + + var csubtype: v8.c.CZigString = .{ .ptr = null, .len = 0 }; + if (!v8.c.v8_inspector__RemoteObject__getSubtype(self.handle, &allocator, &csubtype)) return error.V8AllocFailed; + return cZigStringToString(csubtype); + } + + pub fn getClassName(self: RemoteObject, allocator: Allocator) !?[]const u8 { + if (!v8.c.v8_inspector__RemoteObject__hasClassName(self.handle)) return null; + + var cclass_name: v8.c.CZigString = .{ .ptr = null, .len = 0 }; + if (!v8.c.v8_inspector__RemoteObject__getClassName(self.handle, &allocator, &cclass_name)) return error.V8AllocFailed; + return cZigStringToString(cclass_name); + } + + pub fn getDescription(self: RemoteObject, allocator: Allocator) !?[]const u8 { + if (!v8.c.v8_inspector__RemoteObject__hasDescription(self.handle)) return null; + + var description: v8.c.CZigString = .{ .ptr = null, .len = 0 }; + if (!v8.c.v8_inspector__RemoteObject__getDescription(self.handle, &allocator, &description)) return error.V8AllocFailed; + return cZigStringToString(description); + } + + pub fn getObjectId(self: RemoteObject, allocator: Allocator) !?[]const u8 { + if (!v8.c.v8_inspector__RemoteObject__hasObjectId(self.handle)) return null; + + var cobject_id: v8.c.CZigString = .{ .ptr = null, .len = 0 }; + if (!v8.c.v8_inspector__RemoteObject__getObjectId(self.handle, &allocator, &cobject_id)) return error.V8AllocFailed; + return cZigStringToString(cobject_id); + } +}; + +const Session = struct { + handle: *v8.c.InspectorSession, + + fn deinit(self: Session) void { + v8.c.v8_inspector__Session__DELETE(self.handle); + } + + fn dispatchProtocolMessage(self: Session, isolate: *v8.c.Isolate, msg: []const u8) void { + v8.c.v8_inspector__Session__dispatchProtocolMessage( + self.handle, + isolate, + msg.ptr, + msg.len, + ); + } + + fn wrapObject( + self: Session, + isolate: *v8.c.Isolate, + ctx: *const v8.c.Context, + val: *const v8.c.Value, + grpname: []const u8, + generatepreview: bool, + ) !RemoteObject { + const remote_object = v8.c.v8_inspector__Session__wrapObject( + self.handle, + isolate, + ctx, + val, + grpname.ptr, + grpname.len, + generatepreview, + ).?; + return .{ .handle = remote_object }; + } + + fn unwrapObject( + self: Session, + allocator: Allocator, + object_id: []const u8, + ) !UnwrappedObject { + const in_object_id = v8.c.CZigString{ + .ptr = object_id.ptr, + .len = object_id.len, + }; + var out_error: v8.c.CZigString = .{ .ptr = null, .len = 0 }; + var out_value_handle: ?*v8.c.Value = null; + var out_context_handle: ?*v8.c.Context = null; + var out_object_group: v8.c.CZigString = .{ .ptr = null, .len = 0 }; + + const result = v8.c.v8_inspector__Session__unwrapObject( + self.handle, + &allocator, + &out_error, + in_object_id, + &out_value_handle, + &out_context_handle, + &out_object_group, + ); + + if (!result) { + const error_str = cZigStringToString(out_error) orelse return error.UnwrapFailed; + std.log.err("unwrapObject failed: {s}", .{error_str}); + return error.UnwrapFailed; + } + + return .{ + .value = out_value_handle.?, + .context = out_context_handle.?, + .object_group = cZigStringToString(out_object_group), + }; + } +}; + +const UnwrappedObject = struct { + value: *const v8.c.Value, + context: *const v8.c.Context, + object_group: ?[]const u8, +}; + +const Channel = struct { + handle: *v8.c.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.c.Isolate, + ) Channel { + const handle = v8.c.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.c.v8_inspector__Channel__IMPL__DELETE(self.handle); + } + + fn setInspector(self: Channel, inspector: *anyopaque) void { + v8.c.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.c.InspectorClientImpl, + + fn init() Client { + return .{ .handle = v8.c.v8_inspector__Client__IMPL__CREATE() }; + } + + fn deinit(self: Client) void { + v8.c.v8_inspector__Client__IMPL__DELETE(self.handle); + } + + fn setInspector(self: Client, inspector: *anyopaque) void { + v8.c.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 {} @@ -152,15 +402,107 @@ const NoopInspector = struct { pub fn onQuitMessageLoopOnPause(_: *anyopaque) void {} }; -pub fn getTaggedAnyOpaque(value: v8.Value) ?*js.TaggedAnyOpaque { - if (value.isObject() == false) { +fn fromData(data: *anyopaque) *Inspector { + return @ptrCast(@alignCast(data)); +} + +pub fn getTaggedAnyOpaque(value: *const v8.c.Value) ?*js.TaggedAnyOpaque { + if (!v8.c.v8__Value__IsObject(value)) { return null; } - const obj = value.castTo(v8.Object); - if (obj.internalFieldCount() == 0) { + const internal_field_count = v8.c.v8__Object__InternalFieldCount(value); + if (internal_field_count == 0) { return null; } - const external_data = obj.getInternalField(0).castTo(v8.External).get().?; + const external_value = v8.c.v8__Object__GetInternalField(value, 0).?; + const external_data = v8.c.v8__External__Value(external_value).?; return @ptrCast(@alignCast(external_data)); } + +fn cZigStringToString(s: v8.c.CZigString) ?[]const u8 { + if (s.ptr == null) return null; + return s.ptr[0..s.len]; +} + +// C export functions for Inspector callbacks +pub export fn v8_inspector__Client__IMPL__generateUniqueId( + _: *v8.c.InspectorClientImpl, + data: *anyopaque, +) callconv(.c) i64 { + const inspector: *Inspector = @ptrCast(@alignCast(data)); + return inspector.rnd.random().int(i64); +} + +pub export fn v8_inspector__Client__IMPL__runMessageLoopOnPause( + _: *v8.c.InspectorClientImpl, + data: *anyopaque, + ctx_group_id: c_int, +) callconv(.c) void { + const inspector: *Inspector = @ptrCast(@alignCast(data)); + inspector.channel.onRunMessageLoopOnPause(inspector.channel.ctx, @intCast(ctx_group_id)); +} + +pub export fn v8_inspector__Client__IMPL__quitMessageLoopOnPause( + _: *v8.c.InspectorClientImpl, + data: *anyopaque, +) callconv(.c) void { + const inspector: *Inspector = @ptrCast(@alignCast(data)); + inspector.channel.onQuitMessageLoopOnPause(inspector.channel.ctx); +} + +pub export fn v8_inspector__Client__IMPL__runIfWaitingForDebugger( + _: *v8.c.InspectorClientImpl, + _: *anyopaque, + _: c_int, +) callconv(.c) void { + // TODO +} + +pub export fn v8_inspector__Client__IMPL__consoleAPIMessage( + _: *v8.c.InspectorClientImpl, + _: *anyopaque, + _: c_int, + _: v8.c.MessageErrorLevel, + _: *v8.c.StringView, + _: *v8.c.StringView, + _: c_uint, + _: c_uint, + _: *v8.c.StackTrace, +) callconv(.c) void {} + +pub export fn v8_inspector__Client__IMPL__ensureDefaultContextInGroup( + _: *v8.c.InspectorClientImpl, + data: *anyopaque, +) callconv(.c) ?*const v8.c.Context { + const inspector: *Inspector = @ptrCast(@alignCast(data)); + return inspector.ctx_handle; +} + +pub export fn v8_inspector__Channel__IMPL__sendResponse( + _: *v8.c.InspectorChannelImpl, + data: *anyopaque, + call_id: c_int, + msg: [*c]u8, + length: usize, +) callconv(.c) void { + const inspector: *Inspector = @ptrCast(@alignCast(data)); + inspector.channel.resp(@as(u32, @intCast(call_id)), msg[0..length]); +} + +pub export fn v8_inspector__Channel__IMPL__sendNotification( + _: *v8.c.InspectorChannelImpl, + data: *anyopaque, + msg: [*c]u8, + length: usize, +) callconv(.c) void { + const inspector: *Inspector = @ptrCast(@alignCast(data)); + inspector.channel.notif(msg[0..length]); +} + +pub export fn v8_inspector__Channel__IMPL__flushProtocolNotifications( + _: *v8.c.InspectorChannelImpl, + _: *anyopaque, +) callconv(.c) void { + // TODO +} diff --git a/src/browser/js/Platform.zig b/src/browser/js/Platform.zig index dcec9fe6..6390abad 100644 --- a/src/browser/js/Platform.zig +++ b/src/browser/js/Platform.zig @@ -20,20 +20,22 @@ const js = @import("js.zig"); const v8 = js.v8; const Platform = @This(); -inner: v8.Platform, +handle: *v8.c.Platform, pub fn init() !Platform { - if (v8.initV8ICU() == false) { + if (v8.c.v8__V8__InitializeICU() == false) { return error.FailedToInitializeICU; } - const platform = v8.Platform.initDefault(0, true); - v8.initV8Platform(platform); - v8.initV8(); - return .{ .inner = platform }; + // 0 - threadpool size, 0 == let v8 decide + // 1 - idle_task_support, 1 == enabled + const handle = v8.c.v8__Platform__NewDefaultPlatform(0, 1).?; + v8.c.v8__V8__InitializePlatform(handle); + v8.c.v8__V8__Initialize(); + return .{ .handle = handle }; } pub fn deinit(self: Platform) void { - _ = v8.deinitV8(); - v8.deinitV8Platform(); - self.inner.deinit(); + _ = v8.c.v8__V8__Dispose(); + v8.c.v8__V8__DisposePlatform(); + v8.c.v8__Platform__DELETE(self.handle); } diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index 55b9b724..45c1c327 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -481,7 +481,7 @@ pub export fn v8_inspector__Client__IMPL__valueSubtype( _: *v8.c.InspectorClientImpl, c_value: *const v8.C_Value, ) callconv(.c) [*c]const u8 { - const external_entry = Inspector.getTaggedAnyOpaque(.{ .handle = c_value }) orelse return null; + const external_entry = Inspector.getTaggedAnyOpaque(c_value) orelse return null; return if (external_entry.subtype) |st| @tagName(st) else null; } @@ -498,10 +498,17 @@ pub export fn v8_inspector__Client__IMPL__descriptionForValueSubtype( // We _must_ include a non-null description in order for the subtype value // to be included. Besides that, I don't know if the value has any meaning - const external_entry = Inspector.getTaggedAnyOpaque(.{ .handle = c_value }) orelse return null; + const external_entry = Inspector.getTaggedAnyOpaque(c_value) orelse return null; return if (external_entry.subtype == null) null else ""; } +/// Enables C to allocate using the given Zig allocator +pub export fn zigAlloc(self: *anyopaque, bytes: usize) callconv(.c) ?[*]u8 { + const allocator: *Allocator = @ptrCast(@alignCast(self)); + const allocated_bytes = allocator.alloc(u8, bytes) catch return null; + return allocated_bytes.ptr; +} + test "TaggedAnyOpaque" { // If we grow this, fine, but it should be a conscious decision try std.testing.expectEqual(24, @sizeOf(TaggedAnyOpaque)); diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 9a62c267..3aef758a 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -331,7 +331,7 @@ pub fn BrowserContext(comptime CDP_T: type) type { node_registry: Node.Registry, node_search_list: Node.Search.List, - inspector: js.Inspector, + inspector: *js.Inspector, isolated_worlds: std.ArrayListUnmanaged(IsolatedWorld), http_proxy_changed: bool = false, diff --git a/src/main_wpt.zig b/src/main_wpt.zig index e140b37e..27df82c1 100644 --- a/src/main_wpt.zig +++ b/src/main_wpt.zig @@ -135,7 +135,7 @@ fn run( return err; }; - return value.toString(arena); + return value.toString(.{ .allocator = arena }); } const Writer = struct {