Merge pull request #3558 from lightpanda-io/cdp-frame-owner

cdp: DOM.getFrameOwner returns the owner <iframe>, nodes carry frameId
This commit is contained in:
Karl Seguin authored and GitHub committed 2026-09-18 16:45:04 +08:00
commit ce409c244f
2 files changed
+73 -5

No files matched your search

+25 -4
View File
@@ -22,8 +22,10 @@
const std = @import("std");
const lp = @import("lightpanda");
const id = @import("id.zig");
const Frame = @import("../../browser/Frame.zig");
const DOMNode = @import("../../browser/webapi/Node.zig");
const IFrame = @import("../../browser/webapi/element/html/IFrame.zig");
const NodeRegistry = @import("../../NodeRegistry.zig");
const log = lp.log;
@@ -67,12 +69,12 @@ pub const Search = struct {
}
pub fn create(self: *List, nodes: []const *DOMNode) !Search {
const id = self.search_id;
defer self.search_id = id +% 1;
const search_id = self.search_id;
defer self.search_id = search_id +% 1;
const arena = self.arena.allocator();
const name = switch (id) {
const name = switch (search_id) {
0 => "0",
1 => "1",
2 => "2",
@@ -83,7 +85,7 @@ pub const Search = struct {
7 => "7",
8 => "8",
9 => "9",
else => try std.fmt.allocPrint(arena, "{d}", .{id}),
else => try std.fmt.allocPrint(arena, "{d}", .{search_id}),
};
var registry = self.registry;
@@ -217,6 +219,25 @@ pub const Writer = struct {
try w.objectField("localName");
try w.write(element.getLocalName());
// Chrome names the hosted frame on frame-owner elements and the own
// frame on the document element; that's how clients pair an <iframe>
// with its Page.frameAttached id.
if (element.is(IFrame)) |iframe| {
if (iframe.getContentDocument()) |document| {
if (document._frame) |child| {
try w.objectField("frameId");
try w.write(&id.toFrameId(child._frame_id));
}
}
} else if (dom_node._parent) |dom_parent| {
if (dom_parent._type == .document) {
if (dom_parent.subtype(DOMNode.Document)._frame) |frame| {
try w.objectField("frameId");
try w.write(&id.toFrameId(frame._frame_id));
}
}
}
} else {
try w.objectField("localName");
try w.write("");
+48 -1
View File
@@ -610,7 +610,14 @@ fn getFrameOwner(cmd: *CDP.Command) !void {
return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
};
const node = try bc.node_registry.register(frame.window._document.asNode());
// The element hosting the frame, as in Chrome; the main frame has none.
// Clients (Stagehand's frameLocator, Playwright's contentFrame) match this
// backendNodeId against the <iframe> they resolved in the parent.
const iframe = frame.iframe orelse {
return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
};
const node = try bc.node_registry.register(iframe.asNode());
return cmd.sendResult(.{ .nodeId = node.id, .backendNodeId = node.id }, .{});
}
@@ -1313,6 +1320,46 @@ test "cdp.dom: resolveNode into a child frame's context" {
try ctx.expectSentError(-31998, "ContextNotFound", .{ .id = 15 });
}
test "cdp.dom: getFrameOwner returns the owner iframe element" {
var ctx = try testing.context();
defer ctx.deinit();
const bc = try ctx.loadBrowserContext(.{ .id = "BID-FO", .url = "cdp/isolated_world.html", .target_id = "FID-000000000X".* });
const root = bc.mainFrame() orelse unreachable;
const child = root.child_frames.items[0];
const iframe_node = try bc.node_registry.register(child.iframe.?.asNode());
try ctx.processMessage(.{ .id = 10, .method = "DOM.getFrameOwner", .sessionId = "SID-X", .params = .{
.frameId = &id.toFrameId(child._frame_id),
} });
try ctx.expectSentResult(.{ .nodeId = iframe_node.id, .backendNodeId = iframe_node.id }, .{ .id = 10 });
// The owner element names the frame it hosts.
try ctx.processMessage(.{ .id = 11, .method = "DOM.describeNode", .sessionId = "SID-X", .params = .{
.backendNodeId = iframe_node.id,
} });
try ctx.expectSentResult(.{ .node = .{
.localName = "iframe",
.frameId = &id.toFrameId(child._frame_id),
} }, .{ .id = 11 });
// So does the document element, for its own frame.
const child_html = try bc.node_registry.register(child.window._document.getDocumentElement().?.asNode());
try ctx.processMessage(.{ .id = 12, .method = "DOM.describeNode", .sessionId = "SID-X", .params = .{
.backendNodeId = child_html.id,
} });
try ctx.expectSentResult(.{ .node = .{
.localName = "html",
.frameId = &id.toFrameId(child._frame_id),
} }, .{ .id = 12 });
// The main frame has no owner.
try ctx.processMessage(.{ .id = 13, .method = "DOM.getFrameOwner", .sessionId = "SID-X", .params = .{
.frameId = &id.toFrameId(root._frame_id),
} });
try ctx.expectSentError(-32000, "Frame with the given id does not belong to the target.", .{ .id = 13 });
}
fn mainWorldContextId(bc: *CDP.BrowserContext, frame: *const Frame) !i32 {
var ls: js.Local.Scope = undefined;
frame.js.localScope(&ls);