diff --git a/src/browser/cssom/css_style_declaration.zig b/src/browser/cssom/css_style_declaration.zig index 1e980fb2..98a9015d 100644 --- a/src/browser/cssom/css_style_declaration.zig +++ b/src/browser/cssom/css_style_declaration.zig @@ -85,7 +85,7 @@ pub const CSSStyleDeclaration = struct { return self.order.items.len; } - pub fn get_parentRule() ?CSSRule { + pub fn get_parentRule(_: *const CSSStyleDeclaration) ?CSSRule { return null; } diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig index 02381971..f2fd3a18 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -337,20 +337,15 @@ test "Browser.HTML.Window" { // Note however that we in this test do not wait as the request is just send to the browser try runner.testCases(&.{ .{ - \\ let start; + \\ let start = 0; \\ function step(timestamp) { - \\ if (start === undefined) { - \\ start = timestamp; - \\ } - \\ const elapsed = timestamp - start; - \\ if (elapsed < 2000) { - \\ requestAnimationFrame(step); - \\ } + \\ start = timestamp; \\ } , null, }, .{ "requestAnimationFrame(step);", null }, // returned id is checked in the next test + .{ " start > 0", "true" }, }, .{}); // cancelAnimationFrame should be able to cancel a request with the given id diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 3b0005f4..30b05499 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -23,7 +23,6 @@ const json = std.json; const log = @import("../log.zig"); const App = @import("../app.zig").App; const Env = @import("../browser/env.zig").Env; -const asUint = @import("../str/parser.zig").asUint; const Browser = @import("../browser/browser.zig").Browser; const Session = @import("../browser/session.zig").Session; const Page = @import("../browser/page.zig").Page; @@ -182,41 +181,41 @@ pub fn CDPT(comptime TypeProvider: type) type { switch (domain.len) { 3 => switch (@as(u24, @bitCast(domain[0..3].*))) { - asUint("DOM") => return @import("domains/dom.zig").processMessage(command), - asUint("Log") => return @import("domains/log.zig").processMessage(command), - asUint("CSS") => return @import("domains/css.zig").processMessage(command), + asUint(u24, "DOM") => return @import("domains/dom.zig").processMessage(command), + asUint(u24, "Log") => return @import("domains/log.zig").processMessage(command), + asUint(u24, "CSS") => return @import("domains/css.zig").processMessage(command), else => {}, }, 4 => switch (@as(u32, @bitCast(domain[0..4].*))) { - asUint("Page") => return @import("domains/page.zig").processMessage(command), + asUint(u32, "Page") => return @import("domains/page.zig").processMessage(command), else => {}, }, 5 => switch (@as(u40, @bitCast(domain[0..5].*))) { - asUint("Fetch") => return @import("domains/fetch.zig").processMessage(command), - asUint("Input") => return @import("domains/input.zig").processMessage(command), + asUint(u40, "Fetch") => return @import("domains/fetch.zig").processMessage(command), + asUint(u40, "Input") => return @import("domains/input.zig").processMessage(command), else => {}, }, 6 => switch (@as(u48, @bitCast(domain[0..6].*))) { - asUint("Target") => return @import("domains/target.zig").processMessage(command), + asUint(u48, "Target") => return @import("domains/target.zig").processMessage(command), else => {}, }, 7 => switch (@as(u56, @bitCast(domain[0..7].*))) { - asUint("Browser") => return @import("domains/browser.zig").processMessage(command), - asUint("Runtime") => return @import("domains/runtime.zig").processMessage(command), - asUint("Network") => return @import("domains/network.zig").processMessage(command), + asUint(u56, "Browser") => return @import("domains/browser.zig").processMessage(command), + asUint(u56, "Runtime") => return @import("domains/runtime.zig").processMessage(command), + asUint(u56, "Network") => return @import("domains/network.zig").processMessage(command), else => {}, }, 8 => switch (@as(u64, @bitCast(domain[0..8].*))) { - asUint("Security") => return @import("domains/security.zig").processMessage(command), + asUint(u64, "Security") => return @import("domains/security.zig").processMessage(command), else => {}, }, 9 => switch (@as(u72, @bitCast(domain[0..9].*))) { - asUint("Emulation") => return @import("domains/emulation.zig").processMessage(command), - asUint("Inspector") => return @import("domains/inspector.zig").processMessage(command), + asUint(u72, "Emulation") => return @import("domains/emulation.zig").processMessage(command), + asUint(u72, "Inspector") => return @import("domains/inspector.zig").processMessage(command), else => {}, }, 11 => switch (@as(u88, @bitCast(domain[0..11].*))) { - asUint("Performance") => return @import("domains/performance.zig").processMessage(command), + asUint(u88, "Performance") => return @import("domains/performance.zig").processMessage(command), else => {}, }, else => {}, @@ -696,6 +695,10 @@ const InputParams = struct { } }; +fn asUint(comptime T: type, comptime string: []const u8) T { + return @bitCast(string[0..string.len].*); +} + const testing = @import("testing.zig"); test "cdp: invalid json" { var ctx = testing.context(); diff --git a/src/http/client.zig b/src/http/client.zig index c6cf71c0..385e8e0a 100644 --- a/src/http/client.zig +++ b/src/http/client.zig @@ -3170,7 +3170,7 @@ test "HttpClient: async tls no body" { } } -test "HttpClient: async tls with body x" { +test "HttpClient: async tls with body" { defer testing.reset(); for (0..5) |_| { var client = try testClient(); diff --git a/src/log.zig b/src/log.zig index 93df4c48..e00621ee 100644 --- a/src/log.zig +++ b/src/log.zig @@ -146,6 +146,16 @@ fn logTo(comptime scope: Scope, level: Level, comptime msg: []const u8, data: an } fn logLogfmt(comptime scope: Scope, level: Level, comptime msg: []const u8, data: anytype, writer: anytype) !void { + try logLogFmtPrefix(scope, level, msg, writer); + inline for (@typeInfo(@TypeOf(data)).@"struct".fields) |f| { + const key = " " ++ f.name ++ "="; + try writer.writeAll(key); + try writeValue(.logfmt, @field(data, f.name), writer); + } + try writer.writeByte('\n'); +} + +fn logLogFmtPrefix(comptime scope: Scope, level: Level, comptime msg: []const u8, writer: anytype) !void { try writer.writeAll("$time="); try writer.print("{d}", .{timestamp()}); @@ -164,15 +174,20 @@ fn logLogfmt(comptime scope: Scope, level: Level, comptime msg: []const u8, data break :blk prefix ++ "\"" ++ msg ++ "\""; }; try writer.writeAll(full_msg); +} + +fn logPretty(comptime scope: Scope, level: Level, comptime msg: []const u8, data: anytype, writer: anytype) !void { + try logPrettyPrefix(scope, level, msg, writer); inline for (@typeInfo(@TypeOf(data)).@"struct".fields) |f| { - const key = " " ++ f.name ++ "="; + const key = " " ++ f.name ++ " = "; try writer.writeAll(key); - try writeValue(.logfmt, @field(data, f.name), writer); + try writeValue(.pretty, @field(data, f.name), writer); + try writer.writeByte('\n'); } try writer.writeByte('\n'); } -fn logPretty(comptime scope: Scope, level: Level, comptime msg: []const u8, data: anytype, writer: anytype) !void { +fn logPrettyPrefix(comptime scope: Scope, level: Level, comptime msg: []const u8, writer: anytype) !void { if (scope == .console and level == .fatal and comptime std.mem.eql(u8, msg, "lightpanda")) { try writer.writeAll("\x1b[0;104mWARN "); } else { @@ -201,14 +216,6 @@ fn logPretty(comptime scope: Scope, level: Level, comptime msg: []const u8, data try writer.print(" \x1b[0m[+{d}ms]", .{elapsed()}); try writer.writeByte('\n'); } - - inline for (@typeInfo(@TypeOf(data)).@"struct".fields) |f| { - const key = " " ++ f.name ++ " = "; - try writer.writeAll(key); - try writeValue(.pretty, @field(data, f.name), writer); - try writer.writeByte('\n'); - } - try writer.writeByte('\n'); } pub fn writeValue(comptime format: Format, value: anytype, writer: anytype) !void { diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 86619c6a..3b4259a6 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -1876,7 +1876,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { defer caller.deinit(); const named_function = comptime NamedFunction.init(Struct, "get_" ++ name); - caller.getter(Struct, named_function, info) catch |err| { + caller.method(Struct, named_function, info) catch |err| { caller.handleError(Struct, named_function, err, info); }; } @@ -1891,13 +1891,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { const setter_callback = v8.FunctionTemplate.initCallback(isolate, struct { fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { const info = v8.FunctionCallbackInfo.initFromV8(raw_info); + std.debug.assert(info.length() == 1); + var caller = Caller(Self, State).init(info); defer caller.deinit(); - std.debug.assert(info.length() == 1); - const js_value = info.getArg(0); const named_function = comptime NamedFunction.init(Struct, "set_" ++ name); - caller.setter(Struct, named_function, js_value, info) catch |err| { + caller.method(Struct, named_function, info) catch |err| { caller.handleError(Struct, named_function, err, info); }; } @@ -2424,66 +2424,6 @@ fn Caller(comptime E: type, comptime State: type) type { info.getReturnValue().set(try js_context.zigValueToJs(res)); } - fn getter(self: *Self, comptime Struct: type, comptime named_function: NamedFunction, info: v8.FunctionCallbackInfo) !void { - const js_context = self.js_context; - const func = @field(Struct, named_function.name); - const Getter = @TypeOf(func); - if (@typeInfo(Getter).@"fn".return_type == null) { - @compileError(@typeName(Struct) ++ " has a getter without a return type: " ++ @typeName(Getter)); - } - - var args: ParamterTypes(Getter) = undefined; - const arg_fields = @typeInfo(@TypeOf(args)).@"struct".fields; - switch (arg_fields.len) { - 0 => {}, // getters _can_ be parameterless - 1, 2 => { - const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis()); - comptime assertSelfReceiver(Struct, named_function); - @field(args, "0") = zig_instance; - if (comptime arg_fields.len == 2) { - comptime assertIsStateArg(Struct, named_function, 1); - @field(args, "1") = js_context.state; - } - }, - else => @compileError(named_function.full_name + " has too many parmaters: " ++ @typeName(named_function.func)), - } - const res = @call(.auto, func, args); - info.getReturnValue().set(try js_context.zigValueToJs(res)); - } - - fn setter(self: *Self, comptime Struct: type, comptime named_function: NamedFunction, js_value: v8.Value, info: v8.FunctionCallbackInfo) !void { - const js_context = self.js_context; - const func = @field(Struct, named_function.name); - comptime assertSelfReceiver(Struct, named_function); - - const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis()); - - const Setter = @TypeOf(func); - var args: ParamterTypes(Setter) = undefined; - const arg_fields = @typeInfo(@TypeOf(args)).@"struct".fields; - switch (arg_fields.len) { - 0 => unreachable, // assertSelfReceiver make sure of this - 1 => @compileError(named_function.full_name ++ " only has 1 parameter"), - 2, 3 => { - @field(args, "0") = zig_instance; - @field(args, "1") = try js_context.jsValueToZig(named_function, arg_fields[1].type, js_value); - if (comptime arg_fields.len == 3) { - comptime assertIsStateArg(Struct, named_function, 2); - @field(args, "2") = js_context.state; - } - }, - else => @compileError(named_function.full_name ++ " setter with more than 3 parameters, why?"), - } - - if (@typeInfo(Setter).@"fn".return_type) |return_type| { - if (@typeInfo(return_type) == .error_union) { - _ = try @call(.auto, func, args); - return; - } - } - _ = @call(.auto, func, args); - } - fn getIndex(self: *Self, comptime Struct: type, comptime named_function: NamedFunction, idx: u32, info: v8.PropertyCallbackInfo) !u8 { const js_context = self.js_context; const func = @field(Struct, named_function.name); @@ -2596,13 +2536,7 @@ fn Caller(comptime E: type, comptime State: type) type { if (comptime builtin.mode == .Debug and @hasDecl(@TypeOf(info), "length")) { if (log.enabled(.js, .warn)) { - const args_dump = self.serializeFunctionArgs(info) catch "failed to serialize args"; - log.warn(.js, "function call error", .{ - .name = named_function.full_name, - .err = err, - .args = args_dump, - .stack = stackForLogs(self.call_arena, isolate) catch |err1| @errorName(err1), - }); + logFunctionCallError(self.call_arena, self.isolate, self.v8_context, err, named_function.full_name, info); } } @@ -2670,6 +2604,7 @@ fn Caller(comptime E: type, comptime State: type) type { // Does the error we want to return belong to the custom exeception's ErrorSet fn isErrorSetException(comptime Exception: type, err: anytype) bool { const Entry = std.meta.Tuple(&.{ []const u8, void }); + const error_set = @typeInfo(Exception.ErrorSet).error_set.?; const entries = comptime blk: { var kv: [error_set.len]Entry = undefined; @@ -2808,28 +2743,6 @@ fn Caller(comptime E: type, comptime State: type) type { const Const_State = if (ti == .pointer) *const ti.pointer.child else State; return T == State or T == Const_State; } - - fn serializeFunctionArgs(self: *const Self, info: anytype) ![]const u8 { - const isolate = self.isolate; - const v8_context = self.v8_context; - const arena = self.call_arena; - const separator = log.separator(); - const js_parameter_count = info.length(); - - var arr: std.ArrayListUnmanaged(u8) = .{}; - for (0..js_parameter_count) |i| { - const js_value = info.getArg(@intCast(i)); - const value_string = try valueToDetailString(arena, js_value, isolate, v8_context); - const value_type = try jsStringToZig(arena, try js_value.typeOf(isolate), isolate); - try std.fmt.format(arr.writer(arena), "{s}{d}: {s} ({s})", .{ - separator, - i + 1, - value_string, - value_type, - }); - } - return arr.items; - } }; } @@ -3270,6 +3183,37 @@ const NamedFunction = struct { } }; +// This is extracted to speed up compilation. When left inlined in handleError, +// this can add as much as 10 seconds of compilation time. +fn logFunctionCallError(arena: Allocator, isolate: v8.Isolate, context: v8.Context, err: anyerror, function_name: []const u8, info: v8.FunctionCallbackInfo) void { + const args_dump = serializeFunctionArgs(arena, isolate, context, info) catch "failed to serialize args"; + log.warn(.js, "function call error", .{ + .name = function_name, + .err = err, + .args = args_dump, + .stack = stackForLogs(arena, isolate) catch |err1| @errorName(err1), + }); +} + +fn serializeFunctionArgs(arena: Allocator, isolate: v8.Isolate, context: v8.Context, info: v8.FunctionCallbackInfo) ![]const u8 { + const separator = log.separator(); + const js_parameter_count = info.length(); + + var arr: std.ArrayListUnmanaged(u8) = .{}; + for (0..js_parameter_count) |i| { + const js_value = info.getArg(@intCast(i)); + const value_string = try valueToDetailString(arena, js_value, isolate, context); + const value_type = try jsStringToZig(arena, try js_value.typeOf(isolate), isolate); + try std.fmt.format(arr.writer(arena), "{s}{d}: {s} ({s})", .{ + separator, + i + 1, + value_string, + value_type, + }); + } + return arr.items; +} + // This is called from V8. Whenever the v8 inspector has to describe a value // it'll call this function to gets its [optional] subtype - which, from V8's // point of view, is an arbitrary string. diff --git a/src/str/parser.zig b/src/str/parser.zig deleted file mode 100644 index b2429f81..00000000 --- a/src/str/parser.zig +++ /dev/null @@ -1,125 +0,0 @@ -// 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 . - -// some utils to parser strings. -const std = @import("std"); - -pub const Reader = struct { - pos: usize = 0, - data: []const u8, - - pub fn until(self: *Reader, c: u8) []const u8 { - const pos = self.pos; - const data = self.data; - - const index = std.mem.indexOfScalarPos(u8, data, pos, c) orelse data.len; - self.pos = index; - return data[pos..index]; - } - - pub fn tail(self: *Reader) []const u8 { - const pos = self.pos; - const data = self.data; - if (pos > data.len) { - return ""; - } - self.pos = data.len; - return data[pos..]; - } - - pub fn skip(self: *Reader) bool { - const pos = self.pos; - if (pos >= self.data.len) { - return false; - } - self.pos = pos + 1; - return true; - } -}; - -// converts a comptime-known string (i.e. null terminated) to an uint -pub fn asUint(comptime string: anytype) AsUintReturn(string) { - const byteLength = @bitSizeOf(@TypeOf(string.*)) / 8 - 1; - const expectedType = *const [byteLength:0]u8; - if (@TypeOf(string) != expectedType) { - @compileError("expected : " ++ @typeName(expectedType) ++ - ", got: " ++ @typeName(@TypeOf(string))); - } - - return @bitCast(@as(*const [byteLength]u8, string).*); -} - -fn AsUintReturn(comptime string: anytype) type { - return @Type(.{ - .int = .{ - .bits = @bitSizeOf(@TypeOf(string.*)) - 8, // (- 8) to exclude sentinel 0 - .signedness = .unsigned, - }, - }); -} - -const testing = std.testing; -test "parser.Reader: skip" { - var r = Reader{ .data = "foo" }; - try testing.expectEqual(true, r.skip()); - try testing.expectEqual(true, r.skip()); - try testing.expectEqual(true, r.skip()); - try testing.expectEqual(false, r.skip()); - try testing.expectEqual(false, r.skip()); -} - -test "parser.Reader: tail" { - var r = Reader{ .data = "foo" }; - try testing.expectEqualStrings("foo", r.tail()); - try testing.expectEqualStrings("", r.tail()); - try testing.expectEqualStrings("", r.tail()); -} - -test "parser.Reader: until" { - var r = Reader{ .data = "foo.bar.baz" }; - try testing.expectEqualStrings("foo", r.until('.')); - _ = r.skip(); - try testing.expectEqualStrings("bar", r.until('.')); - _ = r.skip(); - try testing.expectEqualStrings("baz", r.until('.')); - - r = Reader{ .data = "foo" }; - try testing.expectEqualStrings("foo", r.until('.')); - try testing.expectEqualStrings("", r.tail()); - - r = Reader{ .data = "" }; - try testing.expectEqualStrings("", r.until('.')); - try testing.expectEqualStrings("", r.tail()); -} - -test "parser: asUint" { - const ASCII_x = @as(u8, @bitCast([1]u8{'x'})); - const ASCII_ab = @as(u16, @bitCast([2]u8{ 'a', 'b' })); - const ASCII_xyz = @as(u24, @bitCast([3]u8{ 'x', 'y', 'z' })); - const ASCII_abcd = @as(u32, @bitCast([4]u8{ 'a', 'b', 'c', 'd' })); - - try testing.expectEqual(ASCII_x, asUint("x")); - try testing.expectEqual(ASCII_ab, asUint("ab")); - try testing.expectEqual(ASCII_xyz, asUint("xyz")); - try testing.expectEqual(ASCII_abcd, asUint("abcd")); - - try testing.expectEqual(u8, @TypeOf(asUint("x"))); - try testing.expectEqual(u16, @TypeOf(asUint("ab"))); - try testing.expectEqual(u24, @TypeOf(asUint("xyz"))); - try testing.expectEqual(u32, @TypeOf(asUint("abcd"))); -}