Add a dumb renderer to get coordinates

FlatRenderer positions items on a single row, giving each a height and width of
1.

Added getBoundingClientRect to the DOMelement which, when requested for the
first time, will place the item in with the renderer.

The goal here is to give elements a fixed position and to make it easy to map
x,y coordinates onto an element. This should work, at least with puppeteer,
since it first requests the boundingClientRect before issuing a click.
This commit is contained in:
Karl Seguin
2025-04-01 17:51:33 +08:00
parent 647575261e
commit 0253de80de
10 changed files with 285 additions and 23 deletions

View File

@@ -37,6 +37,7 @@ pub const CDP = CDPT(struct {
const SessionIdGen = Incrementing(u32, "SID");
const TargetIdGen = Incrementing(u32, "TID");
const LoaderIdGen = Incrementing(u32, "LID");
const BrowserContextIdGen = Incrementing(u32, "BID");
// Generic so that we can inject mocks into it.
@@ -54,6 +55,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
target_auto_attach: bool = false,
target_id_gen: TargetIdGen = .{},
loader_id_gen: LoaderIdGen = .{},
session_id_gen: SessionIdGen = .{},
browser_context_id_gen: BrowserContextIdGen = .{},
@@ -183,6 +185,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
},
5 => switch (@as(u40, @bitCast(domain[0..5].*))) {
asUint("Fetch") => return @import("domains/fetch.zig").processMessage(command),
asUint("Input") => return @import("domains/input.zig").processMessage(command),
else => {},
},
6 => switch (@as(u48, @bitCast(domain[0..6].*))) {

80
src/cdp/domains/input.zig Normal file
View File

@@ -0,0 +1,80 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
pub fn processMessage(cmd: anytype) !void {
const action = std.meta.stringToEnum(enum {
dispatchMouseEvent,
}, cmd.input.action) orelse return error.UnknownMethod;
switch (action) {
.dispatchMouseEvent => return dispatchMouseEvent(cmd),
}
}
// https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-dispatchMouseEvent
fn dispatchMouseEvent(cmd: anytype) !void {
const params = (try cmd.params(struct {
type: []const u8,
x: u32,
y: u32,
})) orelse return error.InvalidParams;
try cmd.sendResult(null, .{});
if (std.ascii.eqlIgnoreCase(params.type, "mousePressed") == false) {
return;
}
const bc = cmd.browser_context orelse return;
const page = bc.session.currentPage() orelse return;
const click_result = (try page.click(cmd.arena, params.x, params.y)) orelse return;
switch (click_result) {
.navigate => |uri| try clickNavigate(cmd, uri),
}
// result already sent
}
fn clickNavigate(cmd: anytype, uri: std.Uri) !void {
const bc = cmd.browser_context.?;
var url_buf: std.ArrayListUnmanaged(u8) = .{};
try uri.writeToStream(.{
.scheme = true,
.authentication = true,
.authority = true,
.port = true,
.path = true,
.query = true,
}, url_buf.writer(cmd.arena));
const url = url_buf.items;
try cmd.sendEvent("Page.frameRequestedNavigation", .{
.url = url,
.frameId = bc.target_id.?,
.reason = "anchorClick",
.disposition = "currentTab",
}, .{ .session_id = bc.session_id.? });
bc.session.removePage();
_ = try bc.session.createPage(null);
try @import("page.zig").navigateToUrl(cmd, url, false);
}

View File

@@ -129,6 +129,18 @@ fn createIsolatedWorld(cmd: anytype) !void {
}
fn navigate(cmd: anytype) !void {
const params = (try cmd.params(struct {
url: []const u8,
// referrer: ?[]const u8 = null,
// transitionType: ?[]const u8 = null, // TODO: enum
// frameId: ?[]const u8 = null,
// referrerPolicy: ?[]const u8 = null, // TODO: enum
})) orelse return error.InvalidParams;
return navigateToUrl(cmd, params.url, true);
}
pub fn navigateToUrl(cmd: anytype, url: []const u8, send_result: bool) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
// didn't create?
@@ -140,20 +152,10 @@ fn navigate(cmd: anytype) !void {
// if we have a target_id we have to have a page;
std.debug.assert(bc.session.page != null);
const params = (try cmd.params(struct {
url: []const u8,
referrer: ?[]const u8 = null,
transitionType: ?[]const u8 = null, // TODO: enum
frameId: ?[]const u8 = null,
referrerPolicy: ?[]const u8 = null, // TODO: enum
})) orelse return error.InvalidParams;
// change state
bc.reset();
bc.url = params.url;
// TODO: hard coded ID
bc.loader_id = "AF8667A203C5392DBE9AC290044AA4C2";
bc.url = url;
bc.loader_id = cmd.cdp.loader_id_gen.next();
const LifecycleEvent = struct {
frameId: []const u8,
@@ -180,10 +182,12 @@ fn navigate(cmd: anytype) !void {
}
// output
try cmd.sendResult(.{
.frameId = target_id,
.loaderId = bc.loader_id,
}, .{});
if (send_result) {
try cmd.sendResult(.{
.frameId = target_id,
.loaderId = bc.loader_id,
}, .{});
}
// TODO: at this point do we need async the following actions to be async?
@@ -199,7 +203,7 @@ fn navigate(cmd: anytype) !void {
);
var page = bc.session.currentPage().?;
try page.navigate(params.url, aux_data);
try page.navigate(url, aux_data);
// Events
@@ -218,7 +222,7 @@ fn navigate(cmd: anytype) !void {
.type = "Navigation",
.frame = Frame{
.id = target_id,
.url = bc.url,
.url = url,
.securityOrigin = bc.security_origin,
.secureContextType = bc.secure_context_type,
.loaderId = bc.loader_id,

View File

@@ -106,11 +106,17 @@ const Page = struct {
aux_data: []const u8 = "",
doc: ?*parser.Document = null,
pub fn navigate(self: *Page, url: []const u8, aux_data: []const u8) !void {
_ = self;
pub fn navigate(_: *Page, url: []const u8, aux_data: []const u8) !void {
_ = url;
_ = aux_data;
}
const ClickResult = @import("../browser/browser.zig").Page.ClickResult;
pub fn click(_: *Page, _: Allocator, x: u32, y: u32) !?ClickResult {
_ = x;
_ = y;
return null;
}
};
const Client = struct {