mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 14:43:28 +00:00
Compare commits
1 Commits
c9dc4ef57a
...
cbor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9ac1fa3bc |
@@ -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" },
|
||||
|
||||
52
src/cdp/cbor/cbor.zig
Normal file
52
src/cdp/cbor/cbor.zig
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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);
|
||||
}
|
||||
252
src/cdp/cbor/cbor_to_json.zig
Normal file
252
src/cdp/cbor/cbor_to_json.zig
Normal file
@@ -0,0 +1,252 @@
|
||||
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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);
|
||||
}
|
||||
173
src/cdp/cbor/json_to_cbor.zig
Normal file
173
src/cdp/cbor/json_to_cbor.zig
Normal file
@@ -0,0 +1,173 @@
|
||||
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -17,10 +17,12 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
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":<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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user