diff --git a/build.zig.zon b/build.zig.zon index c2299d4b..d068608b 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -13,8 +13,8 @@ .hash = "tigerbeetle_io-0.0.0-ViLgxpyRBAB5BMfIcj3KMXfbJzwARs9uSl8aRy2OXULd", }, .v8 = .{ - .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/faab44996a5cb74c71592bda404208fde4bf2e63.tar.gz", - .hash = "v8-0.0.0-xddH6xWyAwB_NFICSO4Q3O-c7gDKnYiwky5FhQzTZMIr", + .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/bf7ba696b3e819195f8fc349b2778c59aab81a61.tar.gz", + .hash = "v8-0.0.0-xddH6xm3AwA287seRdWB_mIjZ9_Ayk-81z9uwWoag7Er", }, //.v8 = .{ .path = "../zig-v8-fork" }, //.tigerbeetle_io = .{ .path = "../tigerbeetle-io" }, diff --git a/src/cdp/cbor/cbor.zig b/src/cdp/cbor/cbor.zig new file mode 100644 index 00000000..5ea18b15 --- /dev/null +++ b/src/cdp/cbor/cbor.zig @@ -0,0 +1,52 @@ +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pub const jsonToCbor = @import("json_to_cbor.zig").jsonToCbor; +pub const cborToJson = @import("cbor_to_json.zig").cborToJson; + +const testing = @import("../../testing.zig"); + +test "cbor" { + try testCbor("{\"x\":null}"); + try testCbor("{\"x\":true}"); + try testCbor("{\"x\":false}"); + try testCbor("{\"x\":0}"); + try testCbor("{\"x\":1}"); + try testCbor("{\"x\":-1}"); + try testCbor("{\"x\":4832839283}"); + try testCbor("{\"x\":-998128383}"); + try testCbor("{\"x\":48328.39283}"); + try testCbor("{\"x\":-9981.28383}"); + try testCbor("{\"x\":\"\"}"); + try testCbor("{\"x\":\"over 9000!\"}"); + + try testCbor("{\"x\":[]}"); + try testCbor("{\"x\":{}}"); +} + +fn testCbor(json: []const u8) !void { + const std = @import("std"); + + defer testing.reset(); + const encoded = try jsonToCbor(testing.arena_allocator, json); + + var arr: std.ArrayListUnmanaged(u8) = .empty; + try cborToJson(encoded, arr.writer(testing.arena_allocator)); + + try testing.expectEqual(json, arr.items); +} diff --git a/src/cdp/cbor/cbor_to_json.zig b/src/cdp/cbor/cbor_to_json.zig new file mode 100644 index 00000000..6694a920 --- /dev/null +++ b/src/cdp/cbor/cbor_to_json.zig @@ -0,0 +1,252 @@ +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); + +const Allocator = std.mem.Allocator; + +const Error = error{ + EOSReadingFloat, + UnknownTag, + EOSReadingArray, + UnterminatedArray, + EOSReadingMap, + UnterminatedMap, + EOSReadingLength, + InvalidLength, + MissingData, + EOSExpectedString, + ExpectedString, + OutOfMemory, + EmbeddedDataIsShort, + InvalidEmbeddedDataEnvelope, +}; + +pub fn cborToJson(input: []const u8, writer: anytype) !void { + if (input.len < 7) { + return error.InvalidCBORMessage; + } + + var data = input; + while (data.len > 0) { + data = try writeValue(data, writer); + } +} + +fn writeValue(data: []const u8, writer: anytype) Error![]const u8 { + switch (data[0]) { + 0xf4 => { + try writer.writeAll("false"); + return data[1..]; + }, + 0xf5 => { + try writer.writeAll("true"); + return data[1..]; + }, + 0xf6, 0xf7 => { // 0xf7 is undefined + try writer.writeAll("null"); + return data[1..]; + }, + 0x9f => return writeInfiniteArray(data[1..], writer), + 0xbf => return writeInfiniteMap(data[1..], writer), + 0xd8 => { + // This is major type 6, which is generic tagged data. We only + // support 1 tag: embedded cbor data. + if (data.len < 7) { + return error.EmbeddedDataIsShort; + } + if (data[1] != 0x18 or data[2] != 0x5a) { + return error.InvalidEmbeddedDataEnvelope; + } + // skip the length, we have the full paylaod + return writeValue(data[7..], writer); + }, + 0xf9 => { // f16 + if (data.len < 3) { + return error.EOSReadingFloat; + } + try writer.print("{d}", .{@as(f16, @bitCast(std.mem.readInt(u16, data[1..3], .big)))}); + return data[3..]; + }, + 0xfa => { // f32 + if (data.len < 5) { + return error.EOSReadingFloat; + } + try writer.print("{d}", .{@as(f32, @bitCast(std.mem.readInt(u32, data[1..5], .big)))}); + return data[5..]; + }, + 0xfb => { // f64 + if (data.len < 9) { + return error.EOSReadingFloat; + } + try writer.print("{d}", .{@as(f64, @bitCast(std.mem.readInt(u64, data[1..9], .big)))}); + return data[9..]; + }, + else => |b| { + const major_type = b >> 5; + switch (major_type) { + 0 => { + const rest, const length = try parseLength(data); + try writer.print("{d}", .{length}); + return rest; + }, + 1 => { + const rest, const length = try parseLength(data); + try writer.print("{d}", .{-@as(i64, @intCast(length)) - 1}); + return rest; + }, + 2 => { + const rest, const str = try parseString(data); + try writer.writeByte('"'); + try std.base64.standard.Encoder.encodeWriter(writer, str); + try writer.writeByte('"'); + return rest; + }, + 3 => { + const rest, const str = try parseString(data); + try std.json.encodeJsonString(str, .{}, writer); + return rest; + }, + // 4 => unreachable, // fixed-length array + // 5 => unreachable, // fixed-length map + else => return error.UnknownTag, + } + }, + } +} + +// We expect every array from V8 to be an infinite-length array. That it, it +// starts with the special tag: (4<<5) | 31 which an "array" with infinite +// length. +// Of course, it isn't infite, the end of the array happens when we hit a break +// code which is FF (7 << 5) | 31 +fn writeInfiniteArray(d: []const u8, writer: anytype) ![]const u8 { + if (d.len == 0) { + return error.EOSReadingArray; + } + if (d[0] == 255) { + try writer.writeAll("[]"); + return d[1..]; + } + + try writer.writeByte('['); + var data = try writeValue(d, writer); + while (data.len > 0) { + if (data[0] == 255) { + try writer.writeByte(']'); + return data[1..]; + } + try writer.writeByte(','); + data = try writeValue(data, writer); + } + + // Reaching the end of the input is a mistake, should have reached the break + // code + return error.UnterminatedArray; +} + +// We expect every map from V8 to be an infinite-length map. That it, it +// starts with the special tag: (5<<5) | 31 which an "map" with infinite +// length. +// Of course, it isn't infite, the end of the map happens when we hit a break +// code which is FF (7 << 5) | 31 +fn writeInfiniteMap(d: []const u8, writer: anytype) ![]const u8 { + if (d.len == 0) { + return error.EOSReadingMap; + } + if (d[0] == 255) { + try writer.writeAll("{}"); + return d[1..]; + } + + try writer.writeByte('{'); + + var data = blk: { + const data, const field = try maybeParseString(d); + try std.json.encodeJsonString(field, .{}, writer); + try writer.writeByte(':'); + break :blk try writeValue(data, writer); + }; + + while (data.len > 0) { + if (data[0] == 255) { + try writer.writeByte('}'); + return data[1..]; + } + try writer.writeByte(','); + data, const field = try maybeParseString(data); + try std.json.encodeJsonString(field, .{}, writer); + try writer.writeByte(':'); + data = try writeValue(data, writer); + } + + // Reaching the end of the input is a mistake, should have reached the break + // code + return error.UnterminatedMap; +} + +fn parseLength(data: []const u8) !struct { []const u8, usize } { + std.debug.assert(data.len > 0); + switch (data[0] & 0b11111) { + 0...23 => |n| return .{ data[1..], n }, + 24 => { + if (data.len == 1) { + return error.EOSReadingLength; + } + return .{ data[2..], @intCast(data[1]) }; + }, + 25 => { + if (data.len < 3) { + return error.EOSReadingLength; + } + return .{ data[3..], @intCast(std.mem.readInt(u16, data[1..3], .big)) }; + }, + 26 => { + if (data.len < 5) { + return error.EOSReadingLength; + } + return .{ data[5..], @intCast(std.mem.readInt(u32, data[1..5], .big)) }; + }, + 27 => { + if (data.len < 9) { + return error.EOSReadingLength; + } + return .{ data[9..], @intCast(std.mem.readInt(u64, data[1..9], .big)) }; + }, + else => return error.InvalidLength, + } +} + +fn parseString(data: []const u8) !struct { []const u8, []const u8 } { + const rest, const length = try parseLength(data); + if (rest.len < length) { + return error.MissingData; + } + return .{ rest[length..], rest[0..length] }; +} + +fn maybeParseString(data: []const u8) !struct { []const u8, []const u8 } { + if (data.len == 0) { + return error.EOSExpectedString; + } + const b = data[0]; + if (b >> 5 != 3) { + return error.ExpectedString; + } + return parseString(data); +} diff --git a/src/cdp/cbor/json_to_cbor.zig b/src/cdp/cbor/json_to_cbor.zig new file mode 100644 index 00000000..fa0b7466 --- /dev/null +++ b/src/cdp/cbor/json_to_cbor.zig @@ -0,0 +1,173 @@ +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); + +const json = std.json; +const Allocator = std.mem.Allocator; + +const Error = error{ + InvalidJson, + OutOfMemory, + SyntaxError, + UnexpectedEndOfInput, + ValueTooLong, +}; + +pub fn jsonToCbor(arena: Allocator, input: []const u8) ![]const u8 { + var scanner = json.Scanner.initCompleteInput(arena, input); + defer scanner.deinit(); + + var arr: std.ArrayListUnmanaged(u8) = .empty; + try writeNext(arena, &arr, &scanner); + return arr.items; +} + +fn writeNext(arena: Allocator, arr: *std.ArrayListUnmanaged(u8), scanner: *json.Scanner) Error!void { + const token = scanner.nextAlloc(arena, .alloc_if_needed) catch return error.InvalidJson; + return writeToken(arena, arr, scanner, token); +} + +fn writeToken(arena: Allocator, arr: *std.ArrayListUnmanaged(u8), scanner: *json.Scanner, token: json.Token) Error!void { + switch (token) { + .object_begin => return writeObject(arena, arr, scanner), + .array_begin => return writeArray(arena, arr, scanner), + .true => return arr.append(arena, 7 << 5 | 21), + .false => return arr.append(arena, 7 << 5 | 20), + .null => return arr.append(arena, 7 << 5 | 22), + .allocated_string, .string => |key| return writeString(arena, arr, key), + .allocated_number, .number => |s| { + if (json.isNumberFormattedLikeAnInteger(s)) { + return writeInteger(arena, arr, s); + } + const f = std.fmt.parseFloat(f64, s) catch unreachable; + return writeHeader(arena, arr, 7, @intCast(@as(u64, @bitCast(f)))); + }, + else => unreachable, + } +} + +fn writeObject(arena: Allocator, arr: *std.ArrayListUnmanaged(u8), scanner: *json.Scanner) !void { + const envelope = try startEmbeddedMessage(arena, arr); + + // MajorType 5 (map) | 5-byte infinite length + try arr.append(arena, 5 << 5 | 31); + + while (true) { + switch (try scanner.nextAlloc(arena, .alloc_if_needed)) { + .allocated_string, .string => |key| { + try writeString(arena, arr, key); + try writeNext(arena, arr, scanner); + }, + .object_end => { + // MajorType 7 (break) | 5-byte infinite length + try arr.append(arena, 7 << 5 | 31); + return finalizeEmbeddedMessage(arr, envelope); + }, + else => return error.InvalidJson, + } + } +} + +fn writeArray(arena: Allocator, arr: *std.ArrayListUnmanaged(u8), scanner: *json.Scanner) !void { + const envelope = try startEmbeddedMessage(arena, arr); + + // MajorType 4 (array) | 5-byte infinite length + try arr.append(arena, 4 << 5 | 31); + while (true) { + const token = scanner.nextAlloc(arena, .alloc_if_needed) catch return error.InvalidJson; + switch (token) { + .array_end => { + // MajorType 7 (break) | 5-byte infinite length + try arr.append(arena, 7 << 5 | 31); + return finalizeEmbeddedMessage(arr, envelope); + }, + else => try writeToken(arena, arr, scanner, token), + } + } +} + +fn writeString(arena: Allocator, arr: *std.ArrayListUnmanaged(u8), value: []const u8) !void { + try writeHeader(arena, arr, 3, value.len); + return arr.appendSlice(arena, value); +} + +fn writeInteger(arena: Allocator, arr: *std.ArrayListUnmanaged(u8), s: []const u8) !void { + const n = std.fmt.parseInt(i64, s, 10) catch { + return error.InvalidJson; + }; + if (n >= 0) { + return writeHeader(arena, arr, 0, @intCast(n)); + } + return writeHeader(arena, arr, 1, @intCast(-1 - n)); +} + +fn writeHeader(arena: Allocator, arr: *std.ArrayListUnmanaged(u8), comptime typ: u8, count: usize) !void { + switch (count) { + 0...23 => try arr.append(arena, typ << 5 | @as(u8, @intCast(count))), + 24...255 => { + try arr.ensureUnusedCapacity(arena, 2); + arr.appendAssumeCapacity(typ << 5 | 24); + arr.appendAssumeCapacity(@intCast(count)); + }, + 256...65535 => { + try arr.ensureUnusedCapacity(arena, 3); + arr.appendAssumeCapacity(typ << 5 | 25); + arr.appendAssumeCapacity(@intCast((count >> 8) & 0xff)); + arr.appendAssumeCapacity(@intCast(count & 0xff)); + }, + 65536...4294967295 => { + try arr.ensureUnusedCapacity(arena, 5); + arr.appendAssumeCapacity(typ << 5 | 26); + arr.appendAssumeCapacity(@intCast((count >> 24) & 0xff)); + arr.appendAssumeCapacity(@intCast((count >> 16) & 0xff)); + arr.appendAssumeCapacity(@intCast((count >> 8) & 0xff)); + arr.appendAssumeCapacity(@intCast(count & 0xff)); + }, + else => { + try arr.ensureUnusedCapacity(arena, 9); + arr.appendAssumeCapacity(typ << 5 | 27); + arr.appendAssumeCapacity(@intCast((count >> 56) & 0xff)); + arr.appendAssumeCapacity(@intCast((count >> 48) & 0xff)); + arr.appendAssumeCapacity(@intCast((count >> 40) & 0xff)); + arr.appendAssumeCapacity(@intCast((count >> 32) & 0xff)); + arr.appendAssumeCapacity(@intCast((count >> 24) & 0xff)); + arr.appendAssumeCapacity(@intCast((count >> 16) & 0xff)); + arr.appendAssumeCapacity(@intCast((count >> 8) & 0xff)); + arr.appendAssumeCapacity(@intCast(count & 0xff)); + }, + } +} + +// I don't know why, but V8 expects any array or map (including the outer-most +// object), to be encoded as embedded cbor data. This is CBOR that contains CBOR. +// I feel that it's fine that it supports it, but why _require_ it? Seems like +// a waste of 7 bytes. +fn startEmbeddedMessage(arena: Allocator, arr: *std.ArrayListUnmanaged(u8)) !usize { + try arr.appendSlice(arena, &.{ 0xd8, 0x18, 0x5a, 0, 0, 0, 0 }); + return arr.items.len; +} + +fn finalizeEmbeddedMessage(arr: *std.ArrayListUnmanaged(u8), pos: usize) !void { + var items = arr.items; + const length = items.len - pos; + items[pos - 4] = @intCast((length >> 24) & 0xff); + items[pos - 3] = @intCast((length >> 16) & 0xff); + items[pos - 2] = @intCast((length >> 8) & 0xff); + items[pos - 1] = @intCast(length & 0xff); +} diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 8a474c37..de4f1483 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -17,10 +17,12 @@ // along with this program. If not, see . const std = @import("std"); -const Allocator = std.mem.Allocator; + const json = std.json; +const Allocator = std.mem.Allocator; const log = @import("../log.zig"); +const cbor = @import("cbor/cbor.zig"); const App = @import("../app.zig").App; const Env = @import("../browser/env.zig").Env; const asUint = @import("../str/parser.zig").asUint; @@ -458,31 +460,21 @@ pub fn BrowserContext(comptime CDP_T: type) type { defer _ = self.cdp.notification_arena.reset(.{ .retain_with_limit = 1024 * 64 }); } - pub fn callInspector(self: *const Self, msg: []const u8) void { - self.inspector.send(msg); + pub fn callInspector(self: *const Self, arena: Allocator, input: []const u8) !void { + const encoded = try cbor.jsonToCbor(arena, input); + try self.inspector.send(encoded); // force running micro tasks after send input to the inspector. self.cdp.browser.runMicrotasks(); } - pub fn onInspectorResponse(ctx: *anyopaque, _: u32, msg: []const u8) void { - sendInspectorMessage(@alignCast(@ptrCast(ctx)), msg) catch |err| { + pub fn onInspectorResponse(ctx: *anyopaque, _: u32, str: Env.Inspector.StringView) void { + sendInspectorMessage(@alignCast(@ptrCast(ctx)), str) catch |err| { log.err(.cdp, "send inspector response", .{ .err = err }); }; } - pub fn onInspectorEvent(ctx: *anyopaque, msg: []const u8) void { - if (log.enabled(.cdp, .debug)) { - // msg should be {"method":,... - std.debug.assert(std.mem.startsWith(u8, msg, "{\"method\":")); - const method_end = std.mem.indexOfScalar(u8, msg, ',') orelse { - log.err(.cdp, "invalid inspector event", .{ .msg = msg }); - return; - }; - const method = msg[10..method_end]; - log.debug(.cdp, "inspector event", .{ .method = method }); - } - - sendInspectorMessage(@alignCast(@ptrCast(ctx)), msg) catch |err| { + pub fn onInspectorEvent(ctx: *anyopaque, str: Env.Inspector.StringView) void { + sendInspectorMessage(@alignCast(@ptrCast(ctx)), str) catch |err| { log.err(.cdp, "send inspector event", .{ .err = err }); }; } @@ -490,7 +482,7 @@ pub fn BrowserContext(comptime CDP_T: type) type { // 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 // we should be. - fn sendInspectorMessage(self: *Self, msg: []const u8) !void { + fn sendInspectorMessage(self: *Self, str: Env.Inspector.StringView) !void { const session_id = self.session_id orelse { // We no longer have an active session. What should we do // in this case? @@ -501,27 +493,26 @@ pub fn BrowserContext(comptime CDP_T: type) type { var arena = std.heap.ArenaAllocator.init(cdp.allocator); errdefer arena.deinit(); - const field = ",\"sessionId\":\""; - - // + 1 for the closing quote after the session id - // + 10 for the max websocket header - const message_len = msg.len + session_id.len + 1 + field.len + 10; - + const aa = arena.allocator(); var buf: std.ArrayListUnmanaged(u8) = .{}; - buf.ensureTotalCapacity(arena.allocator(), message_len) catch |err| { - log.err(.cdp, "inspector buffer", .{ .err = err }); - return; - }; // reserve 10 bytes for websocket header - buf.appendSliceAssumeCapacity(&.{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); + try buf.appendSlice(aa, &.{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); - // -1 because we dont' want the closing brace '}' - buf.appendSliceAssumeCapacity(msg[0 .. msg.len - 1]); - buf.appendSliceAssumeCapacity(field); - buf.appendSliceAssumeCapacity(session_id); - buf.appendSliceAssumeCapacity("\"}"); - std.debug.assert(buf.items.len == message_len); + try cbor.cborToJson(str.bytes(), buf.writer(aa)); + + std.debug.assert(buf.getLast() == '}'); + + // We need to inject the session_id + // First, we strip out the closing '}' + buf.items.len -= 1; + + // Next we inject the session id field + value + try buf.appendSlice(aa, ",\"sessionId\":\""); + try buf.appendSlice(aa, session_id); + + // Finally, we re-close the object. Smooth. + try buf.appendSlice(aa, "\"}"); try cdp.client.sendJSONRaw(arena, buf); } diff --git a/src/cdp/domains/runtime.zig b/src/cdp/domains/runtime.zig index 707b5912..ce66354f 100644 --- a/src/cdp/domains/runtime.zig +++ b/src/cdp/domains/runtime.zig @@ -44,7 +44,7 @@ fn sendInspector(cmd: anytype, action: anytype) !void { const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; // the result to return is handled directly by the inspector. - bc.callInspector(cmd.input.json); + return bc.callInspector(cmd.arena, cmd.input.json); } fn logInspector(cmd: anytype, action: anytype) !void { diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 800ab03e..f50f8bd8 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -1547,7 +1547,15 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { // If necessary, turn a void context into something we can safely ptrCast const safe_context: *anyopaque = if (ContextT == void) @constCast(@ptrCast(&{})) else ctx; - const channel = v8.InspectorChannel.init(safe_context, InspectorContainer.onInspectorResponse, InspectorContainer.onInspectorEvent, isolate); + const channel = v8.InspectorChannel.init(safe_context, struct { + fn onInspectorResponse(ctx2: *anyopaque, call_id: u32, msg: v8.StringView) void { + InspectorContainer.onInspectorResponse(ctx2, call_id, StringView{ .inner = msg }); + } + }.onInspectorResponse, struct { + fn onInspectorEvent(ctx2: *anyopaque, msg: v8.StringView) void { + InspectorContainer.onInspectorEvent(ctx2, StringView{ .inner = msg }); + } + }.onInspectorEvent, isolate); const client = v8.InspectorClient.init(); @@ -1561,7 +1569,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { self.inner.deinit(); } - pub fn send(self: *const Inspector, msg: []const u8) void { + pub fn send(self: *const Inspector, msg: []const u8) !void { self.session.dispatchProtocolMessage(self.isolate, msg); } @@ -1621,6 +1629,23 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { if (toa.subtype == null or toa.subtype != .node) return error.ObjectIdIsNotANode; return toa.ptr; } + + pub const StringView = struct { + inner: v8.StringView, + + pub fn length(self: StringView) usize { + return self.inner.length(); + } + + pub fn bytes(self: StringView) []const u8 { + return self.inner.bytes()[0..self.length()]; + } + }; + + const NoopInspector = struct { + pub fn onInspectorResponse(_: *anyopaque, _: u32, _: StringView) void {} + pub fn onInspectorEvent(_: *anyopaque, _: StringView) void {} + }; }; pub const RemoteObject = v8.RemoteObject; @@ -3204,11 +3229,6 @@ fn stackForLogs(arena: Allocator, isolate: v8.Isolate) !?[]const u8 { return buf.items; } -const NoopInspector = struct { - pub fn onInspectorResponse(_: *anyopaque, _: u32, _: []const u8) void {} - pub fn onInspectorEvent(_: *anyopaque, _: []const u8) void {} -}; - const ErrorModuleLoader = struct { pub fn fetchModuleSource(_: *anyopaque, _: []const u8) !?[]const u8 { return error.NoModuleLoadConfigured; diff --git a/src/runtime/loop.zig b/src/runtime/loop.zig index a1af200e..cd4f1464 100644 --- a/src/runtime/loop.zig +++ b/src/runtime/loop.zig @@ -127,7 +127,6 @@ pub const Loop = struct { } } - // JS callbacks APIs // ----------------- @@ -255,7 +254,6 @@ pub const Loop = struct { } }.onConnect; - const callback = try self.event_callback_pool.create(); errdefer self.event_callback_pool.destroy(callback); callback.* = .{ .loop = self, .ctx = ctx };