mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
feat(cdp): fire hover events on Input.dispatchMouseEvent mouseMoved
Wire the CDP Input.dispatchMouseEvent "mouseMoved" type to a new Frame.triggerMouseMove, which hit-tests the point via elementFromPoint and dispatches mousemove, mouseover and mouseenter on the target, mirroring the existing triggerMouseClick and the actions.zig hover() event semantics. Previously mouseMoved was silently ignored, so element.hover() over Playwright/Puppeteer (CDP) fired no events. Addresses the hover gap reported in #2043.
This commit is contained in:
@@ -3929,6 +3929,44 @@ pub fn triggerMouseClick(self: *Frame, x: f64, y: f64) !void {
|
||||
try self._event_manager.dispatch(target.asEventTarget(), mouse_event.asEvent());
|
||||
}
|
||||
|
||||
pub fn triggerMouseMove(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 move", .{
|
||||
.url = self.url,
|
||||
.node = target,
|
||||
.x = x,
|
||||
.y = y,
|
||||
.type = self._type,
|
||||
});
|
||||
}
|
||||
|
||||
const move_event: *MouseEvent = try .initTrusted(comptime .wrap("mousemove"), .{
|
||||
.bubbles = true,
|
||||
.cancelable = true,
|
||||
.composed = true,
|
||||
.clientX = x,
|
||||
.clientY = y,
|
||||
}, self);
|
||||
try self._event_manager.dispatch(target.asEventTarget(), move_event.asEvent());
|
||||
|
||||
const over_event: *MouseEvent = try .initTrusted(comptime .wrap("mouseover"), .{
|
||||
.bubbles = true,
|
||||
.cancelable = true,
|
||||
.composed = true,
|
||||
.clientX = x,
|
||||
.clientY = y,
|
||||
}, self);
|
||||
try self._event_manager.dispatch(target.asEventTarget(), over_event.asEvent());
|
||||
|
||||
const enter_event: *MouseEvent = try .initTrusted(comptime .wrap("mouseenter"), .{
|
||||
.composed = true,
|
||||
.clientX = x,
|
||||
.clientY = y,
|
||||
}, self);
|
||||
try self._event_manager.dispatch(target.asEventTarget(), enter_event.asEvent());
|
||||
}
|
||||
|
||||
// callback when the "click" event reaches the frame.
|
||||
pub fn handleClick(self: *Frame, target: *Node) !void {
|
||||
// TODO: Also support <area> elements when implement
|
||||
|
||||
@@ -96,13 +96,17 @@ fn dispatchMouseEvent(cmd: *CDP.Command) !void {
|
||||
|
||||
// quickly ignore types we know we don't handle
|
||||
switch (params.type) {
|
||||
.mouseMoved, .mouseWheel, .mouseReleased => return,
|
||||
.mouseWheel, .mouseReleased => return,
|
||||
else => {},
|
||||
}
|
||||
|
||||
const bc = cmd.browser_context orelse return;
|
||||
const frame = bc.session.currentFrame() orelse return;
|
||||
try frame.triggerMouseClick(params.x, params.y);
|
||||
switch (params.type) {
|
||||
.mousePressed => try frame.triggerMouseClick(params.x, params.y),
|
||||
.mouseMoved => try frame.triggerMouseMove(params.x, params.y),
|
||||
.mouseWheel, .mouseReleased => unreachable,
|
||||
}
|
||||
// result already sent
|
||||
}
|
||||
|
||||
@@ -119,3 +123,46 @@ fn insertText(cmd: *CDP.Command) !void {
|
||||
|
||||
try cmd.sendResult(null, .{});
|
||||
}
|
||||
|
||||
const lp = @import("lightpanda");
|
||||
const testing = @import("../testing.zig");
|
||||
|
||||
test "cdp.input: dispatchMouseEvent mouseMoved fires hover events" {
|
||||
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();
|
||||
|
||||
// Register listeners for the full enter sequence on #hoverTarget, then read
|
||||
// its (faux-layout) position so we can target it precisely.
|
||||
_ = try ls.local.compileAndRun(
|
||||
\\const t = document.getElementById('hoverTarget');
|
||||
\\t.addEventListener('mousemove', () => { window.moved = true; });
|
||||
\\t.addEventListener('mouseenter', () => { window.entered = 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();
|
||||
|
||||
try ctx.processMessage(.{
|
||||
.id = 1,
|
||||
.method = "Input.dispatchMouseEvent",
|
||||
.params = .{ .type = "mouseMoved", .x = rect_x, .y = rect_y },
|
||||
});
|
||||
|
||||
const result = try ls.local.compileAndRun("window.hovered === true && window.entered === true && window.moved === true", null);
|
||||
try testing.expect(result.isTrue());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user