diff --git a/src/browser/URL.zig b/src/browser/URL.zig
index a2062d50..da031949 100644
--- a/src/browser/URL.zig
+++ b/src/browser/URL.zig
@@ -122,6 +122,135 @@ pub fn isCompleteHTTPUrl(url: []const u8) bool {
std.ascii.startsWithIgnoreCase(url, "ftp://");
}
+pub fn getUsername(raw: [:0]const u8) []const u8 {
+ const user_info = getUserInfo(raw) orelse return "";
+ const pos = std.mem.indexOfScalarPos(u8, user_info, 0, ':') orelse return user_info;
+ return user_info[0..pos];
+}
+
+pub fn getPassword(raw: [:0]const u8) []const u8 {
+ const user_info = getUserInfo(raw) orelse return "";
+ const pos = std.mem.indexOfScalarPos(u8, user_info, 0, ':') orelse return "";
+ return user_info[pos + 1 ..];
+}
+
+pub fn getPathname(raw: [:0]const u8) []const u8 {
+ const protocol_end = std.mem.indexOf(u8, raw, "://") orelse 0;
+ const path_start = std.mem.indexOfScalarPos(u8, raw, if (protocol_end > 0) protocol_end + 3 else 0, '/') orelse raw.len;
+
+ const query_or_hash_start = std.mem.indexOfAnyPos(u8, raw, path_start, "?#") orelse raw.len;
+
+ if (path_start >= query_or_hash_start) {
+ if (std.mem.indexOf(u8, raw, "://") != null) return "/";
+ return "";
+ }
+
+ return raw[path_start..query_or_hash_start];
+}
+
+pub fn getProtocol(raw: [:0]const u8) []const u8 {
+ const pos = std.mem.indexOfScalarPos(u8, raw, 0, ':') orelse return "";
+ return raw[0 .. pos + 1];
+}
+
+pub fn getHostname(raw: [:0]const u8) []const u8 {
+ const host = getHost(raw);
+ const pos = std.mem.lastIndexOfScalar(u8, host, ':') orelse return host;
+ return host[0..pos];
+}
+
+pub fn getPort(raw: [:0]const u8) []const u8 {
+ const host = getHost(raw);
+ const pos = std.mem.lastIndexOfScalar(u8, host, ':') orelse return "";
+
+ if (pos + 1 >= host.len) {
+ return "";
+ }
+
+ for (host[pos + 1 ..]) |c| {
+ if (c < '0' or c > '9') {
+ return "";
+ }
+ }
+
+ return host[pos + 1 ..];
+}
+
+pub fn getSearch(raw: [:0]const u8) []const u8 {
+ const pos = std.mem.indexOfScalarPos(u8, raw, 0, '?') orelse return "";
+ const query_part = raw[pos..];
+
+ if (std.mem.indexOfScalarPos(u8, query_part, 0, '#')) |fragment_start| {
+ return query_part[0..fragment_start];
+ }
+
+ return query_part;
+}
+
+pub fn getHash(raw: [:0]const u8) []const u8 {
+ const start = std.mem.indexOfScalarPos(u8, raw, 0, '#') orelse return "";
+ return raw[start..];
+}
+
+pub fn getOrigin(allocator: Allocator, raw: [:0]const u8) !?[]const u8 {
+ const port = getPort(raw);
+ const protocol = getProtocol(raw);
+ const hostname = getHostname(raw);
+
+ const p = std.meta.stringToEnum(KnownProtocol, getProtocol(raw)) orelse return null;
+
+ const include_port = blk: {
+ if (port.len == 0) {
+ break :blk false;
+ }
+ if (p == .@"https:" and std.mem.eql(u8, port, "443")) {
+ break :blk false;
+ }
+ if (p == .@"http:" and std.mem.eql(u8, port, "80")) {
+ break :blk false;
+ }
+ break :blk true;
+ };
+
+ if (include_port) {
+ return try std.fmt.allocPrint(allocator, "{s}//{s}:{s}", .{ protocol, hostname, port });
+ }
+ return try std.fmt.allocPrint(allocator, "{s}//{s}", .{ protocol, hostname });
+}
+
+fn getUserInfo(raw: [:0]const u8) ?[]const u8 {
+ const scheme_end = std.mem.indexOf(u8, raw, "://") orelse return null;
+ const authority_start = scheme_end + 3;
+
+ const pos = std.mem.indexOfScalar(u8, raw[authority_start..], '@') orelse return null;
+ const path_start = std.mem.indexOfScalarPos(u8, raw, authority_start, '/') orelse raw.len;
+
+ const full_pos = authority_start + pos;
+ if (full_pos < path_start) {
+ return raw[authority_start..full_pos];
+ }
+
+ return null;
+}
+
+fn getHost(raw: [:0]const u8) []const u8 {
+ const scheme_end = std.mem.indexOf(u8, raw, "://") orelse return "";
+
+ var authority_start = scheme_end + 3;
+ if (std.mem.indexOf(u8, raw[authority_start..], "@")) |pos| {
+ authority_start += pos + 1;
+ }
+
+ const authority = raw[authority_start..];
+ const path_start = std.mem.indexOfAny(u8, authority, "/?#") orelse return authority;
+ return authority[0..path_start];
+}
+
+const KnownProtocol = enum {
+ @"http:",
+ @"https:",
+};
+
const testing = @import("../testing.zig");
test "URL: isCompleteHTTPUrl" {
try testing.expectEqual(true, isCompleteHTTPUrl("http://example.com/about"));
diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig
index 0e2d5c80..ae2790ea 100644
--- a/src/browser/js/bridge.zig
+++ b/src/browser/js/bridge.zig
@@ -467,4 +467,5 @@ pub const JsApis = flattenTypes(&.{
@import("../webapi/storage/storage.zig"),
@import("../webapi/URL.zig"),
@import("../webapi/Window.zig"),
+ @import("../webapi/MutationObserver.zig"),
});
diff --git a/src/browser/session.zig b/src/browser/session.zig
index 41fd795a..0f90a82a 100644
--- a/src/browser/session.zig
+++ b/src/browser/session.zig
@@ -141,7 +141,7 @@ pub fn wait(self: *Session, wait_ms: u32) WaitResult {
return .done;
};
- if (self.page) |*page| {
+ if (self.page) |page| {
return page.wait(wait_ms);
}
return .no_page;
diff --git a/src/browser/webapi/MutationObserver.zig b/src/browser/webapi/MutationObserver.zig
new file mode 100644
index 00000000..73001ee4
--- /dev/null
+++ b/src/browser/webapi/MutationObserver.zig
@@ -0,0 +1,23 @@
+const js = @import("../js/js.zig");
+
+// @ZIGDOM (haha, bet you wish you hadn't opened this file)
+// puppeteer's startup script creates a MutationObserver, even if it doesn't use
+// it in simple scripts. This not-even-a-skeleton is required for puppeteer/cdp.js
+// to run
+const MutationObserver = @This();
+
+pub fn init() MutationObserver {
+ return .{};
+}
+
+pub const JsApi = struct {
+ pub const bridge = js.Bridge(MutationObserver);
+
+ pub const Meta = struct {
+ pub const name = "MutationObserver";
+ pub const prototype_chain = bridge.prototypeChain();
+ pub var class_index: u16 = 0;
+ };
+
+ pub const constructor = bridge.constructor(MutationObserver.init, .{});
+};
diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig
index e6f03d98..88a827fa 100644
--- a/src/browser/webapi/Node.zig
+++ b/src/browser/webapi/Node.zig
@@ -340,6 +340,9 @@ pub fn setNodeValue(self: *const Node, value: ?[]const u8, page: *Page) !void {
}
pub fn format(self: *Node, writer: *std.Io.Writer) !void {
+ // // If you need extra debugging:
+ // return @import("../dump.zig").deep(self, .{}, writer);
+
return switch (self._type) {
.cdata => |cd| cd.format(writer),
.element => |el| writer.print("{f}", .{el}),
diff --git a/src/browser/webapi/TreeWalker.zig b/src/browser/webapi/TreeWalker.zig
index c2b1f39e..cee99ff1 100644
--- a/src/browser/webapi/TreeWalker.zig
+++ b/src/browser/webapi/TreeWalker.zig
@@ -39,14 +39,22 @@ pub fn TreeWalker(comptime mode: Mode) type {
self._next = children.first();
} else if (node._child_link.next) |n| {
self._next = Node.linkToNode(n);
- } else if (node._parent) |n| {
- if (n == self._root) {
- self._next = null;
- } else {
- self._next = Node.linkToNodeOrNull(n._child_link.next);
- }
} else {
- self._next = null;
+ // No children, no next sibling - walk up until we find a next sibling or hit root
+ var current = node._parent;
+ while (current) |parent| {
+ if (parent == self._root) {
+ self._next = null;
+ break;
+ }
+ if (parent._child_link.next) |next_sibling| {
+ self._next = Node.linkToNode(next_sibling);
+ break;
+ }
+ current = parent._parent;
+ } else {
+ self._next = null;
+ }
}
return node;
}
diff --git a/src/browser/webapi/URL.zig b/src/browser/webapi/URL.zig
index b81c8cb2..d7bf0d7d 100644
--- a/src/browser/webapi/URL.zig
+++ b/src/browser/webapi/URL.zig
@@ -1,6 +1,7 @@
const std = @import("std");
const js = @import("../js/js.zig");
+const U = @import("../URL.zig");
const Page = @import("../Page.zig");
const URLSearchParams = @import("net/URLSearchParams.zig");
@@ -42,106 +43,42 @@ pub fn init(url: [:0]const u8, base_: ?[:0]const u8, page: *Page) !*URL {
}
pub fn getUsername(self: *const URL) []const u8 {
- const user_info = self.getUserInfo() orelse return "";
- const pos = std.mem.indexOfScalarPos(u8, user_info, 0, ':') orelse return user_info;
- return user_info[0..pos];
+ return U.getUsername(self._raw);
}
pub fn getPassword(self: *const URL) []const u8 {
- const user_info = self.getUserInfo() orelse return "";
- const pos = std.mem.indexOfScalarPos(u8, user_info, 0, ':') orelse return "";
- return user_info[pos + 1 ..];
+ return U.getPassword(self._raw);
}
pub fn getPathname(self: *const URL) []const u8 {
- const raw = self._raw;
- const protocol_end = std.mem.indexOf(u8, raw, "://") orelse 0;
- const path_start = std.mem.indexOfScalarPos(u8, raw, if (protocol_end > 0) protocol_end + 3 else 0, '/') orelse raw.len;
-
- const query_or_hash_start = std.mem.indexOfAnyPos(u8, raw, path_start, "?#") orelse raw.len;
-
- if (path_start >= query_or_hash_start) {
- if (std.mem.indexOf(u8, raw, "://") != null) return "/";
- return "";
- }
-
- return raw[path_start..query_or_hash_start];
+ return U.getPathname(self._raw);
}
pub fn getProtocol(self: *const URL) []const u8 {
- const raw = self._raw;
- const pos = std.mem.indexOfScalarPos(u8, raw, 0, ':') orelse return "";
- return raw[0 .. pos + 1];
+ return U.getProtocol(self._raw);
}
pub fn getHostname(self: *const URL) []const u8 {
- const host = self.getHost();
- const pos = std.mem.lastIndexOfScalar(u8, host, ':') orelse return host;
- return host[0..pos];
+ return U.getHostname(self._raw);
}
pub fn getPort(self: *const URL) []const u8 {
- const host = self.getHost();
- const pos = std.mem.lastIndexOfScalar(u8, host, ':') orelse return "";
-
- if (pos + 1 >= host.len) {
- return "";
- }
-
- for (host[pos + 1 ..]) |c| {
- if (c < '0' or c > '9') {
- return "";
- }
- }
-
- return host[pos + 1 ..];
+ return U.getPort(self._raw);
}
pub fn getOrigin(self: *const URL, page: *const Page) ![]const u8 {
- const port = self.getPort();
- const protocol = self.getProtocol();
- const hostname = self.getHostname();
-
- const p = std.meta.stringToEnum(KnownProtocol, self.getProtocol()) orelse {
+ return (try U.getOrigin(page.call_arena, self._raw)) orelse {
// yes, a null string, that's what the spec wants
return "null";
};
-
- const include_port = blk: {
- if (port.len == 0) {
- break :blk false;
- }
- if (p == .@"https:" and std.mem.eql(u8, port, "443")) {
- break :blk false;
- }
- if (p == .@"http:" and std.mem.eql(u8, port, "80")) {
- break :blk false;
- }
- break :blk true;
- };
-
- if (include_port) {
- return std.fmt.allocPrint(page.call_arena, "{s}//{s}:{s}", .{ protocol, hostname, port });
- }
- return std.fmt.allocPrint(page.call_arena, "{s}//{s}", .{ protocol, hostname });
}
pub fn getSearch(self: *const URL) []const u8 {
- const raw = self._raw;
- const pos = std.mem.indexOfScalarPos(u8, raw, 0, '?') orelse return "";
- const query_part = raw[pos..];
-
- if (std.mem.indexOfScalarPos(u8, query_part, 0, '#')) |fragment_start| {
- return query_part[0..fragment_start];
- }
-
- return query_part;
+ return U.getSearch(self._raw);
}
pub fn getHash(self: *const URL) []const u8 {
- const raw = self._raw;
- const start = std.mem.indexOfScalarPos(u8, raw, 0, '#') orelse return "";
- return raw[start..];
+ return U.getHash(self._raw);
}
pub fn getSearchParams(self: *URL, page: *Page) !*URLSearchParams {
diff --git a/src/browser/webapi/storage/storage.zig b/src/browser/webapi/storage/storage.zig
index 13bbc72f..8813c092 100644
--- a/src/browser/webapi/storage/storage.zig
+++ b/src/browser/webapi/storage/storage.zig
@@ -9,6 +9,7 @@ pub fn registerTypes() []const type {
}
pub const Jar = @import("cookie.zig").Jar;
+pub const Cookie =@import("cookie.zig").Cookie;
pub const Shed = struct {
_origins: std.StringHashMapUnmanaged(*Bucket) = .empty,
diff --git a/src/cdp/Node.zig b/src/cdp/Node.zig
index be18206c..c5109312 100644
--- a/src/cdp/Node.zig
+++ b/src/cdp/Node.zig
@@ -16,571 +16,572 @@
// 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 log = @import("../log.zig");
-const parser = @import("../browser/netsurf.zig");
-
-pub const Id = u32;
-
-const Node = @This();
-
-id: Id,
-_node: *parser.Node,
-set_child_nodes_event: bool,
-
-// Whenever we send a node to the client, we register it here for future lookup.
-// We maintain a node -> id and id -> node lookup.
-pub const Registry = struct {
- node_id: u32,
- allocator: Allocator,
- arena: std.heap.ArenaAllocator,
- node_pool: std.heap.MemoryPool(Node),
- lookup_by_id: std.AutoHashMapUnmanaged(Id, *Node),
- lookup_by_node: std.HashMapUnmanaged(*parser.Node, *Node, NodeContext, std.hash_map.default_max_load_percentage),
-
- pub fn init(allocator: Allocator) Registry {
- return .{
- .node_id = 1,
- .lookup_by_id = .{},
- .lookup_by_node = .{},
- .allocator = allocator,
- .arena = std.heap.ArenaAllocator.init(allocator),
- .node_pool = std.heap.MemoryPool(Node).init(allocator),
- };
- }
-
- pub fn deinit(self: *Registry) void {
- const allocator = self.allocator;
- self.lookup_by_id.deinit(allocator);
- self.lookup_by_node.deinit(allocator);
- self.node_pool.deinit();
- self.arena.deinit();
- }
-
- pub fn reset(self: *Registry) void {
- self.lookup_by_id.clearRetainingCapacity();
- self.lookup_by_node.clearRetainingCapacity();
- _ = self.arena.reset(.{ .retain_with_limit = 1024 });
- _ = self.node_pool.reset(.{ .retain_with_limit = 1024 });
- }
-
- pub fn register(self: *Registry, n: *parser.Node) !*Node {
- const node_lookup_gop = try self.lookup_by_node.getOrPut(self.allocator, n);
- if (node_lookup_gop.found_existing) {
- return node_lookup_gop.value_ptr.*;
- }
-
- // on error, we're probably going to abort the entire browser context
- // but, just in case, let's try to keep things tidy.
- errdefer _ = self.lookup_by_node.remove(n);
-
- const node = try self.node_pool.create();
- errdefer self.node_pool.destroy(node);
-
- const id = self.node_id;
- self.node_id = id + 1;
-
- node.* = .{
- ._node = n,
- .id = id,
- .set_child_nodes_event = false,
- };
-
- node_lookup_gop.value_ptr.* = node;
- try self.lookup_by_id.putNoClobber(self.allocator, id, node);
- return node;
- }
-};
-
-const NodeContext = struct {
- pub fn hash(_: NodeContext, n: *parser.Node) u64 {
- return std.hash.Wyhash.hash(0, std.mem.asBytes(&@intFromPtr(n)));
- }
-
- pub fn eql(_: NodeContext, a: *parser.Node, b: *parser.Node) bool {
- return @intFromPtr(a) == @intFromPtr(b);
- }
-};
-
-// Searches are a 3 step process:
-// 1 - Dom.performSearch
-// 2 - Dom.getSearchResults
-// 3 - Dom.discardSearchResults
-//
-// For a given browser context, we can have multiple active searches. I.e.
-// performSearch could be called multiple times without getSearchResults or
-// discardSearchResults being called. We keep these active searches in the
-// browser context's node_search_list, which is a SearchList. Since we don't
-// expect many active searches (mostly just 1), a list is fine to scan through.
-pub const Search = struct {
- name: []const u8,
- node_ids: []const Id,
-
- pub const List = struct {
- registry: *Registry,
- search_id: u16 = 0,
- arena: std.heap.ArenaAllocator,
- searches: std.ArrayListUnmanaged(Search) = .{},
-
- pub fn init(allocator: Allocator, registry: *Registry) List {
- return .{
- .registry = registry,
- .arena = std.heap.ArenaAllocator.init(allocator),
- };
- }
-
- pub fn deinit(self: *List) void {
- self.arena.deinit();
- }
-
- pub fn reset(self: *List) void {
- self.search_id = 0;
- self.searches = .{};
- _ = self.arena.reset(.{ .retain_with_limit = 4096 });
- }
-
- pub fn create(self: *List, nodes: []const *parser.Node) !Search {
- const id = self.search_id;
- defer self.search_id = id +% 1;
-
- const arena = self.arena.allocator();
-
- const name = switch (id) {
- 0 => "0",
- 1 => "1",
- 2 => "2",
- 3 => "3",
- 4 => "4",
- 5 => "5",
- 6 => "6",
- 7 => "7",
- 8 => "8",
- 9 => "9",
- else => try std.fmt.allocPrint(arena, "{d}", .{id}),
- };
-
- var registry = self.registry;
- const node_ids = try arena.alloc(Id, nodes.len);
- for (nodes, node_ids) |node, *node_id| {
- node_id.* = (try registry.register(node)).id;
- }
-
- const search = Search{
- .name = name,
- .node_ids = node_ids,
- };
- try self.searches.append(arena, search);
- return search;
- }
-
- pub fn remove(self: *List, name: []const u8) void {
- for (self.searches.items, 0..) |search, i| {
- if (std.mem.eql(u8, name, search.name)) {
- _ = self.searches.swapRemove(i);
- return;
- }
- }
- }
-
- pub fn get(self: *const List, name: []const u8) ?Search {
- for (self.searches.items) |search| {
- if (std.mem.eql(u8, name, search.name)) {
- return search;
- }
- }
- return null;
- }
- };
-};
-
-// Need a custom writer, because we can't just serialize the node as-is.
-// Sometimes we want to serializ the node without chidren, sometimes with just
-// its direct children, and sometimes the entire tree.
-// (For now, we only support direct children)
-
-pub const Writer = struct {
- depth: i32,
- exclude_root: bool,
- root: *const Node,
- registry: *Registry,
-
- pub const Opts = struct {
- depth: i32 = 0,
- exclude_root: bool = false,
- };
-
- pub fn jsonStringify(self: *const Writer, w: anytype) error{WriteFailed}!void {
- if (self.exclude_root) {
- _ = self.writeChildren(self.root, 1, w) catch |err| {
- log.err(.cdp, "node writeChildren", .{ .err = err });
- return error.WriteFailed;
- };
- } else {
- self.toJSON(self.root, 0, w) catch |err| {
- // The only error our jsonStringify method can return is
- // @TypeOf(w).Error. In other words, our code can't return its own
- // error, we can only return a writer error. Kinda sucks.
- log.err(.cdp, "node toJSON stringify", .{ .err = err });
- return error.WriteFailed;
- };
- }
- }
-
- fn toJSON(self: *const Writer, node: *const Node, depth: usize, w: anytype) !void {
- try w.beginObject();
- try self.writeCommon(node, false, w);
-
- try w.objectField("children");
- const child_count = try self.writeChildren(node, depth, w);
- try w.objectField("childNodeCount");
- try w.write(child_count);
-
- try w.endObject();
- }
-
- fn writeChildren(self: *const Writer, node: *const Node, depth: usize, w: anytype) anyerror!usize {
- var registry = self.registry;
- const child_nodes = try parser.nodeGetChildNodes(node._node);
- const child_count = parser.nodeListLength(child_nodes);
- const full_child = self.depth < 0 or self.depth < depth;
-
- var i: usize = 0;
- try w.beginArray();
- for (0..child_count) |_| {
- const child = (parser.nodeListItem(child_nodes, @intCast(i))) orelse break;
- const child_node = try registry.register(child);
- if (full_child) {
- try self.toJSON(child_node, depth + 1, w);
- } else {
- try w.beginObject();
- try self.writeCommon(child_node, true, w);
- try w.endObject();
- }
-
- i += 1;
- }
- try w.endArray();
-
- return i;
- }
-
- fn writeCommon(self: *const Writer, node: *const Node, include_child_count: bool, w: anytype) !void {
- try w.objectField("nodeId");
- try w.write(node.id);
-
- try w.objectField("backendNodeId");
- try w.write(node.id);
-
- const n = node._node;
-
- if (parser.nodeParentNode(n)) |p| {
- const parent_node = try self.registry.register(p);
- try w.objectField("parentId");
- try w.write(parent_node.id);
- }
-
- const _map = try parser.nodeGetAttributes(n);
- if (_map) |map| {
- const attr_count = try parser.namedNodeMapGetLength(map);
- try w.objectField("attributes");
- try w.beginArray();
- for (0..attr_count) |i| {
- const attr = try parser.namedNodeMapItem(map, @intCast(i)) orelse continue;
- try w.write(try parser.attributeGetName(attr));
- try w.write(try parser.attributeGetValue(attr) orelse continue);
- }
- try w.endArray();
- }
-
- try w.objectField("nodeType");
- try w.write(@intFromEnum(parser.nodeType(n)));
-
- try w.objectField("nodeName");
- try w.write(try parser.nodeName(n));
-
- try w.objectField("localName");
- try w.write(try parser.nodeLocalName(n));
-
- try w.objectField("nodeValue");
- try w.write((parser.nodeValue(n)) orelse "");
-
- if (include_child_count) {
- try w.objectField("childNodeCount");
- const child_nodes = try parser.nodeGetChildNodes(n);
- try w.write(parser.nodeListLength(child_nodes));
- }
-
- try w.objectField("documentURL");
- try w.write(null);
-
- try w.objectField("baseURL");
- try w.write(null);
-
- try w.objectField("xmlVersion");
- try w.write("");
-
- try w.objectField("compatibilityMode");
- try w.write("NoQuirksMode");
-
- try w.objectField("isScrollable");
- try w.write(false);
- }
-};
-
-const testing = @import("testing.zig");
-test "cdp Node: Registry register" {
- parser.init();
- defer parser.deinit();
-
- var registry = Registry.init(testing.allocator);
- defer registry.deinit();
-
- try testing.expectEqual(0, registry.lookup_by_id.count());
- try testing.expectEqual(0, registry.lookup_by_node.count());
-
- var doc = try testing.Document.init("link1
");
- defer doc.deinit();
-
- {
- const n = (try doc.querySelector("#a1")).?;
- const node = try registry.register(n);
- const n1b = registry.lookup_by_id.get(1).?;
- const n1c = registry.lookup_by_node.get(node._node).?;
- try testing.expectEqual(node, n1b);
- try testing.expectEqual(node, n1c);
-
- try testing.expectEqual(1, node.id);
- try testing.expectEqual(n, node._node);
- }
-
- {
- const n = (try doc.querySelector("p")).?;
- const node = try registry.register(n);
- const n1b = registry.lookup_by_id.get(2).?;
- const n1c = registry.lookup_by_node.get(node._node).?;
- try testing.expectEqual(node, n1b);
- try testing.expectEqual(node, n1c);
-
- try testing.expectEqual(2, node.id);
- try testing.expectEqual(n, node._node);
- }
-}
-
-test "cdp Node: search list" {
- parser.init();
- defer parser.deinit();
-
- var registry = Registry.init(testing.allocator);
- defer registry.deinit();
-
- var search_list = Search.List.init(testing.allocator, ®istry);
- defer search_list.deinit();
-
- {
- // empty search list, noops
- search_list.remove("0");
- try testing.expectEqual(null, search_list.get("0"));
- }
-
- {
- // empty nodes
- const s1 = try search_list.create(&.{});
- try testing.expectEqual("0", s1.name);
- try testing.expectEqual(0, s1.node_ids.len);
-
- const s2 = search_list.get("0").?;
- try testing.expectEqual("0", s2.name);
- try testing.expectEqual(0, s2.node_ids.len);
-
- search_list.remove("0");
- try testing.expectEqual(null, search_list.get("0"));
- }
-
- {
- var doc = try testing.Document.init("");
- defer doc.deinit();
-
- const s1 = try search_list.create(try doc.querySelectorAll("a"));
- try testing.expectEqual("1", s1.name);
- try testing.expectEqualSlices(u32, &.{ 1, 2 }, s1.node_ids);
-
- try testing.expectEqual(2, registry.lookup_by_id.count());
- try testing.expectEqual(2, registry.lookup_by_node.count());
-
- const s2 = try search_list.create(try doc.querySelectorAll("#a1"));
- try testing.expectEqual("2", s2.name);
- try testing.expectEqualSlices(u32, &.{1}, s2.node_ids);
-
- const s3 = try search_list.create(try doc.querySelectorAll("#a2"));
- try testing.expectEqual("3", s3.name);
- try testing.expectEqualSlices(u32, &.{2}, s3.node_ids);
-
- try testing.expectEqual(2, registry.lookup_by_id.count());
- try testing.expectEqual(2, registry.lookup_by_node.count());
- }
-}
-
-test "cdp Node: Writer" {
- parser.init();
- defer parser.deinit();
-
- var registry = Registry.init(testing.allocator);
- defer registry.deinit();
-
- var doc = try testing.Document.init("");
- defer doc.deinit();
-
- {
- const node = try registry.register(doc.asNode());
- const json = try std.json.Stringify.valueAlloc(testing.allocator, Writer{
- .root = node,
- .depth = 0,
- .exclude_root = false,
- .registry = ®istry,
- }, .{});
- defer testing.allocator.free(json);
-
- try testing.expectJson(.{
- .nodeId = 1,
- .backendNodeId = 1,
- .nodeType = 9,
- .nodeName = "#document",
- .localName = "",
- .nodeValue = "",
- .documentURL = null,
- .baseURL = null,
- .xmlVersion = "",
- .isScrollable = false,
- .compatibilityMode = "NoQuirksMode",
- .childNodeCount = 1,
- .children = &.{.{
- .nodeId = 2,
- .backendNodeId = 2,
- .nodeType = 1,
- .nodeName = "HTML",
- .localName = "html",
- .nodeValue = "",
- .childNodeCount = 2,
- .documentURL = null,
- .baseURL = null,
- .xmlVersion = "",
- .compatibilityMode = "NoQuirksMode",
- .isScrollable = false,
- }},
- }, json);
- }
-
- {
- const node = registry.lookup_by_id.get(2).?;
- const json = try std.json.Stringify.valueAlloc(testing.allocator, Writer{
- .root = node,
- .depth = 1,
- .exclude_root = false,
- .registry = ®istry,
- }, .{});
- defer testing.allocator.free(json);
-
- try testing.expectJson(.{
- .nodeId = 2,
- .backendNodeId = 2,
- .nodeType = 1,
- .nodeName = "HTML",
- .localName = "html",
- .nodeValue = "",
- .childNodeCount = 2,
- .documentURL = null,
- .baseURL = null,
- .xmlVersion = "",
- .compatibilityMode = "NoQuirksMode",
- .isScrollable = false,
- .children = &.{ .{
- .nodeId = 3,
- .backendNodeId = 3,
- .nodeType = 1,
- .nodeName = "HEAD",
- .localName = "head",
- .nodeValue = "",
- .childNodeCount = 0,
- .documentURL = null,
- .baseURL = null,
- .xmlVersion = "",
- .compatibilityMode = "NoQuirksMode",
- .isScrollable = false,
- .parentId = 2,
- }, .{
- .nodeId = 4,
- .backendNodeId = 4,
- .nodeType = 1,
- .nodeName = "BODY",
- .localName = "body",
- .nodeValue = "",
- .childNodeCount = 2,
- .documentURL = null,
- .baseURL = null,
- .xmlVersion = "",
- .compatibilityMode = "NoQuirksMode",
- .isScrollable = false,
- .parentId = 2,
- } },
- }, json);
- }
-
- {
- const node = registry.lookup_by_id.get(2).?;
- const json = try std.json.Stringify.valueAlloc(testing.allocator, Writer{
- .root = node,
- .depth = -1,
- .exclude_root = true,
- .registry = ®istry,
- }, .{});
- defer testing.allocator.free(json);
-
- try testing.expectJson(&.{ .{
- .nodeId = 3,
- .backendNodeId = 3,
- .nodeType = 1,
- .nodeName = "HEAD",
- .localName = "head",
- .nodeValue = "",
- .childNodeCount = 0,
- .documentURL = null,
- .baseURL = null,
- .xmlVersion = "",
- .compatibilityMode = "NoQuirksMode",
- .isScrollable = false,
- .parentId = 2,
- }, .{
- .nodeId = 4,
- .backendNodeId = 4,
- .nodeType = 1,
- .nodeName = "BODY",
- .localName = "body",
- .nodeValue = "",
- .childNodeCount = 2,
- .documentURL = null,
- .baseURL = null,
- .xmlVersion = "",
- .compatibilityMode = "NoQuirksMode",
- .isScrollable = false,
- .children = &.{ .{
- .nodeId = 5,
- .localName = "a",
- .childNodeCount = 0,
- .parentId = 4,
- }, .{
- .nodeId = 6,
- .localName = "div",
- .childNodeCount = 1,
- .parentId = 4,
- .children = &.{.{
- .nodeId = 7,
- .localName = "a",
- .childNodeCount = 0,
- .parentId = 6,
- }},
- } },
- } }, json);
- }
-}
+// @ZIGDOM
+// const std = @import("std");
+// const Allocator = std.mem.Allocator;
+
+// const log = @import("../log.zig");
+// const parser = @import("../browser/netsurf.zig");
+
+// pub const Id = u32;
+
+// const Node = @This();
+
+// id: Id,
+// _node: *parser.Node,
+// set_child_nodes_event: bool,
+
+// // Whenever we send a node to the client, we register it here for future lookup.
+// // We maintain a node -> id and id -> node lookup.
+// pub const Registry = struct {
+// node_id: u32,
+// allocator: Allocator,
+// arena: std.heap.ArenaAllocator,
+// node_pool: std.heap.MemoryPool(Node),
+// lookup_by_id: std.AutoHashMapUnmanaged(Id, *Node),
+// lookup_by_node: std.HashMapUnmanaged(*parser.Node, *Node, NodeContext, std.hash_map.default_max_load_percentage),
+
+// pub fn init(allocator: Allocator) Registry {
+// return .{
+// .node_id = 1,
+// .lookup_by_id = .{},
+// .lookup_by_node = .{},
+// .allocator = allocator,
+// .arena = std.heap.ArenaAllocator.init(allocator),
+// .node_pool = std.heap.MemoryPool(Node).init(allocator),
+// };
+// }
+
+// pub fn deinit(self: *Registry) void {
+// const allocator = self.allocator;
+// self.lookup_by_id.deinit(allocator);
+// self.lookup_by_node.deinit(allocator);
+// self.node_pool.deinit();
+// self.arena.deinit();
+// }
+
+// pub fn reset(self: *Registry) void {
+// self.lookup_by_id.clearRetainingCapacity();
+// self.lookup_by_node.clearRetainingCapacity();
+// _ = self.arena.reset(.{ .retain_with_limit = 1024 });
+// _ = self.node_pool.reset(.{ .retain_with_limit = 1024 });
+// }
+
+// pub fn register(self: *Registry, n: *parser.Node) !*Node {
+// const node_lookup_gop = try self.lookup_by_node.getOrPut(self.allocator, n);
+// if (node_lookup_gop.found_existing) {
+// return node_lookup_gop.value_ptr.*;
+// }
+
+// // on error, we're probably going to abort the entire browser context
+// // but, just in case, let's try to keep things tidy.
+// errdefer _ = self.lookup_by_node.remove(n);
+
+// const node = try self.node_pool.create();
+// errdefer self.node_pool.destroy(node);
+
+// const id = self.node_id;
+// self.node_id = id + 1;
+
+// node.* = .{
+// ._node = n,
+// .id = id,
+// .set_child_nodes_event = false,
+// };
+
+// node_lookup_gop.value_ptr.* = node;
+// try self.lookup_by_id.putNoClobber(self.allocator, id, node);
+// return node;
+// }
+// };
+
+// const NodeContext = struct {
+// pub fn hash(_: NodeContext, n: *parser.Node) u64 {
+// return std.hash.Wyhash.hash(0, std.mem.asBytes(&@intFromPtr(n)));
+// }
+
+// pub fn eql(_: NodeContext, a: *parser.Node, b: *parser.Node) bool {
+// return @intFromPtr(a) == @intFromPtr(b);
+// }
+// };
+
+// // Searches are a 3 step process:
+// // 1 - Dom.performSearch
+// // 2 - Dom.getSearchResults
+// // 3 - Dom.discardSearchResults
+// //
+// // For a given browser context, we can have multiple active searches. I.e.
+// // performSearch could be called multiple times without getSearchResults or
+// // discardSearchResults being called. We keep these active searches in the
+// // browser context's node_search_list, which is a SearchList. Since we don't
+// // expect many active searches (mostly just 1), a list is fine to scan through.
+// pub const Search = struct {
+// name: []const u8,
+// node_ids: []const Id,
+
+// pub const List = struct {
+// registry: *Registry,
+// search_id: u16 = 0,
+// arena: std.heap.ArenaAllocator,
+// searches: std.ArrayListUnmanaged(Search) = .{},
+
+// pub fn init(allocator: Allocator, registry: *Registry) List {
+// return .{
+// .registry = registry,
+// .arena = std.heap.ArenaAllocator.init(allocator),
+// };
+// }
+
+// pub fn deinit(self: *List) void {
+// self.arena.deinit();
+// }
+
+// pub fn reset(self: *List) void {
+// self.search_id = 0;
+// self.searches = .{};
+// _ = self.arena.reset(.{ .retain_with_limit = 4096 });
+// }
+
+// pub fn create(self: *List, nodes: []const *parser.Node) !Search {
+// const id = self.search_id;
+// defer self.search_id = id +% 1;
+
+// const arena = self.arena.allocator();
+
+// const name = switch (id) {
+// 0 => "0",
+// 1 => "1",
+// 2 => "2",
+// 3 => "3",
+// 4 => "4",
+// 5 => "5",
+// 6 => "6",
+// 7 => "7",
+// 8 => "8",
+// 9 => "9",
+// else => try std.fmt.allocPrint(arena, "{d}", .{id}),
+// };
+
+// var registry = self.registry;
+// const node_ids = try arena.alloc(Id, nodes.len);
+// for (nodes, node_ids) |node, *node_id| {
+// node_id.* = (try registry.register(node)).id;
+// }
+
+// const search = Search{
+// .name = name,
+// .node_ids = node_ids,
+// };
+// try self.searches.append(arena, search);
+// return search;
+// }
+
+// pub fn remove(self: *List, name: []const u8) void {
+// for (self.searches.items, 0..) |search, i| {
+// if (std.mem.eql(u8, name, search.name)) {
+// _ = self.searches.swapRemove(i);
+// return;
+// }
+// }
+// }
+
+// pub fn get(self: *const List, name: []const u8) ?Search {
+// for (self.searches.items) |search| {
+// if (std.mem.eql(u8, name, search.name)) {
+// return search;
+// }
+// }
+// return null;
+// }
+// };
+// };
+
+// // Need a custom writer, because we can't just serialize the node as-is.
+// // Sometimes we want to serializ the node without chidren, sometimes with just
+// // its direct children, and sometimes the entire tree.
+// // (For now, we only support direct children)
+
+// pub const Writer = struct {
+// depth: i32,
+// exclude_root: bool,
+// root: *const Node,
+// registry: *Registry,
+
+// pub const Opts = struct {
+// depth: i32 = 0,
+// exclude_root: bool = false,
+// };
+
+// pub fn jsonStringify(self: *const Writer, w: anytype) error{WriteFailed}!void {
+// if (self.exclude_root) {
+// _ = self.writeChildren(self.root, 1, w) catch |err| {
+// log.err(.cdp, "node writeChildren", .{ .err = err });
+// return error.WriteFailed;
+// };
+// } else {
+// self.toJSON(self.root, 0, w) catch |err| {
+// // The only error our jsonStringify method can return is
+// // @TypeOf(w).Error. In other words, our code can't return its own
+// // error, we can only return a writer error. Kinda sucks.
+// log.err(.cdp, "node toJSON stringify", .{ .err = err });
+// return error.WriteFailed;
+// };
+// }
+// }
+
+// fn toJSON(self: *const Writer, node: *const Node, depth: usize, w: anytype) !void {
+// try w.beginObject();
+// try self.writeCommon(node, false, w);
+
+// try w.objectField("children");
+// const child_count = try self.writeChildren(node, depth, w);
+// try w.objectField("childNodeCount");
+// try w.write(child_count);
+
+// try w.endObject();
+// }
+
+// fn writeChildren(self: *const Writer, node: *const Node, depth: usize, w: anytype) anyerror!usize {
+// var registry = self.registry;
+// const child_nodes = try parser.nodeGetChildNodes(node._node);
+// const child_count = parser.nodeListLength(child_nodes);
+// const full_child = self.depth < 0 or self.depth < depth;
+
+// var i: usize = 0;
+// try w.beginArray();
+// for (0..child_count) |_| {
+// const child = (parser.nodeListItem(child_nodes, @intCast(i))) orelse break;
+// const child_node = try registry.register(child);
+// if (full_child) {
+// try self.toJSON(child_node, depth + 1, w);
+// } else {
+// try w.beginObject();
+// try self.writeCommon(child_node, true, w);
+// try w.endObject();
+// }
+
+// i += 1;
+// }
+// try w.endArray();
+
+// return i;
+// }
+
+// fn writeCommon(self: *const Writer, node: *const Node, include_child_count: bool, w: anytype) !void {
+// try w.objectField("nodeId");
+// try w.write(node.id);
+
+// try w.objectField("backendNodeId");
+// try w.write(node.id);
+
+// const n = node._node;
+
+// if (parser.nodeParentNode(n)) |p| {
+// const parent_node = try self.registry.register(p);
+// try w.objectField("parentId");
+// try w.write(parent_node.id);
+// }
+
+// const _map = try parser.nodeGetAttributes(n);
+// if (_map) |map| {
+// const attr_count = try parser.namedNodeMapGetLength(map);
+// try w.objectField("attributes");
+// try w.beginArray();
+// for (0..attr_count) |i| {
+// const attr = try parser.namedNodeMapItem(map, @intCast(i)) orelse continue;
+// try w.write(try parser.attributeGetName(attr));
+// try w.write(try parser.attributeGetValue(attr) orelse continue);
+// }
+// try w.endArray();
+// }
+
+// try w.objectField("nodeType");
+// try w.write(@intFromEnum(parser.nodeType(n)));
+
+// try w.objectField("nodeName");
+// try w.write(try parser.nodeName(n));
+
+// try w.objectField("localName");
+// try w.write(try parser.nodeLocalName(n));
+
+// try w.objectField("nodeValue");
+// try w.write((parser.nodeValue(n)) orelse "");
+
+// if (include_child_count) {
+// try w.objectField("childNodeCount");
+// const child_nodes = try parser.nodeGetChildNodes(n);
+// try w.write(parser.nodeListLength(child_nodes));
+// }
+
+// try w.objectField("documentURL");
+// try w.write(null);
+
+// try w.objectField("baseURL");
+// try w.write(null);
+
+// try w.objectField("xmlVersion");
+// try w.write("");
+
+// try w.objectField("compatibilityMode");
+// try w.write("NoQuirksMode");
+
+// try w.objectField("isScrollable");
+// try w.write(false);
+// }
+// };
+
+// const testing = @import("testing.zig");
+// test "cdp Node: Registry register" {
+// parser.init();
+// defer parser.deinit();
+
+// var registry = Registry.init(testing.allocator);
+// defer registry.deinit();
+
+// try testing.expectEqual(0, registry.lookup_by_id.count());
+// try testing.expectEqual(0, registry.lookup_by_node.count());
+
+// var doc = try testing.Document.init("link1");
+// defer doc.deinit();
+
+// {
+// const n = (try doc.querySelector("#a1")).?;
+// const node = try registry.register(n);
+// const n1b = registry.lookup_by_id.get(1).?;
+// const n1c = registry.lookup_by_node.get(node._node).?;
+// try testing.expectEqual(node, n1b);
+// try testing.expectEqual(node, n1c);
+
+// try testing.expectEqual(1, node.id);
+// try testing.expectEqual(n, node._node);
+// }
+
+// {
+// const n = (try doc.querySelector("p")).?;
+// const node = try registry.register(n);
+// const n1b = registry.lookup_by_id.get(2).?;
+// const n1c = registry.lookup_by_node.get(node._node).?;
+// try testing.expectEqual(node, n1b);
+// try testing.expectEqual(node, n1c);
+
+// try testing.expectEqual(2, node.id);
+// try testing.expectEqual(n, node._node);
+// }
+// }
+
+// test "cdp Node: search list" {
+// parser.init();
+// defer parser.deinit();
+
+// var registry = Registry.init(testing.allocator);
+// defer registry.deinit();
+
+// var search_list = Search.List.init(testing.allocator, ®istry);
+// defer search_list.deinit();
+
+// {
+// // empty search list, noops
+// search_list.remove("0");
+// try testing.expectEqual(null, search_list.get("0"));
+// }
+
+// {
+// // empty nodes
+// const s1 = try search_list.create(&.{});
+// try testing.expectEqual("0", s1.name);
+// try testing.expectEqual(0, s1.node_ids.len);
+
+// const s2 = search_list.get("0").?;
+// try testing.expectEqual("0", s2.name);
+// try testing.expectEqual(0, s2.node_ids.len);
+
+// search_list.remove("0");
+// try testing.expectEqual(null, search_list.get("0"));
+// }
+
+// {
+// var doc = try testing.Document.init("");
+// defer doc.deinit();
+
+// const s1 = try search_list.create(try doc.querySelectorAll("a"));
+// try testing.expectEqual("1", s1.name);
+// try testing.expectEqualSlices(u32, &.{ 1, 2 }, s1.node_ids);
+
+// try testing.expectEqual(2, registry.lookup_by_id.count());
+// try testing.expectEqual(2, registry.lookup_by_node.count());
+
+// const s2 = try search_list.create(try doc.querySelectorAll("#a1"));
+// try testing.expectEqual("2", s2.name);
+// try testing.expectEqualSlices(u32, &.{1}, s2.node_ids);
+
+// const s3 = try search_list.create(try doc.querySelectorAll("#a2"));
+// try testing.expectEqual("3", s3.name);
+// try testing.expectEqualSlices(u32, &.{2}, s3.node_ids);
+
+// try testing.expectEqual(2, registry.lookup_by_id.count());
+// try testing.expectEqual(2, registry.lookup_by_node.count());
+// }
+// }
+
+// test "cdp Node: Writer" {
+// parser.init();
+// defer parser.deinit();
+
+// var registry = Registry.init(testing.allocator);
+// defer registry.deinit();
+
+// var doc = try testing.Document.init("");
+// defer doc.deinit();
+
+// {
+// const node = try registry.register(doc.asNode());
+// const json = try std.json.Stringify.valueAlloc(testing.allocator, Writer{
+// .root = node,
+// .depth = 0,
+// .exclude_root = false,
+// .registry = ®istry,
+// }, .{});
+// defer testing.allocator.free(json);
+
+// try testing.expectJson(.{
+// .nodeId = 1,
+// .backendNodeId = 1,
+// .nodeType = 9,
+// .nodeName = "#document",
+// .localName = "",
+// .nodeValue = "",
+// .documentURL = null,
+// .baseURL = null,
+// .xmlVersion = "",
+// .isScrollable = false,
+// .compatibilityMode = "NoQuirksMode",
+// .childNodeCount = 1,
+// .children = &.{.{
+// .nodeId = 2,
+// .backendNodeId = 2,
+// .nodeType = 1,
+// .nodeName = "HTML",
+// .localName = "html",
+// .nodeValue = "",
+// .childNodeCount = 2,
+// .documentURL = null,
+// .baseURL = null,
+// .xmlVersion = "",
+// .compatibilityMode = "NoQuirksMode",
+// .isScrollable = false,
+// }},
+// }, json);
+// }
+
+// {
+// const node = registry.lookup_by_id.get(2).?;
+// const json = try std.json.Stringify.valueAlloc(testing.allocator, Writer{
+// .root = node,
+// .depth = 1,
+// .exclude_root = false,
+// .registry = ®istry,
+// }, .{});
+// defer testing.allocator.free(json);
+
+// try testing.expectJson(.{
+// .nodeId = 2,
+// .backendNodeId = 2,
+// .nodeType = 1,
+// .nodeName = "HTML",
+// .localName = "html",
+// .nodeValue = "",
+// .childNodeCount = 2,
+// .documentURL = null,
+// .baseURL = null,
+// .xmlVersion = "",
+// .compatibilityMode = "NoQuirksMode",
+// .isScrollable = false,
+// .children = &.{ .{
+// .nodeId = 3,
+// .backendNodeId = 3,
+// .nodeType = 1,
+// .nodeName = "HEAD",
+// .localName = "head",
+// .nodeValue = "",
+// .childNodeCount = 0,
+// .documentURL = null,
+// .baseURL = null,
+// .xmlVersion = "",
+// .compatibilityMode = "NoQuirksMode",
+// .isScrollable = false,
+// .parentId = 2,
+// }, .{
+// .nodeId = 4,
+// .backendNodeId = 4,
+// .nodeType = 1,
+// .nodeName = "BODY",
+// .localName = "body",
+// .nodeValue = "",
+// .childNodeCount = 2,
+// .documentURL = null,
+// .baseURL = null,
+// .xmlVersion = "",
+// .compatibilityMode = "NoQuirksMode",
+// .isScrollable = false,
+// .parentId = 2,
+// } },
+// }, json);
+// }
+
+// {
+// const node = registry.lookup_by_id.get(2).?;
+// const json = try std.json.Stringify.valueAlloc(testing.allocator, Writer{
+// .root = node,
+// .depth = -1,
+// .exclude_root = true,
+// .registry = ®istry,
+// }, .{});
+// defer testing.allocator.free(json);
+
+// try testing.expectJson(&.{ .{
+// .nodeId = 3,
+// .backendNodeId = 3,
+// .nodeType = 1,
+// .nodeName = "HEAD",
+// .localName = "head",
+// .nodeValue = "",
+// .childNodeCount = 0,
+// .documentURL = null,
+// .baseURL = null,
+// .xmlVersion = "",
+// .compatibilityMode = "NoQuirksMode",
+// .isScrollable = false,
+// .parentId = 2,
+// }, .{
+// .nodeId = 4,
+// .backendNodeId = 4,
+// .nodeType = 1,
+// .nodeName = "BODY",
+// .localName = "body",
+// .nodeValue = "",
+// .childNodeCount = 2,
+// .documentURL = null,
+// .baseURL = null,
+// .xmlVersion = "",
+// .compatibilityMode = "NoQuirksMode",
+// .isScrollable = false,
+// .children = &.{ .{
+// .nodeId = 5,
+// .localName = "a",
+// .childNodeCount = 0,
+// .parentId = 4,
+// }, .{
+// .nodeId = 6,
+// .localName = "div",
+// .childNodeCount = 1,
+// .parentId = 4,
+// .children = &.{.{
+// .nodeId = 7,
+// .localName = "a",
+// .childNodeCount = 0,
+// .parentId = 6,
+// }},
+// } },
+// } }, json);
+// }
+// }
diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig
index 7b6590e8..73c5e514 100644
--- a/src/cdp/cdp.zig
+++ b/src/cdp/cdp.zig
@@ -24,12 +24,12 @@ const log = @import("../log.zig");
const js = @import("../browser/js/js.zig");
const polyfill = @import("../browser/polyfill/polyfill.zig");
-const App = @import("../app.zig").App;
-const Browser = @import("../browser/browser.zig").Browser;
-const Session = @import("../browser/session.zig").Session;
-const Page = @import("../browser/page.zig").Page;
+const App = @import("../App.zig");
+const Browser = @import("../browser/Browser.zig");
+const Session = @import("../browser/Session.zig");
+const Page = @import("../browser/Page.zig");
const Incrementing = @import("../id.zig").Incrementing;
-const Notification = @import("../notification.zig").Notification;
+const Notification = @import("../Notification.zig");
const LogInterceptor = @import("domains/log.zig").LogInterceptor;
const InterceptState = @import("domains/fetch.zig").InterceptState;
@@ -37,7 +37,7 @@ pub const URL_BASE = "chrome://newtab/";
pub const LOADER_ID = "LOADERID24DD2FD56CF1EF33C965C79C";
pub const CDP = CDPT(struct {
- const Client = *@import("../server.zig").Client;
+ const Client = *@import("../Server.zig").Client;
});
const SessionIdGen = Incrementing(u32, "SID");
@@ -117,7 +117,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
// timeouts (or http events) which are ready to be processed.
pub fn hasPage() bool {}
- pub fn pageWait(self: *Self, ms: i32) Session.WaitResult {
+ pub fn pageWait(self: *Self, ms: u32) Session.WaitResult {
const session = &(self.browser.session orelse return .no_page);
return session.wait(ms);
}
@@ -203,7 +203,8 @@ pub fn CDPT(comptime TypeProvider: type) type {
},
5 => switch (@as(u40, @bitCast(domain[0..5].*))) {
asUint(u40, "Fetch") => return @import("domains/fetch.zig").processMessage(command),
- asUint(u40, "Input") => return @import("domains/input.zig").processMessage(command),
+ // @ZIGDOM
+ // asUint(u40, "Input") => return @import("domains/input.zig").processMessage(command),
else => {},
},
6 => switch (@as(u48, @bitCast(domain[0..6].*))) {
@@ -286,7 +287,8 @@ pub fn CDPT(comptime TypeProvider: type) type {
}
pub fn BrowserContext(comptime CDP_T: type) type {
- const Node = @import("Node.zig");
+ // @ZIGMOD
+ // const Node = @import("Node.zig");
return struct {
id: []const u8,
@@ -326,8 +328,9 @@ pub fn BrowserContext(comptime CDP_T: type) type {
security_origin: []const u8,
page_life_cycle_events: bool,
secure_context_type: []const u8,
- node_registry: Node.Registry,
- node_search_list: Node.Search.List,
+ // @ZIGDOM
+ // node_registry: Node.Registry,
+ // node_search_list: Node.Search.List,
inspector: js.Inspector,
isolated_worlds: std.ArrayListUnmanaged(IsolatedWorld),
@@ -360,8 +363,9 @@ pub fn BrowserContext(comptime CDP_T: type) type {
const inspector = try cdp.browser.env.newInspector(arena, self);
- var registry = Node.Registry.init(allocator);
- errdefer registry.deinit();
+ // @ZIGDOM
+ // var registry = Node.Registry.init(allocator);
+ // errdefer registry.deinit();
self.* = .{
.id = id,
@@ -374,8 +378,9 @@ pub fn BrowserContext(comptime CDP_T: type) type {
.secure_context_type = "Secure", // TODO = enum
.loader_id = LOADER_ID,
.page_life_cycle_events = false, // TODO; Target based value
- .node_registry = registry,
- .node_search_list = undefined,
+ // @ZIGDOM
+ // .node_registry = registry,
+ // .node_search_list = undefined,
.isolated_worlds = .empty,
.inspector = inspector,
.notification_arena = cdp.notification_arena.allocator(),
@@ -383,7 +388,8 @@ pub fn BrowserContext(comptime CDP_T: type) type {
.captured_responses = .empty,
.log_interceptor = LogInterceptor(Self).init(allocator, self),
};
- self.node_search_list = Node.Search.List.init(allocator, &self.node_registry);
+ // ZIGDOM
+ // self.node_search_list = Node.Search.List.init(allocator, &self.node_registry);
errdefer self.deinit();
try cdp.browser.notification.register(.page_remove, self, onPageRemove);
@@ -418,8 +424,9 @@ pub fn BrowserContext(comptime CDP_T: type) type {
world.deinit();
}
self.isolated_worlds.clearRetainingCapacity();
- self.node_registry.deinit();
- self.node_search_list.deinit();
+ // @ZIGDOM
+ // self.node_registry.deinit();
+ // self.node_search_list.deinit();
self.cdp.browser.notification.unregisterAll(self);
if (self.http_proxy_changed) {
@@ -433,8 +440,10 @@ pub fn BrowserContext(comptime CDP_T: type) type {
}
pub fn reset(self: *Self) void {
- self.node_registry.reset();
- self.node_search_list.reset();
+ // @ZIGDOM
+ _ = self;
+ // self.node_registry.reset();
+ // self.node_search_list.reset();
}
pub fn createIsolatedWorld(self: *Self, world_name: []const u8, grant_universal_access: bool) !*IsolatedWorld {
@@ -453,19 +462,20 @@ pub fn BrowserContext(comptime CDP_T: type) type {
return world;
}
- pub fn nodeWriter(self: *Self, root: *const Node, opts: Node.Writer.Opts) Node.Writer {
- return .{
- .root = root,
- .depth = opts.depth,
- .exclude_root = opts.exclude_root,
- .registry = &self.node_registry,
- };
- }
+ // @ZIGDOM
+ // pub fn nodeWriter(self: *Self, root: *const Node, opts: Node.Writer.Opts) Node.Writer {
+ // return .{
+ // .root = root,
+ // .depth = opts.depth,
+ // .exclude_root = opts.exclude_root,
+ // .registry = &self.node_registry,
+ // };
+ // }
- pub fn getURL(self: *const Self) ?[]const u8 {
+ pub fn getURL(self: *const Self) ?[:0]const u8 {
const page = self.session.currentPage() orelse return null;
- const raw_url = page.url.raw;
- return if (raw_url.len == 0) null else raw_url;
+ const url = page.url;
+ return if (url.len == 0) null else url;
}
pub fn networkEnable(self: *Self) !void {
diff --git a/src/cdp/domains/dom.zig b/src/cdp/domains/dom.zig
index 0f0ff8f8..e99fd6b6 100644
--- a/src/cdp/domains/dom.zig
+++ b/src/cdp/domains/dom.zig
@@ -19,655 +19,656 @@
const std = @import("std");
const log = @import("../../log.zig");
const Allocator = std.mem.Allocator;
-const Node = @import("../Node.zig");
-const css = @import("../../browser/dom/css.zig");
-const parser = @import("../../browser/netsurf.zig");
-const dom_node = @import("../../browser/dom/node.zig");
-const Element = @import("../../browser/dom/element.zig").Element;
+// const css = @import("../../browser/dom/css.zig");
+// const parser = @import("../../browser/netsurf.zig");
+// const dom_node = @import("../../browser/dom/node.zig");
pub fn processMessage(cmd: anytype) !void {
const action = std.meta.stringToEnum(enum {
enable,
- getDocument,
- performSearch,
- getSearchResults,
- discardSearchResults,
- querySelector,
- querySelectorAll,
- resolveNode,
- describeNode,
- scrollIntoViewIfNeeded,
- getContentQuads,
- getBoxModel,
- requestChildNodes,
- getFrameOwner,
+ // ZIGDOM
+ // getDocument,
+ // performSearch,
+ // getSearchResults,
+ // discardSearchResults,
+ // querySelector,
+ // querySelectorAll,
+ // resolveNode,
+ // describeNode,
+ // scrollIntoViewIfNeeded,
+ // getContentQuads,
+ // getBoxModel,
+ // requestChildNodes,
+ // getFrameOwner,
}, cmd.input.action) orelse return error.UnknownMethod;
switch (action) {
.enable => return cmd.sendResult(null, .{}),
- .getDocument => return getDocument(cmd),
- .performSearch => return performSearch(cmd),
- .getSearchResults => return getSearchResults(cmd),
- .discardSearchResults => return discardSearchResults(cmd),
- .querySelector => return querySelector(cmd),
- .querySelectorAll => return querySelectorAll(cmd),
- .resolveNode => return resolveNode(cmd),
- .describeNode => return describeNode(cmd),
- .scrollIntoViewIfNeeded => return scrollIntoViewIfNeeded(cmd),
- .getContentQuads => return getContentQuads(cmd),
- .getBoxModel => return getBoxModel(cmd),
- .requestChildNodes => return requestChildNodes(cmd),
- .getFrameOwner => return getFrameOwner(cmd),
+ // @ZIGDOM
+ // .getDocument => return getDocument(cmd),
+ // .performSearch => return performSearch(cmd),
+ // .getSearchResults => return getSearchResults(cmd),
+ // .discardSearchResults => return discardSearchResults(cmd),
+ // .querySelector => return querySelector(cmd),
+ // .querySelectorAll => return querySelectorAll(cmd),
+ // .resolveNode => return resolveNode(cmd),
+ // .describeNode => return describeNode(cmd),
+ // .scrollIntoViewIfNeeded => return scrollIntoViewIfNeeded(cmd),
+ // .getContentQuads => return getContentQuads(cmd),
+ // .getBoxModel => return getBoxModel(cmd),
+ // .requestChildNodes => return requestChildNodes(cmd),
+ // .getFrameOwner => return getFrameOwner(cmd),
}
}
-// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-getDocument
-fn getDocument(cmd: anytype) !void {
- const Params = struct {
- // CDP documentation implies that 0 isn't valid, but it _does_ work in Chrome
- depth: i32 = 3,
- pierce: bool = false,
- };
- const params = try cmd.params(Params) orelse Params{};
-
- if (params.pierce) {
- log.warn(.cdp, "not implemented", .{ .feature = "DOM.getDocument: Not implemented pierce parameter" });
- }
-
- const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
- const page = bc.session.currentPage() orelse return error.PageNotLoaded;
- const doc = parser.documentHTMLToDocument(page.window.document);
-
- const node = try bc.node_registry.register(parser.documentToNode(doc));
- return cmd.sendResult(.{ .root = bc.nodeWriter(node, .{ .depth = params.depth }) }, .{});
-}
-
-// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-performSearch
-fn performSearch(cmd: anytype) !void {
- const params = (try cmd.params(struct {
- query: []const u8,
- includeUserAgentShadowDOM: ?bool = null,
- })) orelse return error.InvalidParams;
-
- const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
- const page = bc.session.currentPage() orelse return error.PageNotLoaded;
- const doc = parser.documentHTMLToDocument(page.window.document);
-
- const allocator = cmd.cdp.allocator;
- var list = try css.querySelectorAll(allocator, parser.documentToNode(doc), params.query);
- defer list.deinit(allocator);
-
- const search = try bc.node_search_list.create(list.nodes.items);
-
- // dispatch setChildNodesEvents to inform the client of the subpart of node
- // tree covering the results.
- try dispatchSetChildNodes(cmd, list.nodes.items);
-
- return cmd.sendResult(.{
- .searchId = search.name,
- .resultCount = @as(u32, @intCast(search.node_ids.len)),
- }, .{});
-}
-
-// dispatchSetChildNodes send the setChildNodes event for the whole DOM tree
-// hierarchy of each nodes.
-// We dispatch event in the reverse order: from the top level to the direct parents.
-// We should dispatch a node only if it has never been sent.
-fn dispatchSetChildNodes(cmd: anytype, nodes: []*parser.Node) !void {
- const arena = cmd.arena;
- const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
- const session_id = bc.session_id orelse return error.SessionIdNotLoaded;
-
- var parents: std.ArrayListUnmanaged(*Node) = .{};
- for (nodes) |_n| {
- var n = _n;
- while (true) {
- const p = parser.nodeParentNode(n) orelse break;
-
- // Register the node.
- const node = try bc.node_registry.register(p);
- if (node.set_child_nodes_event) break;
- try parents.append(arena, node);
- n = p;
- }
- }
-
- const plen = parents.items.len;
- if (plen == 0) return;
-
- var i: usize = plen;
- // We're going to iterate in reverse order from how we added them.
- // This ensures that we're emitting the tree of nodes top-down.
- while (i > 0) {
- i -= 1;
- const node = parents.items[i];
- // Although our above loop won't add an already-sent node to `parents`
- // this can still be true because two nodes can share the same parent node
- // so we might have just sent the node a previous iteration of this loop
- if (node.set_child_nodes_event) continue;
-
- node.set_child_nodes_event = true;
-
- // If the node has no parent, it's the root node.
- // We don't dispatch event for it because we assume the root node is
- // dispatched via the DOM.getDocument command.
- const p = parser.nodeParentNode(node._node) orelse {
- continue;
- };
-
- // Retrieve the parent from the registry.
- const parent_node = try bc.node_registry.register(p);
-
- try cmd.sendEvent("DOM.setChildNodes", .{
- .parentId = parent_node.id,
- .nodes = .{bc.nodeWriter(node, .{})},
- }, .{
- .session_id = session_id,
- });
- }
-}
-
-// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-discardSearchResults
-fn discardSearchResults(cmd: anytype) !void {
- const params = (try cmd.params(struct {
- searchId: []const u8,
- })) orelse return error.InvalidParams;
-
- const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
-
- bc.node_search_list.remove(params.searchId);
- return cmd.sendResult(null, .{});
-}
-
-// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-getSearchResults
-fn getSearchResults(cmd: anytype) !void {
- const params = (try cmd.params(struct {
- searchId: []const u8,
- fromIndex: u32,
- toIndex: u32,
- })) orelse return error.InvalidParams;
-
- if (params.fromIndex >= params.toIndex) {
- return error.BadIndices;
- }
-
- const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
-
- const search = bc.node_search_list.get(params.searchId) orelse {
- return error.SearchResultNotFound;
- };
-
- const node_ids = search.node_ids;
-
- if (params.fromIndex >= node_ids.len) return error.BadFromIndex;
- if (params.toIndex > node_ids.len) return error.BadToIndex;
-
- return cmd.sendResult(.{ .nodeIds = node_ids[params.fromIndex..params.toIndex] }, .{});
-}
-
-fn querySelector(cmd: anytype) !void {
- const params = (try cmd.params(struct {
- nodeId: Node.Id,
- selector: []const u8,
- })) orelse return error.InvalidParams;
-
- const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
-
- const node = bc.node_registry.lookup_by_id.get(params.nodeId) orelse {
- return cmd.sendError(-32000, "Could not find node with given id", .{});
- };
-
- const selected_node = try css.querySelector(
- cmd.arena,
- node._node,
- params.selector,
- ) orelse return error.NodeNotFoundForGivenId;
-
- const registered_node = try bc.node_registry.register(selected_node);
-
- // Dispatch setChildNodesEvents to inform the client of the subpart of node tree covering the results.
- var array = [1]*parser.Node{selected_node};
- try dispatchSetChildNodes(cmd, array[0..]);
-
- return cmd.sendResult(.{
- .nodeId = registered_node.id,
- }, .{});
-}
-
-fn querySelectorAll(cmd: anytype) !void {
- const params = (try cmd.params(struct {
- nodeId: Node.Id,
- selector: []const u8,
- })) orelse return error.InvalidParams;
-
- const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
-
- const node = bc.node_registry.lookup_by_id.get(params.nodeId) orelse {
- return cmd.sendError(-32000, "Could not find node with given id", .{});
- };
-
- const arena = cmd.arena;
- const selected_nodes = try css.querySelectorAll(arena, node._node, params.selector);
- const nodes = selected_nodes.nodes.items;
-
- const node_ids = try arena.alloc(Node.Id, nodes.len);
- for (nodes, node_ids) |selected_node, *node_id| {
- node_id.* = (try bc.node_registry.register(selected_node)).id;
- }
-
- // Dispatch setChildNodesEvents to inform the client of the subpart of node tree covering the results.
- try dispatchSetChildNodes(cmd, nodes);
-
- return cmd.sendResult(.{
- .nodeIds = node_ids,
- }, .{});
-}
-
-fn resolveNode(cmd: anytype) !void {
- const params = (try cmd.params(struct {
- nodeId: ?Node.Id = null,
- backendNodeId: ?u32 = null,
- objectGroup: ?[]const u8 = null,
- executionContextId: ?u32 = null,
- })) orelse return error.InvalidParams;
-
- const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
- const page = bc.session.currentPage() orelse return error.PageNotLoaded;
-
- var js_context = page.js;
- if (params.executionContextId) |context_id| {
- if (js_context.v8_context.debugContextId() != context_id) {
- for (bc.isolated_worlds.items) |*isolated_world| {
- js_context = &(isolated_world.executor.context orelse return error.ContextNotFound);
- if (js_context.v8_context.debugContextId() == context_id) {
- break;
- }
- } else return error.ContextNotFound;
- }
- }
-
- const input_node_id = params.nodeId orelse params.backendNodeId orelse return error.InvalidParam;
- const node = bc.node_registry.lookup_by_id.get(input_node_id) orelse return error.UnknownNode;
-
- // node._node is a *parser.Node 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
- const remote_object = try bc.inspector.getRemoteObject(
- js_context,
- params.objectGroup orelse "",
- try dom_node.Node.toInterface(node._node),
- );
- defer remote_object.deinit();
-
- const arena = cmd.arena;
- return cmd.sendResult(.{ .object = .{
- .type = try remote_object.getType(arena),
- .subtype = try remote_object.getSubtype(arena),
- .className = try remote_object.getClassName(arena),
- .description = try remote_object.getDescription(arena),
- .objectId = try remote_object.getObjectId(arena),
- } }, .{});
-}
-
-fn describeNode(cmd: anytype) !void {
- const params = (try cmd.params(struct {
- nodeId: ?Node.Id = null,
- backendNodeId: ?Node.Id = null,
- objectId: ?[]const u8 = null,
- depth: i32 = 1,
- pierce: bool = false,
- })) orelse return error.InvalidParams;
-
- if (params.pierce) {
- log.warn(.cdp, "not implemented", .{ .feature = "DOM.describeNode: Not implemented pierce parameter" });
- }
- const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
-
- const node = try getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId);
-
- return cmd.sendResult(.{ .node = bc.nodeWriter(node, .{ .depth = params.depth }) }, .{});
-}
-
-// An array of quad vertices, x immediately followed by y for each point, points clock-wise.
-// Note Y points downward
-// We are assuming the start/endpoint is not repeated.
-const Quad = [8]f64;
-
-const BoxModel = struct {
- content: Quad,
- padding: Quad,
- border: Quad,
- margin: Quad,
- width: i32,
- height: i32,
- // shapeOutside: ?ShapeOutsideInfo,
-};
-
-fn rectToQuad(rect: Element.DOMRect) Quad {
- return Quad{
- rect.x,
- rect.y,
- rect.x + rect.width,
- rect.y,
- rect.x + rect.width,
- rect.y + rect.height,
- rect.x,
- rect.y + rect.height,
- };
-}
-
-fn scrollIntoViewIfNeeded(cmd: anytype) !void {
- const params = (try cmd.params(struct {
- nodeId: ?Node.Id = null,
- backendNodeId: ?u32 = null,
- objectId: ?[]const u8 = null,
- rect: ?Element.DOMRect = null,
- })) orelse return error.InvalidParams;
- // Only 1 of nodeId, backendNodeId, objectId may be set, but chrome just takes the first non-null
-
- // We retrieve the node to at least check if it exists and is valid.
- const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
- const node = try getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId);
-
- const node_type = parser.nodeType(node._node);
- switch (node_type) {
- .element => {},
- .document => {},
- .text => {},
- else => return error.NodeDoesNotHaveGeometry,
- }
-
- return cmd.sendResult(null, .{});
-}
-
-fn getNode(arena: Allocator, browser_context: anytype, node_id: ?Node.Id, backend_node_id: ?Node.Id, object_id: ?[]const u8) !*Node {
- const input_node_id = node_id orelse backend_node_id;
- if (input_node_id) |input_node_id_| {
- return browser_context.node_registry.lookup_by_id.get(input_node_id_) orelse return error.NodeNotFound;
- }
- if (object_id) |object_id_| {
- // Retrieve the object from which ever context it is in.
- const parser_node = try browser_context.inspector.getNodePtr(arena, object_id_);
- return try browser_context.node_registry.register(@ptrCast(@alignCast(parser_node)));
- }
- return error.MissingParams;
-}
-
-// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-getContentQuads
-// Related to: https://drafts.csswg.org/cssom-view/#the-geometryutils-interface
-fn getContentQuads(cmd: anytype) !void {
- const params = (try cmd.params(struct {
- nodeId: ?Node.Id = null,
- backendNodeId: ?Node.Id = null,
- objectId: ?[]const u8 = null,
- })) orelse return error.InvalidParams;
-
- const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
- const page = bc.session.currentPage() orelse return error.PageNotLoaded;
-
- const node = try getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId);
-
- // TODO likely if the following CSS properties are set the quads should be empty
- // visibility: hidden
- // display: none
-
- if (parser.nodeType(node._node) != .element) return error.NodeIsNotAnElement;
- // TODO implement for document or text
- // Most likely document would require some hierachgy in the renderer. It is left unimplemented till we have a good example.
- // Text may be tricky, multiple quads in case of multiple lines? empty quads of text = ""?
- // Elements like SVGElement may have multiple quads.
-
- const element = parser.nodeToElement(node._node);
- const rect = try Element._getBoundingClientRect(element, page);
- const quad = rectToQuad(rect);
-
- return cmd.sendResult(.{ .quads = &.{quad} }, .{});
-}
-
-fn getBoxModel(cmd: anytype) !void {
- const params = (try cmd.params(struct {
- nodeId: ?Node.Id = null,
- backendNodeId: ?u32 = null,
- objectId: ?[]const u8 = null,
- })) orelse return error.InvalidParams;
-
- const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
- const page = bc.session.currentPage() orelse return error.PageNotLoaded;
-
- const node = try getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId);
-
- // TODO implement for document or text
- if (parser.nodeType(node._node) != .element) return error.NodeIsNotAnElement;
- const element = parser.nodeToElement(node._node);
-
- const rect = try Element._getBoundingClientRect(element, page);
- const quad = rectToQuad(rect);
-
- return cmd.sendResult(.{ .model = BoxModel{
- .content = quad,
- .padding = quad,
- .border = quad,
- .margin = quad,
- .width = @intFromFloat(rect.width),
- .height = @intFromFloat(rect.height),
- } }, .{});
-}
-
-fn requestChildNodes(cmd: anytype) !void {
- const params = (try cmd.params(struct {
- nodeId: Node.Id,
- depth: i32 = 1,
- pierce: bool = false,
- })) orelse return error.InvalidParams;
-
- if (params.depth == 0) return error.InvalidParams;
- const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
- const session_id = bc.session_id orelse return error.SessionIdNotLoaded;
- const node = bc.node_registry.lookup_by_id.get(params.nodeId) orelse {
- return error.InvalidNode;
- };
-
- try cmd.sendEvent("DOM.setChildNodes", .{
- .parentId = node.id,
- .nodes = bc.nodeWriter(node, .{ .depth = params.depth, .exclude_root = true }),
- }, .{
- .session_id = session_id,
- });
-
- return cmd.sendResult(null, .{});
-}
-
-fn getFrameOwner(cmd: anytype) !void {
- const params = (try cmd.params(struct {
- frameId: []const u8,
- })) orelse return error.InvalidParams;
-
- const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
- const target_id = bc.target_id orelse return error.TargetNotLoaded;
- if (std.mem.eql(u8, target_id, params.frameId) == false) {
- return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
- }
-
- const page = bc.session.currentPage() orelse return error.PageNotLoaded;
- const doc = parser.documentHTMLToDocument(page.window.document);
-
- const node = try bc.node_registry.register(parser.documentToNode(doc));
- return cmd.sendResult(.{ .nodeId = node.id, .backendNodeId = node.id }, .{});
-}
-
-const testing = @import("../testing.zig");
-
-test "cdp.dom: getSearchResults unknown search id" {
- var ctx = testing.context();
- defer ctx.deinit();
-
- try testing.expectError(error.BrowserContextNotLoaded, ctx.processMessage(.{
- .id = 8,
- .method = "DOM.getSearchResults",
- .params = .{ .searchId = "Nope", .fromIndex = 0, .toIndex = 10 },
- }));
-}
-
-test "cdp.dom: search flow" {
- var ctx = testing.context();
- defer ctx.deinit();
-
- _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .html = "1
2
" });
-
- try ctx.processMessage(.{
- .id = 12,
- .method = "DOM.performSearch",
- .params = .{ .query = "p" },
- });
- try ctx.expectSentResult(.{ .searchId = "0", .resultCount = 2 }, .{ .id = 12 });
-
- {
- // getSearchResults
- try ctx.processMessage(.{
- .id = 13,
- .method = "DOM.getSearchResults",
- .params = .{ .searchId = "0", .fromIndex = 0, .toIndex = 2 },
- });
- try ctx.expectSentResult(.{ .nodeIds = &.{ 1, 2 } }, .{ .id = 13 });
-
- // different fromIndex
- try ctx.processMessage(.{
- .id = 14,
- .method = "DOM.getSearchResults",
- .params = .{ .searchId = "0", .fromIndex = 1, .toIndex = 2 },
- });
- try ctx.expectSentResult(.{ .nodeIds = &.{2} }, .{ .id = 14 });
-
- // different toIndex
- try ctx.processMessage(.{
- .id = 15,
- .method = "DOM.getSearchResults",
- .params = .{ .searchId = "0", .fromIndex = 0, .toIndex = 1 },
- });
- try ctx.expectSentResult(.{ .nodeIds = &.{1} }, .{ .id = 15 });
- }
-
- try ctx.processMessage(.{
- .id = 16,
- .method = "DOM.discardSearchResults",
- .params = .{ .searchId = "0" },
- });
- try ctx.expectSentResult(null, .{ .id = 16 });
-
- // make sure the delete actually did something
- try testing.expectError(error.SearchResultNotFound, ctx.processMessage(.{
- .id = 17,
- .method = "DOM.getSearchResults",
- .params = .{ .searchId = "0", .fromIndex = 0, .toIndex = 1 },
- }));
-}
-
-test "cdp.dom: querySelector unknown search id" {
- var ctx = testing.context();
- defer ctx.deinit();
-
- _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .html = "1
2
" });
-
- try ctx.processMessage(.{
- .id = 9,
- .method = "DOM.querySelector",
- .params = .{ .nodeId = 99, .selector = "" },
- });
- try ctx.expectSentError(-32000, "Could not find node with given id", .{});
-
- try ctx.processMessage(.{
- .id = 9,
- .method = "DOM.querySelectorAll",
- .params = .{ .nodeId = 99, .selector = "" },
- });
- try ctx.expectSentError(-32000, "Could not find node with given id", .{});
-}
-
-test "cdp.dom: querySelector Node not found" {
- var ctx = testing.context();
- defer ctx.deinit();
-
- _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .html = "1
2
" });
-
- try ctx.processMessage(.{ // Hacky way to make sure nodeId 1 exists in the registry
- .id = 3,
- .method = "DOM.performSearch",
- .params = .{ .query = "p" },
- });
- try ctx.expectSentResult(.{ .searchId = "0", .resultCount = 2 }, .{ .id = 3 });
-
- try testing.expectError(error.NodeNotFoundForGivenId, ctx.processMessage(.{
- .id = 4,
- .method = "DOM.querySelector",
- .params = .{ .nodeId = 1, .selector = "a" },
- }));
-
- try ctx.processMessage(.{
- .id = 5,
- .method = "DOM.querySelectorAll",
- .params = .{ .nodeId = 1, .selector = "a" },
- });
- try ctx.expectSentResult(.{ .nodeIds = &[_]u32{} }, .{ .id = 5 });
-}
-
-test "cdp.dom: querySelector Nodes found" {
- var ctx = testing.context();
- defer ctx.deinit();
-
- _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .html = "" });
-
- try ctx.processMessage(.{ // Hacky way to make sure nodeId 1 exists in the registry
- .id = 3,
- .method = "DOM.performSearch",
- .params = .{ .query = "div" },
- });
- try ctx.expectSentResult(.{ .searchId = "0", .resultCount = 1 }, .{ .id = 3 });
-
- try ctx.processMessage(.{
- .id = 4,
- .method = "DOM.querySelector",
- .params = .{ .nodeId = 1, .selector = "p" },
- });
- try ctx.expectSentEvent("DOM.setChildNodes", null, .{});
- try ctx.expectSentResult(.{ .nodeId = 6 }, .{ .id = 4 });
-
- try ctx.processMessage(.{
- .id = 5,
- .method = "DOM.querySelectorAll",
- .params = .{ .nodeId = 1, .selector = "p" },
- });
- try ctx.expectSentEvent("DOM.setChildNodes", null, .{});
- try ctx.expectSentResult(.{ .nodeIds = &.{6} }, .{ .id = 5 });
-}
-
-test "cdp.dom: getBoxModel" {
- var ctx = testing.context();
- defer ctx.deinit();
-
- _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .html = "" });
-
- try ctx.processMessage(.{ // Hacky way to make sure nodeId 1 exists in the registry
- .id = 3,
- .method = "DOM.getDocument",
- });
-
- try ctx.processMessage(.{
- .id = 4,
- .method = "DOM.querySelector",
- .params = .{ .nodeId = 1, .selector = "p" },
- });
- try ctx.expectSentResult(.{ .nodeId = 3 }, .{ .id = 4 });
-
- try ctx.processMessage(.{
- .id = 5,
- .method = "DOM.getBoxModel",
- .params = .{ .nodeId = 6 },
- });
- try ctx.expectSentResult(.{ .model = BoxModel{
- .content = Quad{ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 },
- .padding = Quad{ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 },
- .border = Quad{ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 },
- .margin = Quad{ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 },
- .width = 1,
- .height = 1,
- } }, .{ .id = 5 });
-}
+// ZIGDOM
+// // https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-getDocument
+// fn getDocument(cmd: anytype) !void {
+// const Params = struct {
+// // CDP documentation implies that 0 isn't valid, but it _does_ work in Chrome
+// depth: i32 = 3,
+// pierce: bool = false,
+// };
+// const params = try cmd.params(Params) orelse Params{};
+
+// if (params.pierce) {
+// log.warn(.cdp, "not implemented", .{ .feature = "DOM.getDocument: Not implemented pierce parameter" });
+// }
+
+// const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+// const page = bc.session.currentPage() orelse return error.PageNotLoaded;
+// const doc = parser.documentHTMLToDocument(page.window.document);
+
+// const node = try bc.node_registry.register(parser.documentToNode(doc));
+// return cmd.sendResult(.{ .root = bc.nodeWriter(node, .{ .depth = params.depth }) }, .{});
+// }
+
+// // https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-performSearch
+// fn performSearch(cmd: anytype) !void {
+// const params = (try cmd.params(struct {
+// query: []const u8,
+// includeUserAgentShadowDOM: ?bool = null,
+// })) orelse return error.InvalidParams;
+
+// const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+// const page = bc.session.currentPage() orelse return error.PageNotLoaded;
+// const doc = parser.documentHTMLToDocument(page.window.document);
+
+// const allocator = cmd.cdp.allocator;
+// var list = try css.querySelectorAll(allocator, parser.documentToNode(doc), params.query);
+// defer list.deinit(allocator);
+
+// const search = try bc.node_search_list.create(list.nodes.items);
+
+// // dispatch setChildNodesEvents to inform the client of the subpart of node
+// // tree covering the results.
+// try dispatchSetChildNodes(cmd, list.nodes.items);
+
+// return cmd.sendResult(.{
+// .searchId = search.name,
+// .resultCount = @as(u32, @intCast(search.node_ids.len)),
+// }, .{});
+// }
+
+// // dispatchSetChildNodes send the setChildNodes event for the whole DOM tree
+// // hierarchy of each nodes.
+// // We dispatch event in the reverse order: from the top level to the direct parents.
+// // We should dispatch a node only if it has never been sent.
+// fn dispatchSetChildNodes(cmd: anytype, nodes: []*parser.Node) !void {
+// const arena = cmd.arena;
+// const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+// const session_id = bc.session_id orelse return error.SessionIdNotLoaded;
+
+// var parents: std.ArrayListUnmanaged(*Node) = .{};
+// for (nodes) |_n| {
+// var n = _n;
+// while (true) {
+// const p = parser.nodeParentNode(n) orelse break;
+
+// // Register the node.
+// const node = try bc.node_registry.register(p);
+// if (node.set_child_nodes_event) break;
+// try parents.append(arena, node);
+// n = p;
+// }
+// }
+
+// const plen = parents.items.len;
+// if (plen == 0) return;
+
+// var i: usize = plen;
+// // We're going to iterate in reverse order from how we added them.
+// // This ensures that we're emitting the tree of nodes top-down.
+// while (i > 0) {
+// i -= 1;
+// const node = parents.items[i];
+// // Although our above loop won't add an already-sent node to `parents`
+// // this can still be true because two nodes can share the same parent node
+// // so we might have just sent the node a previous iteration of this loop
+// if (node.set_child_nodes_event) continue;
+
+// node.set_child_nodes_event = true;
+
+// // If the node has no parent, it's the root node.
+// // We don't dispatch event for it because we assume the root node is
+// // dispatched via the DOM.getDocument command.
+// const p = parser.nodeParentNode(node._node) orelse {
+// continue;
+// };
+
+// // Retrieve the parent from the registry.
+// const parent_node = try bc.node_registry.register(p);
+
+// try cmd.sendEvent("DOM.setChildNodes", .{
+// .parentId = parent_node.id,
+// .nodes = .{bc.nodeWriter(node, .{})},
+// }, .{
+// .session_id = session_id,
+// });
+// }
+// }
+
+// // https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-discardSearchResults
+// fn discardSearchResults(cmd: anytype) !void {
+// const params = (try cmd.params(struct {
+// searchId: []const u8,
+// })) orelse return error.InvalidParams;
+
+// const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+
+// bc.node_search_list.remove(params.searchId);
+// return cmd.sendResult(null, .{});
+// }
+
+// // https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-getSearchResults
+// fn getSearchResults(cmd: anytype) !void {
+// const params = (try cmd.params(struct {
+// searchId: []const u8,
+// fromIndex: u32,
+// toIndex: u32,
+// })) orelse return error.InvalidParams;
+
+// if (params.fromIndex >= params.toIndex) {
+// return error.BadIndices;
+// }
+
+// const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+
+// const search = bc.node_search_list.get(params.searchId) orelse {
+// return error.SearchResultNotFound;
+// };
+
+// const node_ids = search.node_ids;
+
+// if (params.fromIndex >= node_ids.len) return error.BadFromIndex;
+// if (params.toIndex > node_ids.len) return error.BadToIndex;
+
+// return cmd.sendResult(.{ .nodeIds = node_ids[params.fromIndex..params.toIndex] }, .{});
+// }
+
+// fn querySelector(cmd: anytype) !void {
+// const params = (try cmd.params(struct {
+// nodeId: Node.Id,
+// selector: []const u8,
+// })) orelse return error.InvalidParams;
+
+// const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+
+// const node = bc.node_registry.lookup_by_id.get(params.nodeId) orelse {
+// return cmd.sendError(-32000, "Could not find node with given id", .{});
+// };
+
+// const selected_node = try css.querySelector(
+// cmd.arena,
+// node._node,
+// params.selector,
+// ) orelse return error.NodeNotFoundForGivenId;
+
+// const registered_node = try bc.node_registry.register(selected_node);
+
+// // Dispatch setChildNodesEvents to inform the client of the subpart of node tree covering the results.
+// var array = [1]*parser.Node{selected_node};
+// try dispatchSetChildNodes(cmd, array[0..]);
+
+// return cmd.sendResult(.{
+// .nodeId = registered_node.id,
+// }, .{});
+// }
+
+// fn querySelectorAll(cmd: anytype) !void {
+// const params = (try cmd.params(struct {
+// nodeId: Node.Id,
+// selector: []const u8,
+// })) orelse return error.InvalidParams;
+
+// const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+
+// const node = bc.node_registry.lookup_by_id.get(params.nodeId) orelse {
+// return cmd.sendError(-32000, "Could not find node with given id", .{});
+// };
+
+// const arena = cmd.arena;
+// const selected_nodes = try css.querySelectorAll(arena, node._node, params.selector);
+// const nodes = selected_nodes.nodes.items;
+
+// const node_ids = try arena.alloc(Node.Id, nodes.len);
+// for (nodes, node_ids) |selected_node, *node_id| {
+// node_id.* = (try bc.node_registry.register(selected_node)).id;
+// }
+
+// // Dispatch setChildNodesEvents to inform the client of the subpart of node tree covering the results.
+// try dispatchSetChildNodes(cmd, nodes);
+
+// return cmd.sendResult(.{
+// .nodeIds = node_ids,
+// }, .{});
+// }
+
+// fn resolveNode(cmd: anytype) !void {
+// const params = (try cmd.params(struct {
+// nodeId: ?Node.Id = null,
+// backendNodeId: ?u32 = null,
+// objectGroup: ?[]const u8 = null,
+// executionContextId: ?u32 = null,
+// })) orelse return error.InvalidParams;
+
+// const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+// const page = bc.session.currentPage() orelse return error.PageNotLoaded;
+
+// var js_context = page.js;
+// if (params.executionContextId) |context_id| {
+// if (js_context.v8_context.debugContextId() != context_id) {
+// for (bc.isolated_worlds.items) |*isolated_world| {
+// js_context = &(isolated_world.executor.context orelse return error.ContextNotFound);
+// if (js_context.v8_context.debugContextId() == context_id) {
+// break;
+// }
+// } else return error.ContextNotFound;
+// }
+// }
+
+// const input_node_id = params.nodeId orelse params.backendNodeId orelse return error.InvalidParam;
+// const node = bc.node_registry.lookup_by_id.get(input_node_id) orelse return error.UnknownNode;
+
+// // node._node is a *parser.Node 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
+// const remote_object = try bc.inspector.getRemoteObject(
+// js_context,
+// params.objectGroup orelse "",
+// try dom_node.Node.toInterface(node._node),
+// );
+// defer remote_object.deinit();
+
+// const arena = cmd.arena;
+// return cmd.sendResult(.{ .object = .{
+// .type = try remote_object.getType(arena),
+// .subtype = try remote_object.getSubtype(arena),
+// .className = try remote_object.getClassName(arena),
+// .description = try remote_object.getDescription(arena),
+// .objectId = try remote_object.getObjectId(arena),
+// } }, .{});
+// }
+
+// fn describeNode(cmd: anytype) !void {
+// const params = (try cmd.params(struct {
+// nodeId: ?Node.Id = null,
+// backendNodeId: ?Node.Id = null,
+// objectId: ?[]const u8 = null,
+// depth: i32 = 1,
+// pierce: bool = false,
+// })) orelse return error.InvalidParams;
+
+// if (params.pierce) {
+// log.warn(.cdp, "not implemented", .{ .feature = "DOM.describeNode: Not implemented pierce parameter" });
+// }
+// const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+
+// const node = try getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId);
+
+// return cmd.sendResult(.{ .node = bc.nodeWriter(node, .{ .depth = params.depth }) }, .{});
+// }
+
+// // An array of quad vertices, x immediately followed by y for each point, points clock-wise.
+// // Note Y points downward
+// // We are assuming the start/endpoint is not repeated.
+// const Quad = [8]f64;
+
+// const BoxModel = struct {
+// content: Quad,
+// padding: Quad,
+// border: Quad,
+// margin: Quad,
+// width: i32,
+// height: i32,
+// // shapeOutside: ?ShapeOutsideInfo,
+// };
+
+// fn rectToQuad(rect: Element.DOMRect) Quad {
+// return Quad{
+// rect.x,
+// rect.y,
+// rect.x + rect.width,
+// rect.y,
+// rect.x + rect.width,
+// rect.y + rect.height,
+// rect.x,
+// rect.y + rect.height,
+// };
+// }
+
+// fn scrollIntoViewIfNeeded(cmd: anytype) !void {
+// const params = (try cmd.params(struct {
+// nodeId: ?Node.Id = null,
+// backendNodeId: ?u32 = null,
+// objectId: ?[]const u8 = null,
+// rect: ?Element.DOMRect = null,
+// })) orelse return error.InvalidParams;
+// // Only 1 of nodeId, backendNodeId, objectId may be set, but chrome just takes the first non-null
+
+// // We retrieve the node to at least check if it exists and is valid.
+// const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+// const node = try getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId);
+
+// const node_type = parser.nodeType(node._node);
+// switch (node_type) {
+// .element => {},
+// .document => {},
+// .text => {},
+// else => return error.NodeDoesNotHaveGeometry,
+// }
+
+// return cmd.sendResult(null, .{});
+// }
+
+// fn getNode(arena: Allocator, browser_context: anytype, node_id: ?Node.Id, backend_node_id: ?Node.Id, object_id: ?[]const u8) !*Node {
+// const input_node_id = node_id orelse backend_node_id;
+// if (input_node_id) |input_node_id_| {
+// return browser_context.node_registry.lookup_by_id.get(input_node_id_) orelse return error.NodeNotFound;
+// }
+// if (object_id) |object_id_| {
+// // Retrieve the object from which ever context it is in.
+// const parser_node = try browser_context.inspector.getNodePtr(arena, object_id_);
+// return try browser_context.node_registry.register(@ptrCast(@alignCast(parser_node)));
+// }
+// return error.MissingParams;
+// }
+
+// // https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-getContentQuads
+// // Related to: https://drafts.csswg.org/cssom-view/#the-geometryutils-interface
+// fn getContentQuads(cmd: anytype) !void {
+// const params = (try cmd.params(struct {
+// nodeId: ?Node.Id = null,
+// backendNodeId: ?Node.Id = null,
+// objectId: ?[]const u8 = null,
+// })) orelse return error.InvalidParams;
+
+// const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+// const page = bc.session.currentPage() orelse return error.PageNotLoaded;
+
+// const node = try getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId);
+
+// // TODO likely if the following CSS properties are set the quads should be empty
+// // visibility: hidden
+// // display: none
+
+// if (parser.nodeType(node._node) != .element) return error.NodeIsNotAnElement;
+// // TODO implement for document or text
+// // Most likely document would require some hierachgy in the renderer. It is left unimplemented till we have a good example.
+// // Text may be tricky, multiple quads in case of multiple lines? empty quads of text = ""?
+// // Elements like SVGElement may have multiple quads.
+
+// const element = parser.nodeToElement(node._node);
+// const rect = try Element._getBoundingClientRect(element, page);
+// const quad = rectToQuad(rect);
+
+// return cmd.sendResult(.{ .quads = &.{quad} }, .{});
+// }
+
+// fn getBoxModel(cmd: anytype) !void {
+// const params = (try cmd.params(struct {
+// nodeId: ?Node.Id = null,
+// backendNodeId: ?u32 = null,
+// objectId: ?[]const u8 = null,
+// })) orelse return error.InvalidParams;
+
+// const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+// const page = bc.session.currentPage() orelse return error.PageNotLoaded;
+
+// const node = try getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId);
+
+// // TODO implement for document or text
+// if (parser.nodeType(node._node) != .element) return error.NodeIsNotAnElement;
+// const element = parser.nodeToElement(node._node);
+
+// const rect = try Element._getBoundingClientRect(element, page);
+// const quad = rectToQuad(rect);
+
+// return cmd.sendResult(.{ .model = BoxModel{
+// .content = quad,
+// .padding = quad,
+// .border = quad,
+// .margin = quad,
+// .width = @intFromFloat(rect.width),
+// .height = @intFromFloat(rect.height),
+// } }, .{});
+// }
+
+// fn requestChildNodes(cmd: anytype) !void {
+// const params = (try cmd.params(struct {
+// nodeId: Node.Id,
+// depth: i32 = 1,
+// pierce: bool = false,
+// })) orelse return error.InvalidParams;
+
+// if (params.depth == 0) return error.InvalidParams;
+// const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+// const session_id = bc.session_id orelse return error.SessionIdNotLoaded;
+// const node = bc.node_registry.lookup_by_id.get(params.nodeId) orelse {
+// return error.InvalidNode;
+// };
+
+// try cmd.sendEvent("DOM.setChildNodes", .{
+// .parentId = node.id,
+// .nodes = bc.nodeWriter(node, .{ .depth = params.depth, .exclude_root = true }),
+// }, .{
+// .session_id = session_id,
+// });
+
+// return cmd.sendResult(null, .{});
+// }
+
+// fn getFrameOwner(cmd: anytype) !void {
+// const params = (try cmd.params(struct {
+// frameId: []const u8,
+// })) orelse return error.InvalidParams;
+
+// const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+// const target_id = bc.target_id orelse return error.TargetNotLoaded;
+// if (std.mem.eql(u8, target_id, params.frameId) == false) {
+// return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
+// }
+
+// const page = bc.session.currentPage() orelse return error.PageNotLoaded;
+// const doc = parser.documentHTMLToDocument(page.window.document);
+
+// const node = try bc.node_registry.register(parser.documentToNode(doc));
+// return cmd.sendResult(.{ .nodeId = node.id, .backendNodeId = node.id }, .{});
+// }
+
+// const testing = @import("../testing.zig");
+
+// test "cdp.dom: getSearchResults unknown search id" {
+// var ctx = testing.context();
+// defer ctx.deinit();
+
+// try testing.expectError(error.BrowserContextNotLoaded, ctx.processMessage(.{
+// .id = 8,
+// .method = "DOM.getSearchResults",
+// .params = .{ .searchId = "Nope", .fromIndex = 0, .toIndex = 10 },
+// }));
+// }
+
+// test "cdp.dom: search flow" {
+// var ctx = testing.context();
+// defer ctx.deinit();
+
+// _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .html = "1
2
" });
+
+// try ctx.processMessage(.{
+// .id = 12,
+// .method = "DOM.performSearch",
+// .params = .{ .query = "p" },
+// });
+// try ctx.expectSentResult(.{ .searchId = "0", .resultCount = 2 }, .{ .id = 12 });
+
+// {
+// // getSearchResults
+// try ctx.processMessage(.{
+// .id = 13,
+// .method = "DOM.getSearchResults",
+// .params = .{ .searchId = "0", .fromIndex = 0, .toIndex = 2 },
+// });
+// try ctx.expectSentResult(.{ .nodeIds = &.{ 1, 2 } }, .{ .id = 13 });
+
+// // different fromIndex
+// try ctx.processMessage(.{
+// .id = 14,
+// .method = "DOM.getSearchResults",
+// .params = .{ .searchId = "0", .fromIndex = 1, .toIndex = 2 },
+// });
+// try ctx.expectSentResult(.{ .nodeIds = &.{2} }, .{ .id = 14 });
+
+// // different toIndex
+// try ctx.processMessage(.{
+// .id = 15,
+// .method = "DOM.getSearchResults",
+// .params = .{ .searchId = "0", .fromIndex = 0, .toIndex = 1 },
+// });
+// try ctx.expectSentResult(.{ .nodeIds = &.{1} }, .{ .id = 15 });
+// }
+
+// try ctx.processMessage(.{
+// .id = 16,
+// .method = "DOM.discardSearchResults",
+// .params = .{ .searchId = "0" },
+// });
+// try ctx.expectSentResult(null, .{ .id = 16 });
+
+// // make sure the delete actually did something
+// try testing.expectError(error.SearchResultNotFound, ctx.processMessage(.{
+// .id = 17,
+// .method = "DOM.getSearchResults",
+// .params = .{ .searchId = "0", .fromIndex = 0, .toIndex = 1 },
+// }));
+// }
+
+// test "cdp.dom: querySelector unknown search id" {
+// var ctx = testing.context();
+// defer ctx.deinit();
+
+// _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .html = "1
2
" });
+
+// try ctx.processMessage(.{
+// .id = 9,
+// .method = "DOM.querySelector",
+// .params = .{ .nodeId = 99, .selector = "" },
+// });
+// try ctx.expectSentError(-32000, "Could not find node with given id", .{});
+
+// try ctx.processMessage(.{
+// .id = 9,
+// .method = "DOM.querySelectorAll",
+// .params = .{ .nodeId = 99, .selector = "" },
+// });
+// try ctx.expectSentError(-32000, "Could not find node with given id", .{});
+// }
+
+// test "cdp.dom: querySelector Node not found" {
+// var ctx = testing.context();
+// defer ctx.deinit();
+
+// _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .html = "1
2
" });
+
+// try ctx.processMessage(.{ // Hacky way to make sure nodeId 1 exists in the registry
+// .id = 3,
+// .method = "DOM.performSearch",
+// .params = .{ .query = "p" },
+// });
+// try ctx.expectSentResult(.{ .searchId = "0", .resultCount = 2 }, .{ .id = 3 });
+
+// try testing.expectError(error.NodeNotFoundForGivenId, ctx.processMessage(.{
+// .id = 4,
+// .method = "DOM.querySelector",
+// .params = .{ .nodeId = 1, .selector = "a" },
+// }));
+
+// try ctx.processMessage(.{
+// .id = 5,
+// .method = "DOM.querySelectorAll",
+// .params = .{ .nodeId = 1, .selector = "a" },
+// });
+// try ctx.expectSentResult(.{ .nodeIds = &[_]u32{} }, .{ .id = 5 });
+// }
+
+// test "cdp.dom: querySelector Nodes found" {
+// var ctx = testing.context();
+// defer ctx.deinit();
+
+// _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .html = "" });
+
+// try ctx.processMessage(.{ // Hacky way to make sure nodeId 1 exists in the registry
+// .id = 3,
+// .method = "DOM.performSearch",
+// .params = .{ .query = "div" },
+// });
+// try ctx.expectSentResult(.{ .searchId = "0", .resultCount = 1 }, .{ .id = 3 });
+
+// try ctx.processMessage(.{
+// .id = 4,
+// .method = "DOM.querySelector",
+// .params = .{ .nodeId = 1, .selector = "p" },
+// });
+// try ctx.expectSentEvent("DOM.setChildNodes", null, .{});
+// try ctx.expectSentResult(.{ .nodeId = 6 }, .{ .id = 4 });
+
+// try ctx.processMessage(.{
+// .id = 5,
+// .method = "DOM.querySelectorAll",
+// .params = .{ .nodeId = 1, .selector = "p" },
+// });
+// try ctx.expectSentEvent("DOM.setChildNodes", null, .{});
+// try ctx.expectSentResult(.{ .nodeIds = &.{6} }, .{ .id = 5 });
+// }
+
+// test "cdp.dom: getBoxModel" {
+// var ctx = testing.context();
+// defer ctx.deinit();
+
+// _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .html = "" });
+
+// try ctx.processMessage(.{ // Hacky way to make sure nodeId 1 exists in the registry
+// .id = 3,
+// .method = "DOM.getDocument",
+// });
+
+// try ctx.processMessage(.{
+// .id = 4,
+// .method = "DOM.querySelector",
+// .params = .{ .nodeId = 1, .selector = "p" },
+// });
+// try ctx.expectSentResult(.{ .nodeId = 3 }, .{ .id = 4 });
+
+// try ctx.processMessage(.{
+// .id = 5,
+// .method = "DOM.getBoxModel",
+// .params = .{ .nodeId = 6 },
+// });
+// try ctx.expectSentResult(.{ .model = BoxModel{
+// .content = Quad{ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 },
+// .padding = Quad{ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 },
+// .border = Quad{ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 },
+// .margin = Quad{ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 },
+// .width = 1,
+// .height = 1,
+// } }, .{ .id = 5 });
+// }
diff --git a/src/cdp/domains/fetch.zig b/src/cdp/domains/fetch.zig
index f6fb302b..ef11e15d 100644
--- a/src/cdp/domains/fetch.zig
+++ b/src/cdp/domains/fetch.zig
@@ -23,7 +23,7 @@ const log = @import("../../log.zig");
const network = @import("network.zig");
const Http = @import("../../http/Http.zig");
-const Notification = @import("../../notification.zig").Notification;
+const Notification = @import("../../Notification.zig");
pub fn processMessage(cmd: anytype) !void {
const action = std.meta.stringToEnum(enum {
diff --git a/src/cdp/domains/input.zig b/src/cdp/domains/input.zig
index d81fb1c8..b4f2990a 100644
--- a/src/cdp/domains/input.zig
+++ b/src/cdp/domains/input.zig
@@ -17,7 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
-const Page = @import("../../browser/page.zig").Page;
+const Page = @import("../../browser/Page.zig");
pub fn processMessage(cmd: anytype) !void {
const action = std.meta.stringToEnum(enum {
diff --git a/src/cdp/domains/log.zig b/src/cdp/domains/log.zig
index 368a7954..07d3c6d6 100644
--- a/src/cdp/domains/log.zig
+++ b/src/cdp/domains/log.zig
@@ -101,7 +101,7 @@ pub fn LogInterceptor(comptime BC: type) type {
.fatal => "error",
},
.text = self.allocating.written(),
- .timestamp = @import("../../datetime.zig").milliTimestamp(),
+ .timestamp = @import("../../datetime.zig").milliTimestamp(.monotonic),
},
}, .{
.session_id = self.bc.session_id,
diff --git a/src/cdp/domains/network.zig b/src/cdp/domains/network.zig
index 0d7014d0..c41d1988 100644
--- a/src/cdp/domains/network.zig
+++ b/src/cdp/domains/network.zig
@@ -21,7 +21,7 @@ const Allocator = std.mem.Allocator;
const CdpStorage = @import("storage.zig");
const Transfer = @import("../../http/Client.zig").Transfer;
-const Notification = @import("../../notification.zig").Notification;
+const Notification = @import("../../Notification.zig");
pub fn processMessage(cmd: anytype) !void {
const action = std.meta.stringToEnum(enum {
@@ -87,7 +87,7 @@ fn setExtraHTTPHeaders(cmd: anytype) !void {
return cmd.sendResult(null, .{});
}
-const Cookie = @import("../../browser/storage/storage.zig").Cookie;
+const Cookie = @import("../../browser/webapi/storage/storage.zig").Cookie;
// Only matches the cookie on provided parameters
fn cookieMatches(cookie: *const Cookie, name: []const u8, domain: ?[]const u8, path: ?[]const u8) bool {
@@ -173,7 +173,7 @@ fn getCookies(cmd: anytype) !void {
const params = (try cmd.params(GetCookiesParam)) orelse GetCookiesParam{};
// If not specified, use the URLs of the page and all of its subframes. TODO subframes
- const page_url = if (bc.session.page) |*page| page.url.raw else null; // @speed: avoid repasing the URL
+ const page_url = if (bc.session.page) |page| page.url else null;
const param_urls = params.urls orelse &[_][]const u8{page_url orelse return error.InvalidParams};
var urls = try std.ArrayListUnmanaged(CdpStorage.PreparedUri).initCapacity(cmd.arena, param_urls.len);
@@ -247,7 +247,7 @@ pub fn httpRequestStart(arena: Allocator, bc: anytype, msg: *const Notification.
.requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}),
.frameId = target_id,
.loaderId = bc.loader_id,
- .documentUrl = DocumentUrlWriter.init(&page.url.uri),
+ .documentUrl = page.url,
.request = TransferAsRequestWriter.init(transfer),
.initiator = .{ .type = "other" },
}, .{ .session_id = session_id });
@@ -416,34 +416,35 @@ const TransferAsResponseWriter = struct {
}
};
-const DocumentUrlWriter = struct {
- uri: *std.Uri,
+// @ZIGDOM - do we still need this? just send the full URL?
+// const DocumentUrlWriter = struct {
+// uri: *std.Uri,
- fn init(uri: *std.Uri) DocumentUrlWriter {
- return .{
- .uri = uri,
- };
- }
+// fn init(uri: *std.Uri) DocumentUrlWriter {
+// return .{
+// .uri = uri,
+// };
+// }
- pub fn jsonStringify(self: *const DocumentUrlWriter, jws: anytype) !void {
- self._jsonStringify(jws) catch return error.WriteFailed;
- }
- fn _jsonStringify(self: *const DocumentUrlWriter, jws: anytype) !void {
- const writer = jws.writer;
+// pub fn jsonStringify(self: *const DocumentUrlWriter, jws: anytype) !void {
+// self._jsonStringify(jws) catch return error.WriteFailed;
+// }
+// fn _jsonStringify(self: *const DocumentUrlWriter, jws: anytype) !void {
+// const writer = jws.writer;
- try jws.beginWriteRaw();
- try writer.writeByte('\"');
- try self.uri.writeToStream(writer, .{
- .scheme = true,
- .authentication = true,
- .authority = true,
- .path = true,
- .query = true,
- });
- try writer.writeByte('\"');
- jws.endWriteRaw();
- }
-};
+// try jws.beginWriteRaw();
+// try writer.writeByte('\"');
+// try self.uri.writeToStream(writer, .{
+// .scheme = true,
+// .authentication = true,
+// .authority = true,
+// .path = true,
+// .query = true,
+// });
+// try writer.writeByte('\"');
+// jws.endWriteRaw();
+// }
+// };
fn idFromRequestId(request_id: []const u8) !u64 {
if (!std.mem.startsWith(u8, request_id, "REQ-")) {
diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig
index 1f6b720a..7107d686 100644
--- a/src/cdp/domains/page.zig
+++ b/src/cdp/domains/page.zig
@@ -17,8 +17,8 @@
// along with this program. If not, see .
const std = @import("std");
-const Page = @import("../../browser/page.zig").Page;
-const Notification = @import("../../notification.zig").Notification;
+const Page = @import("../../browser/Page.zig");
+const Notification = @import("../../Notification.zig");
const Allocator = std.mem.Allocator;
@@ -134,7 +134,7 @@ fn createIsolatedWorld(cmd: anytype) !void {
fn navigate(cmd: anytype) !void {
const params = (try cmd.params(struct {
- url: []const u8,
+ url: [:0]const u8,
// referrer: ?[]const u8 = null,
// transitionType: ?[]const u8 = null, // TODO: enum
// frameId: ?[]const u8 = null,
@@ -253,7 +253,8 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
bc.inspector.contextCreated(
page.js,
"",
- try page.origin(arena),
+ "", // @ZIGDOM
+ // try page.origin(arena),
aux_data,
true,
);
@@ -360,7 +361,7 @@ pub fn pageNetworkAlmostIdle(bc: anytype, event: *const Notification.PageNetwork
return sendPageLifecycle(bc, "networkAlmostIdle", event.timestamp);
}
-fn sendPageLifecycle(bc: anytype, name: []const u8, timestamp: u32) !void {
+fn sendPageLifecycle(bc: anytype, name: []const u8, timestamp: u64) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
@@ -379,7 +380,7 @@ const LifecycleEvent = struct {
frameId: []const u8,
loaderId: ?[]const u8,
name: []const u8,
- timestamp: u32,
+ timestamp: u64,
};
const testing = @import("../testing.zig");
diff --git a/src/cdp/domains/storage.zig b/src/cdp/domains/storage.zig
index 662d079f..83547502 100644
--- a/src/cdp/domains/storage.zig
+++ b/src/cdp/domains/storage.zig
@@ -19,9 +19,9 @@
const std = @import("std");
const log = @import("../../log.zig");
-const Cookie = @import("../../browser/storage/storage.zig").Cookie;
-const CookieJar = @import("../../browser/storage/storage.zig").CookieJar;
-pub const PreparedUri = @import("../../browser/storage/cookie.zig").PreparedUri;
+const Cookie = @import("../../browser/webapi/storage/storage.zig").Cookie;
+const CookieJar = @import("../../browser/webapi/storage/storage.zig").Jar;
+pub const PreparedUri = @import("../../browser/webapi/storage/cookie.zig").PreparedUri;
pub fn processMessage(cmd: anytype) !void {
const action = std.meta.stringToEnum(enum {
diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig
index 26f4cfbe..3ea78b71 100644
--- a/src/cdp/domains/target.zig
+++ b/src/cdp/domains/target.zig
@@ -143,13 +143,14 @@ fn createTarget(cmd: anytype) !void {
bc.target_id = target_id;
- var page = try bc.session.createPage();
+ const page = try bc.session.createPage();
{
const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});
bc.inspector.contextCreated(
page.js,
"",
- try page.origin(cmd.arena),
+ "", // @ZIGDOM
+ // try page.origin(arena),
aux_data,
true,
);
diff --git a/src/cdp/testing.zig b/src/cdp/testing.zig
index 0c052d12..7c086f6f 100644
--- a/src/cdp/testing.zig
+++ b/src/cdp/testing.zig
@@ -24,7 +24,6 @@ const ArenaAllocator = std.heap.ArenaAllocator;
const Testing = @This();
const main = @import("cdp.zig");
-const parser = @import("../browser/netsurf.zig");
const base = @import("../testing.zig");
pub const allocator = base.allocator;
diff --git a/src/http/Client.zig b/src/http/Client.zig
index fe0a5a1f..65f31066 100644
--- a/src/http/Client.zig
+++ b/src/http/Client.zig
@@ -176,7 +176,7 @@ pub fn abort(self: *Client) void {
}
}
-pub fn tick(self: *Client, timeout_ms: i32) !PerformStatus {
+pub fn tick(self: *Client, timeout_ms: u32) !PerformStatus {
while (true) {
if (self.handles.hasAvailable() == false) {
break;
@@ -188,7 +188,7 @@ pub fn tick(self: *Client, timeout_ms: i32) !PerformStatus {
const handle = self.handles.getFreeHandle().?;
try self.makeRequest(handle, transfer);
}
- return self.perform(timeout_ms);
+ return self.perform(@intCast(timeout_ms));
}
pub fn request(self: *Client, req: Request) !void {
diff --git a/src/http/Http.zig b/src/http/Http.zig
index 17b481d0..e5be87ee 100644
--- a/src/http/Http.zig
+++ b/src/http/Http.zig
@@ -83,7 +83,7 @@ pub fn deinit(self: *Http) void {
self.arena.deinit();
}
-pub fn poll(self: *Http, timeout_ms: i32) Client.PerformStatus {
+pub fn poll(self: *Http, timeout_ms: u32) Client.PerformStatus {
return self.client.tick(timeout_ms) catch |err| {
log.err(.app, "http poll", .{ .err = err });
return .normal;
diff --git a/src/lightpanda.zig b/src/lightpanda.zig
index 54e42573..f037ce3e 100644
--- a/src/lightpanda.zig
+++ b/src/lightpanda.zig
@@ -1,5 +1,7 @@
const std = @import("std");
pub const App = @import("App.zig");
+pub const Server = @import("Server.zig");
+
pub const log = @import("log.zig");
pub const dump = @import("browser/dump.zig");
pub const build_config = @import("build_config");
diff --git a/src/main.zig b/src/main.zig
index 6c90196d..1da7af4b 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -99,27 +99,24 @@ fn run(allocator: Allocator, main_arena: Allocator) !void {
app.telemetry.record(.{ .run = {} });
switch (args.mode) {
- .serve => {
- return;
- // @ZIGDOM-CDP
- // .serve => |opts| {
- // log.debug(.app, "startup", .{ .mode = "serve" });
- // const address = std.net.Address.parseIp4(opts.host, opts.port) catch |err| {
- // log.fatal(.app, "invalid server address", .{ .err = err, .host = opts.host, .port = opts.port });
- // return args.printUsageAndExit(false);
- // };
+ .serve => |opts| {
+ log.debug(.app, "startup", .{ .mode = "serve" });
+ const address = std.net.Address.parseIp4(opts.host, opts.port) catch |err| {
+ log.fatal(.app, "invalid server address", .{ .err = err, .host = opts.host, .port = opts.port });
+ return args.printUsageAndExit(false);
+ };
- // // _server is global to handle graceful shutdown.
- // _server = try lp.Server.init(app, address);
- // const server = &_server.?;
- // defer server.deinit();
+ // _server is global to handle graceful shutdown.
+ _server = try lp.Server.init(app, address);
+ const server = &_server.?;
+ defer server.deinit();
- // // max timeout of 1 week.
- // const timeout = if (opts.timeout > 604_800) 604_800_000 else @as(i32, opts.timeout) * 1000;
- // server.run(address, timeout) catch |err| {
- // log.fatal(.app, "server run error", .{ .err = err });
- // return err;
- // };
+ // max timeout of 1 week.
+ const timeout = if (opts.timeout > 604_800) 604_800_000 else @as(u32, opts.timeout) * 1000;
+ server.run(address, timeout) catch |err| {
+ log.fatal(.app, "server run error", .{ .err = err });
+ return err;
+ };
},
.fetch => |opts| {
const url = opts.url;
diff --git a/src/server.zig b/src/server.zig
index afb55e43..4d42f001 100644
--- a/src/server.zig
+++ b/src/server.zig
@@ -26,7 +26,7 @@ const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const log = @import("log.zig");
-const App = @import("app.zig").App;
+const App = @import("App.zig");
const CDP = @import("cdp/cdp.zig").CDP;
const MAX_HTTP_REQUEST_SIZE = 4096;
@@ -69,7 +69,7 @@ pub fn deinit(self: *Server) void {
self.allocator.free(self.json_version_response);
}
-pub fn run(self: *Server, address: net.Address, timeout_ms: i32) !void {
+pub fn run(self: *Server, address: net.Address, timeout_ms: u32) !void {
const flags = posix.SOCK.STREAM | posix.SOCK.CLOEXEC;
const listener = try posix.socket(address.any.family, flags, posix.IPPROTO.TCP);
self.listener = listener;
@@ -112,7 +112,7 @@ pub fn run(self: *Server, address: net.Address, timeout_ms: i32) !void {
}
}
-fn readLoop(self: *Server, socket: posix.socket_t, timeout_ms: i32) !void {
+fn readLoop(self: *Server, socket: posix.socket_t, timeout_ms: u32) !void {
// This shouldn't be necessary, but the Client is HUGE (> 512KB) because
// it has a large read buffer. I don't know why, but v8 crashes if this
// is on the stack (and I assume it's related to its size).
@@ -143,7 +143,7 @@ fn readLoop(self: *Server, socket: posix.socket_t, timeout_ms: i32) !void {
}
var cdp = &client.mode.cdp;
- var last_message = timestamp();
+ var last_message = timestamp(.monotonic);
var ms_remaining = timeout_ms;
while (true) {
switch (cdp.pageWait(ms_remaining)) {
@@ -151,7 +151,7 @@ fn readLoop(self: *Server, socket: posix.socket_t, timeout_ms: i32) !void {
if (try client.readSocket() == false) {
return;
}
- last_message = timestamp();
+ last_message = timestamp(.monotonic);
ms_remaining = timeout_ms;
},
.no_page => {
@@ -162,16 +162,16 @@ fn readLoop(self: *Server, socket: posix.socket_t, timeout_ms: i32) !void {
if (try client.readSocket() == false) {
return;
}
- last_message = timestamp();
+ last_message = timestamp(.monotonic);
ms_remaining = timeout_ms;
},
.done => {
- const elapsed = timestamp() - last_message;
+ const elapsed = timestamp(.monotonic) - last_message;
if (elapsed > ms_remaining) {
log.info(.app, "CDP timeout", .{});
return;
}
- ms_remaining -= @as(i32, @intCast(elapsed));
+ ms_remaining -= @intCast(elapsed);
},
}
}
@@ -928,9 +928,7 @@ fn buildJSONVersionResponse(
return try std.fmt.allocPrint(allocator, response_format, .{ body_len, address });
}
-fn timestamp() u32 {
- return @import("datetime.zig").timestamp();
-}
+pub const timestamp = @import("datetime.zig").timestamp;
// In-place string lowercase
fn toLower(str: []u8) []u8 {
diff --git a/src/telemetry/lightpanda.zig b/src/telemetry/lightpanda.zig
index 621f4742..cd87bf8e 100644
--- a/src/telemetry/lightpanda.zig
+++ b/src/telemetry/lightpanda.zig
@@ -6,7 +6,7 @@ const Thread = std.Thread;
const Allocator = std.mem.Allocator;
const log = @import("../log.zig");
-const App = @import("../app.zig").App;
+const App = @import("../App.zig");
const Http = @import("../http/Http.zig");
const telemetry = @import("telemetry.zig");