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 @@ +

2

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 = "

2

" }); - -// 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 = "

2

" }); - -// 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; }