mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
Merge pull request #2657 from rohitsux/feat/cdp-mouse-button-clickcount
feat(cdp): support mouse button and clickCount in Input.dispatchMouseEvent
This commit is contained in:
@@ -3902,25 +3902,45 @@ fn findFrameByName(frame: *Frame, name: []const u8) ?*Frame {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn triggerMouseClick(self: *Frame, x: f64, y: f64) !void {
|
||||
const target = (try self.window._document.elementFromPoint(x, y, self)) orelse return;
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.frame, "frame mouse click", .{
|
||||
.url = self.url,
|
||||
.node = target,
|
||||
.x = x,
|
||||
.y = y,
|
||||
.type = self._type,
|
||||
});
|
||||
}
|
||||
const mouse_event: *MouseEvent = try .initTrusted(comptime .wrap("click"), .{
|
||||
// DOM MouseEvent.button values.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
|
||||
pub const mouse_button = struct {
|
||||
pub const main: i32 = 0; // left
|
||||
pub const auxiliary: i32 = 1; // middle
|
||||
pub const secondary: i32 = 2; // right
|
||||
pub const fourth: i32 = 3; // back
|
||||
pub const fifth: i32 = 4; // forward
|
||||
};
|
||||
|
||||
// Dispatch a single trusted mouse event of the given type on `target`, carrying
|
||||
// the pressed button and pointer position. `detail` is the click count (used for
|
||||
// click/dblclick); 0 for events where it does not apply.
|
||||
fn dispatchMouseEventOn(self: *Frame, target: *Element, comptime typ: []const u8, x: f64, y: f64, button: i32, detail: u32) !void {
|
||||
const event: *MouseEvent = try .initTrusted(comptime .wrap(typ), .{
|
||||
.bubbles = true,
|
||||
.cancelable = true,
|
||||
.composed = true,
|
||||
.clientX = x,
|
||||
.clientY = y,
|
||||
.button = button,
|
||||
.detail = detail,
|
||||
}, self);
|
||||
try self._event_manager.dispatch(target.asEventTarget(), mouse_event.asEvent());
|
||||
try self._event_manager.dispatch(target.asEventTarget(), event.asEvent());
|
||||
}
|
||||
|
||||
pub fn triggerMousePress(self: *Frame, x: f64, y: f64, button: i32) !void {
|
||||
const target = (try self.window._document.elementFromPoint(x, y, self)) orelse return;
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.frame, "frame mouse press", .{
|
||||
.url = self.url,
|
||||
.node = target,
|
||||
.x = x,
|
||||
.y = y,
|
||||
.button = button,
|
||||
.type = self._type,
|
||||
});
|
||||
}
|
||||
try self.dispatchMouseEventOn(target, "mousedown", x, y, button, 0);
|
||||
}
|
||||
|
||||
pub fn triggerMouseMove(self: *Frame, x: f64, y: f64) !void {
|
||||
@@ -3961,7 +3981,7 @@ pub fn triggerMouseMove(self: *Frame, x: f64, y: f64) !void {
|
||||
try self._event_manager.dispatch(target.asEventTarget(), enter_event.asEvent());
|
||||
}
|
||||
|
||||
pub fn triggerMouseRelease(self: *Frame, x: f64, y: f64) !void {
|
||||
pub fn triggerMouseRelease(self: *Frame, x: f64, y: f64, button: i32, click_count: i32) !void {
|
||||
const target = (try self.window._document.elementFromPoint(x, y, self)) orelse return;
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.frame, "frame mouse release", .{
|
||||
@@ -3969,17 +3989,28 @@ pub fn triggerMouseRelease(self: *Frame, x: f64, y: f64) !void {
|
||||
.node = target,
|
||||
.x = x,
|
||||
.y = y,
|
||||
.button = button,
|
||||
.type = self._type,
|
||||
});
|
||||
}
|
||||
const up_event: *MouseEvent = try .initTrusted(comptime .wrap("mouseup"), .{
|
||||
.bubbles = true,
|
||||
.cancelable = true,
|
||||
.composed = true,
|
||||
.clientX = x,
|
||||
.clientY = y,
|
||||
}, self);
|
||||
try self._event_manager.dispatch(target.asEventTarget(), up_event.asEvent());
|
||||
|
||||
const detail: u32 = if (click_count > 0) @intCast(click_count) else 1;
|
||||
|
||||
try self.dispatchMouseEventOn(target, "mouseup", x, y, button, detail);
|
||||
|
||||
// After mouseup, the activation event depends on the button.
|
||||
switch (button) {
|
||||
mouse_button.main => {
|
||||
try self.dispatchMouseEventOn(target, "click", x, y, button, detail);
|
||||
// A second click in quick succession also fires dblclick.
|
||||
if (click_count == 2) {
|
||||
try self.dispatchMouseEventOn(target, "dblclick", x, y, button, detail);
|
||||
}
|
||||
},
|
||||
mouse_button.auxiliary => try self.dispatchMouseEventOn(target, "auxclick", x, y, button, detail),
|
||||
mouse_button.secondary => try self.dispatchMouseEventOn(target, "contextmenu", x, y, button, detail),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn triggerMouseWheel(self: *Frame, x: f64, y: f64, delta_x: f64, delta_y: f64) !void {
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
const std = @import("std");
|
||||
const CDP = @import("../CDP.zig");
|
||||
|
||||
const dom_button = @import("../../browser/Frame.zig").mouse_button;
|
||||
|
||||
pub fn processMessage(cmd: *CDP.Command) !void {
|
||||
const action = std.meta.stringToEnum(enum {
|
||||
dispatchKeyEvent,
|
||||
@@ -82,6 +84,8 @@ fn dispatchMouseEvent(cmd: *CDP.Command) !void {
|
||||
x: f64,
|
||||
y: f64,
|
||||
type: Type,
|
||||
button: Button = .none,
|
||||
clickCount: i32 = 0,
|
||||
deltaX: f64 = 0,
|
||||
deltaY: f64 = 0,
|
||||
// Many optional parameters are not implemented yet, see documentation url.
|
||||
@@ -92,15 +96,36 @@ fn dispatchMouseEvent(cmd: *CDP.Command) !void {
|
||||
mouseMoved,
|
||||
mouseWheel,
|
||||
};
|
||||
|
||||
// https://chromedevtools.github.io/devtools-protocol/tot/Input/#type-MouseButton
|
||||
const Button = enum {
|
||||
none,
|
||||
left,
|
||||
middle,
|
||||
right,
|
||||
back,
|
||||
forward,
|
||||
};
|
||||
})) orelse return error.InvalidParams;
|
||||
|
||||
try cmd.sendResult(null, .{});
|
||||
|
||||
const bc = cmd.browser_context orelse return;
|
||||
const frame = bc.session.currentFrame() orelse return;
|
||||
|
||||
// Map the CDP button name to the DOM MouseEvent.button value.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
|
||||
const button: i32 = switch (params.button) {
|
||||
.none, .left => dom_button.main,
|
||||
.middle => dom_button.auxiliary,
|
||||
.right => dom_button.secondary,
|
||||
.back => dom_button.fourth,
|
||||
.forward => dom_button.fifth,
|
||||
};
|
||||
|
||||
switch (params.type) {
|
||||
.mousePressed => try frame.triggerMouseClick(params.x, params.y),
|
||||
.mouseReleased => try frame.triggerMouseRelease(params.x, params.y),
|
||||
.mousePressed => try frame.triggerMousePress(params.x, params.y, button),
|
||||
.mouseReleased => try frame.triggerMouseRelease(params.x, params.y, button, params.clickCount),
|
||||
.mouseMoved => try frame.triggerMouseMove(params.x, params.y),
|
||||
.mouseWheel => try frame.triggerMouseWheel(params.x, params.y, params.deltaX, params.deltaY),
|
||||
}
|
||||
@@ -237,3 +262,55 @@ test "cdp.input: dispatchMouseEvent mouseWheel fires wheel event" {
|
||||
const result = try ls.local.compileAndRun("window.wheelDeltaY === 40", null);
|
||||
try testing.expect(result.isTrue());
|
||||
}
|
||||
|
||||
test "cdp.input: dispatchMouseEvent right button fires contextmenu, double-click fires dblclick" {
|
||||
var ctx = try testing.context();
|
||||
defer ctx.deinit();
|
||||
|
||||
const bc = try ctx.loadBrowserContext(.{});
|
||||
const frame = try bc.session.createPage();
|
||||
const url = "http://localhost:9582/src/browser/tests/mcp_actions.html";
|
||||
try frame.navigate(url, .{ .reason = .address_bar, .kind = .{ .push = null } });
|
||||
var runner = try bc.session.runner(.{});
|
||||
try runner.wait(.{ .ms = 2000 });
|
||||
|
||||
var ls: lp.js.Local.Scope = undefined;
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
var try_catch: lp.js.TryCatch = undefined;
|
||||
try_catch.init(&ls.local);
|
||||
defer try_catch.deinit();
|
||||
|
||||
_ = try ls.local.compileAndRun(
|
||||
\\const t = document.getElementById('hoverTarget');
|
||||
\\t.addEventListener('mousedown', (e) => { window.downButton = e.button; });
|
||||
\\t.addEventListener('contextmenu', (e) => { window.ctxButton = e.button; });
|
||||
\\t.addEventListener('dblclick', () => { window.dbl = true; });
|
||||
, null);
|
||||
|
||||
const rect_x = try (try ls.local.compileAndRun("document.getElementById('hoverTarget').getBoundingClientRect().x", null)).toF64();
|
||||
const rect_y = try (try ls.local.compileAndRun("document.getElementById('hoverTarget').getBoundingClientRect().y", null)).toF64();
|
||||
|
||||
// Right button: press carries button=2, release fires contextmenu (not click).
|
||||
try ctx.processMessage(.{
|
||||
.id = 1,
|
||||
.method = "Input.dispatchMouseEvent",
|
||||
.params = .{ .type = "mousePressed", .x = rect_x, .y = rect_y, .button = "right" },
|
||||
});
|
||||
try ctx.processMessage(.{
|
||||
.id = 2,
|
||||
.method = "Input.dispatchMouseEvent",
|
||||
.params = .{ .type = "mouseReleased", .x = rect_x, .y = rect_y, .button = "right" },
|
||||
});
|
||||
|
||||
// Left button with clickCount 2 fires dblclick.
|
||||
try ctx.processMessage(.{
|
||||
.id = 3,
|
||||
.method = "Input.dispatchMouseEvent",
|
||||
.params = .{ .type = "mouseReleased", .x = rect_x, .y = rect_y, .button = "left", .clickCount = 2 },
|
||||
});
|
||||
|
||||
const result = try ls.local.compileAndRun("window.downButton === 2 && window.ctxButton === 2 && window.dbl === true", null);
|
||||
try testing.expect(result.isTrue());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user