mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
feat(cdp): fire wheel events on Input.dispatchMouseEvent mouseWheel
Wire the CDP Input.dispatchMouseEvent "mouseWheel" type to a new Frame.triggerMouseWheel, which hit-tests the point via elementFromPoint and dispatches a wheel event (with deltaX/deltaY) on the target. When the wheel event is not cancelled, it applies the scroll offset and fires a scroll event, mirroring the wheel handling in WebDriver.zig. Previously mouseWheel was silently ignored. Follow-up to #2636.
This commit is contained in:
@@ -61,6 +61,7 @@ const popover = @import("webapi/element/popover.zig");
|
||||
const NavigationKind = @import("webapi/navigation/root.zig").NavigationKind;
|
||||
const KeyboardEvent = @import("webapi/event/KeyboardEvent.zig");
|
||||
const MouseEvent = @import("webapi/event/MouseEvent.zig");
|
||||
const WheelEvent = @import("webapi/event/WheelEvent.zig");
|
||||
|
||||
const HttpClient = @import("HttpClient.zig");
|
||||
|
||||
@@ -3962,6 +3963,55 @@ pub fn triggerMouseRelease(self: *Frame, x: f64, y: f64) !void {
|
||||
try self._event_manager.dispatch(target.asEventTarget(), up_event.asEvent());
|
||||
}
|
||||
|
||||
pub fn triggerMouseWheel(self: *Frame, x: f64, y: f64, delta_x: f64, delta_y: f64) !void {
|
||||
const target = (try self.window._document.elementFromPoint(x, y, self)) orelse return;
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.frame, "frame mouse wheel", .{
|
||||
.url = self.url,
|
||||
.node = target,
|
||||
.x = x,
|
||||
.y = y,
|
||||
.delta_x = delta_x,
|
||||
.delta_y = delta_y,
|
||||
.type = self._type,
|
||||
});
|
||||
}
|
||||
|
||||
const wheel_event: *WheelEvent = try .initTrusted("wheel", .{
|
||||
.bubbles = true,
|
||||
.cancelable = true,
|
||||
.composed = true,
|
||||
.clientX = x,
|
||||
.clientY = y,
|
||||
.deltaX = delta_x,
|
||||
.deltaY = delta_y,
|
||||
}, self);
|
||||
|
||||
// Keep the event alive past dispatch so we can read _prevent_default.
|
||||
wheel_event.asEvent().acquireRef();
|
||||
defer _ = wheel_event.asEvent().releaseRef(self._page);
|
||||
try self._event_manager.dispatch(target.asEventTarget(), wheel_event.asEvent());
|
||||
|
||||
if (wheel_event.asEvent()._prevent_default) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply the scroll and fire a trusted scroll event, mirroring WebDriver wheel.
|
||||
// CDP deltas are untrusted, so guard NaN and saturate the addition.
|
||||
const new_left: i32 = @as(i32, @intCast(target.getScrollLeft(self))) +| deltaToScroll(delta_x);
|
||||
const new_top: i32 = @as(i32, @intCast(target.getScrollTop(self))) +| deltaToScroll(delta_y);
|
||||
try target.setScrollLeft(new_left, self);
|
||||
try target.setScrollTop(new_top, self);
|
||||
|
||||
const scroll_event = try Event.initTrusted(comptime .wrap("scroll"), .{ .bubbles = true }, self._page);
|
||||
try self._event_manager.dispatch(target.asEventTarget(), scroll_event);
|
||||
}
|
||||
|
||||
fn deltaToScroll(d: f64) i32 {
|
||||
if (std.math.isNan(d)) return 0;
|
||||
return @intFromFloat(std.math.clamp(d, std.math.minInt(i32), std.math.maxInt(i32)));
|
||||
}
|
||||
|
||||
// callback when the "click" event reaches the frame.
|
||||
pub fn handleClick(self: *Frame, target: *Node) !void {
|
||||
// TODO: Also support <area> elements when implement
|
||||
|
||||
@@ -82,6 +82,8 @@ fn dispatchMouseEvent(cmd: *CDP.Command) !void {
|
||||
x: f64,
|
||||
y: f64,
|
||||
type: Type,
|
||||
deltaX: f64 = 0,
|
||||
deltaY: f64 = 0,
|
||||
// Many optional parameters are not implemented yet, see documentation url.
|
||||
|
||||
const Type = enum {
|
||||
@@ -94,19 +96,13 @@ fn dispatchMouseEvent(cmd: *CDP.Command) !void {
|
||||
|
||||
try cmd.sendResult(null, .{});
|
||||
|
||||
// quickly ignore types we know we don't handle
|
||||
switch (params.type) {
|
||||
.mouseWheel => return,
|
||||
else => {},
|
||||
}
|
||||
|
||||
const bc = cmd.browser_context orelse return;
|
||||
const frame = bc.session.currentFrame() orelse return;
|
||||
switch (params.type) {
|
||||
.mousePressed => try frame.triggerMouseClick(params.x, params.y),
|
||||
.mouseReleased => try frame.triggerMouseRelease(params.x, params.y),
|
||||
.mouseMoved => try frame.triggerMouseMove(params.x, params.y),
|
||||
.mouseWheel => unreachable,
|
||||
.mouseWheel => try frame.triggerMouseWheel(params.x, params.y, params.deltaX, params.deltaY),
|
||||
}
|
||||
// result already sent
|
||||
}
|
||||
@@ -204,3 +200,40 @@ test "cdp.input: dispatchMouseEvent mouseReleased fires mouseup" {
|
||||
const result = try ls.local.compileAndRun("window.released === true", null);
|
||||
try testing.expect(result.isTrue());
|
||||
}
|
||||
|
||||
test "cdp.input: dispatchMouseEvent mouseWheel fires wheel event" {
|
||||
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(
|
||||
\\document.getElementById('scrollbox')
|
||||
\\ .addEventListener('wheel', (e) => { window.wheelDeltaY = e.deltaY; });
|
||||
, null);
|
||||
|
||||
const rect_x = try (try ls.local.compileAndRun("document.getElementById('scrollbox').getBoundingClientRect().x", null)).toF64();
|
||||
const rect_y = try (try ls.local.compileAndRun("document.getElementById('scrollbox').getBoundingClientRect().y", null)).toF64();
|
||||
|
||||
try ctx.processMessage(.{
|
||||
.id = 1,
|
||||
.method = "Input.dispatchMouseEvent",
|
||||
.params = .{ .type = "mouseWheel", .x = rect_x, .y = rect_y, .deltaY = 40 },
|
||||
});
|
||||
|
||||
const result = try ls.local.compileAndRun("window.wheelDeltaY === 40", null);
|
||||
try testing.expect(result.isTrue());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user