diff --git a/src/browser/js/Inspector.zig b/src/browser/js/Inspector.zig
index f04409e8..04a8c5c8 100644
--- a/src/browser/js/Inspector.zig
+++ b/src/browser/js/Inspector.zig
@@ -109,7 +109,7 @@ pub fn getRemoteObject(
group: []const u8,
value: anytype,
) !RemoteObject {
- const js_value = try context.zigValueToJs(value);
+ const js_value = try context.zigValueToJs(value, .{});
// We do not want to expose this as a parameter for now
const generate_preview = false;
@@ -127,8 +127,10 @@ pub fn getNodePtr(self: *const Inspector, allocator: Allocator, object_id: []con
const unwrapped = try self.session.unwrapObject(allocator, object_id);
// The values context and groupId are not used here
const toa = getTaggedAnyOpaque(unwrapped.value) orelse return null;
- if (toa.subtype == null or toa.subtype != .node) return error.ObjectIdIsNotANode;
- return toa.ptr;
+ if (toa.subtype == null or toa.subtype != .node) {
+ return error.ObjectIdIsNotANode;
+ }
+ return toa.value;
}
const NoopInspector = struct {
diff --git a/src/browser/tests/cdp/dom1.html b/src/browser/tests/cdp/dom1.html
new file mode 100644
index 00000000..db532cc5
--- /dev/null
+++ b/src/browser/tests/cdp/dom1.html
@@ -0,0 +1 @@
+
1
2
diff --git a/src/browser/tests/cdp/dom2.html b/src/browser/tests/cdp/dom2.html
new file mode 100644
index 00000000..c9b3acb3
--- /dev/null
+++ b/src/browser/tests/cdp/dom2.html
@@ -0,0 +1 @@
+
diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig
index cd6b003e..8022cea8 100644
--- a/src/browser/webapi/Element.zig
+++ b/src/browser/webapi/Element.zig
@@ -27,7 +27,6 @@ const reflect = @import("../reflect.zig");
const Node = @import("Node.zig");
const CSS = @import("CSS.zig");
-const DOMRect = @import("DOMRect.zig");
const ShadowRoot = @import("ShadowRoot.zig");
const collections = @import("collections.zig");
const Selector = @import("selector/Selector.zig");
@@ -35,6 +34,7 @@ const Animation = @import("animation/Animation.zig");
const DOMStringMap = @import("element/DOMStringMap.zig");
const CSSStyleProperties = @import("css/CSSStyleProperties.zig");
+pub const DOMRect = @import("DOMRect.zig");
pub const Svg = @import("element/Svg.zig");
pub const Html = @import("element/Html.zig");
pub const Attribute = @import("element/Attribute.zig");
diff --git a/src/cdp/domains/dom.zig b/src/cdp/domains/dom.zig
index e99fd6b6..791f9458 100644
--- a/src/cdp/domains/dom.zig
+++ b/src/cdp/domains/dom.zig
@@ -18,657 +18,642 @@
const std = @import("std");
const log = @import("../../log.zig");
+const Node = @import("../Node.zig");
+const DOMNode = @import("../../browser/webapi/Node.zig");
+const Selector = @import("../../browser/webapi/selector/Selector.zig");
+
const Allocator = std.mem.Allocator;
-// 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,
- // ZIGDOM
- // getDocument,
- // performSearch,
- // getSearchResults,
- // discardSearchResults,
- // querySelector,
- // querySelectorAll,
- // resolveNode,
- // describeNode,
- // scrollIntoViewIfNeeded,
- // getContentQuads,
- // getBoxModel,
- // requestChildNodes,
- // getFrameOwner,
+ 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, .{}),
- // @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),
+ .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
-// // 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 });
-// }
+// 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 node = try bc.node_registry.register(page.window._document.asNode());
+ 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 list = try Selector.querySelectorAll(page.window._document.asNode(), params.query, page);
+ const search = try bc.node_search_list.create(list._nodes);
+
+ // dispatch setChildNodesEvents to inform the client of the subpart of node
+ // tree covering the results.
+ try dispatchSetChildNodes(cmd, list._nodes);
+
+ 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, dom_nodes: []const *DOMNode) !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.ArrayList(*Node) = .empty;
+ for (dom_nodes) |dom_node| {
+ var current = dom_node;
+ while (true) {
+ const parent_node = current._parent orelse break;
+
+ const node = try bc.node_registry.register(parent_node);
+ if (node.set_child_nodes_event) {
+ break;
+ }
+ try parents.append(arena, node);
+ current = parent_node;
+ }
+ }
+
+ 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 dom_parent = node.dom._parent orelse continue;
+
+ // Retrieve the parent from the registry.
+ const parent_node = try bc.node_registry.register(dom_parent);
+
+ 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 page = bc.session.currentPage() orelse return error.PageNotLoaded;
+
+ const node = bc.node_registry.lookup_by_id.get(params.nodeId) orelse {
+ return cmd.sendError(-32000, "Could not find node with given id", .{});
+ };
+
+ const element = try Selector.querySelector(node.dom, params.selector, page) orelse return error.NodeNotFoundForGivenId;
+ const dom_node = element.asNode();
+ const registered_node = try bc.node_registry.register(dom_node);
+
+ // Dispatch setChildNodesEvents to inform the client of the subpart of node tree covering the results.
+ var array = [1]*DOMNode{dom_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 page = bc.session.currentPage() orelse return error.PageNotLoaded;
+
+ 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_nodes = try Selector.querySelectorAll(node.dom, params.selector, page);
+ const nodes = selected_nodes._nodes;
+
+ const node_ids = try cmd.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 *DOMNode 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 "",
+ node.dom,
+ );
+ 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: *const DOMNode.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: ?DOMNode.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);
+
+ switch (node.dom._type) {
+ .element => {},
+ .document => {},
+ .cdata => {},
+ 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
+
+ const element = node.dom.is(DOMNode.Element) orelse 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 rect = try element.getBoundingClientRect(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
+ const element = node.dom.is(DOMNode.Element) orelse return error.NodeIsNotAnElement;
+
+ const rect = try element.getBoundingClientRect(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 node = try bc.node_registry.register(page.window._document.asNode());
+ 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", .url = "cdp/dom1.html" });
+
+ 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", .url = "cdp/dom1.html" });
+
+ 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", .url = "cdp/dom1.html" });
+
+ 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", .url = "cdp/dom2.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 = 7 }, .{ .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 = &.{7} }, .{ .id = 5 });
+}
+
+test "cdp.dom: getBoxModel" {
+ var ctx = testing.context();
+ defer ctx.deinit();
+
+ _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom2.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/testing.zig b/src/cdp/testing.zig
index cefc8823..8d459a9f 100644
--- a/src/cdp/testing.zig
+++ b/src/cdp/testing.zig
@@ -93,7 +93,7 @@ const TestContext = struct {
id: ?[]const u8 = null,
target_id: ?[]const u8 = null,
session_id: ?[]const u8 = null,
- html: ?[]const u8 = null,
+ url: ?[:0]const u8 = null,
};
pub fn loadBrowserContext(self: *TestContext, opts: BrowserContextOpts) !*main.BrowserContext(TestCDP) {
var c = self.cdp();
@@ -116,12 +116,23 @@ const TestContext = struct {
bc.session_id = sid;
}
- // @ZIGDOM
- // if (opts.html) |html| {
- // if (bc.session_id == null) bc.session_id = "SID-X";
- // const page = try bc.session.createPage();
- // page.window._document = (try Document.init(html)).doc;
- // }
+ if (opts.url) |url| {
+ if (bc.session_id == null) {
+ bc.session_id = "SID-X";
+ }
+ if (bc.target_id == null) {
+ bc.target_id = "TID-X";
+ }
+ const page = try bc.session.createPage();
+ const full_url = try std.fmt.allocPrintSentinel(
+ self.arena.allocator(),
+ "http://127.0.0.1:9582/src/browser/tests/{s}",
+ .{ url },
+ 0,
+ );
+ try page.navigate(full_url, .{});
+ bc.session.fetchWait(2000);
+ }
return bc;
}
diff --git a/src/testing.zig b/src/testing.zig
index b250dc20..452ce812 100644
--- a/src/testing.zig
+++ b/src/testing.zig
@@ -430,10 +430,6 @@ pub fn pageTest(comptime test_file: []const u8) !*Page {
try page.navigate(url, .{});
test_session.fetchWait(2000);
-
- page._session.browser.runMicrotasks();
- page._session.browser.runMessageLoop();
-
return page;
}