mirror of
https://github.com/daijro/camoufox.git
synced 2026-08-18 16:00:58 +00:00
fix(juggler): restore humanized mouse trajectory dropped in FF146 migration (#677)
The FF146 Juggler migration (03c1230) rewrote the sendEvents() helper in
Page.dispatchMouseEvent to batch events via Promise.all and, in doing so,
dropped the Camoufox humanize branch. Since then, with humanize=True a long
page.mouse.move() emitted only the single endpoint mousemove — the native C++
cursor trajectory (MouseTrajectories.hpp / ChromeUtils.camouGetMouseTrajectory)
was never invoked, even though the generator and config plumbing remained.
Reintroduce the trajectory expansion inside the new sendEvents():
- factor the single-event dispatch into a sendOne() helper
- when humanize is enabled, expand a mousemove into the C++-generated
trajectory, dispatching intermediate points sequentially with a 10ms delay,
then always finishing exactly on the requested destination so clicks/hover
still land on target
- restore _lastTrackedPos so each move's trajectory starts from the real
previous cursor position
Verified against the built v152.0.4-beta.25 binary: humanize=True now emits
~180 intermediate mousemoves ending exactly at the destination (was 2);
normal click/fill/drag and humanized click-to-target are unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -81,6 +81,9 @@ export class PageHandler {
|
||||
|
||||
this._isDragging = false;
|
||||
this._lastMousePosition = { x: 0, y: 0 };
|
||||
// Camoufox: last position an actual mousemove landed on. Used as the
|
||||
// start point for the humanized cursor trajectory (humanize=True).
|
||||
this._lastTrackedPos = { x: 0, y: 0 };
|
||||
|
||||
this._reportedFrameIds = new Set();
|
||||
this._networkEventsForUnreportedFrameIds = new Map();
|
||||
@@ -523,13 +526,14 @@ export class PageHandler {
|
||||
await helper.awaitTopic('apz-repaints-flushed');
|
||||
|
||||
const watcher = new EventWatcher(this._pageEventSink, types, this._pendingEventWatchers);
|
||||
const promises = [];
|
||||
for (const type of types) {
|
||||
// Dispatch a single synthesized mouse event to the renderer and return a
|
||||
// promise that resolves once the renderer acks it.
|
||||
const sendOne = (eventType, eventX, eventY) => {
|
||||
// This dispatches to the renderer synchronously.
|
||||
const jugglerEventId = win.windowUtils.jugglerSendMouseEvent(
|
||||
type,
|
||||
x + boundingBox.left,
|
||||
y + boundingBox.top,
|
||||
eventType,
|
||||
eventX + boundingBox.left,
|
||||
eventY + boundingBox.top,
|
||||
button,
|
||||
clickCount,
|
||||
modifiers,
|
||||
@@ -542,7 +546,33 @@ export class PageHandler {
|
||||
win.windowUtils.DEFAULT_MOUSE_POINTER_ID /* pointerIdentifier */,
|
||||
false /* disablePointerEvent */
|
||||
);
|
||||
promises.push(watcher.ensureEvent(type, eventObject => eventObject.jugglerEventId === jugglerEventId));
|
||||
return watcher.ensureEvent(eventType, eventObject => eventObject.jugglerEventId === jugglerEventId);
|
||||
};
|
||||
|
||||
const promises = [];
|
||||
for (const type of types) {
|
||||
// Camoufox: when humanize is enabled, expand a direct mousemove into a
|
||||
// human-like trajectory of intermediate mousemoves generated in C++
|
||||
// (ChromeUtils.camouGetMouseTrajectory / MouseTrajectories.hpp).
|
||||
if (type === 'mousemove' && ChromeUtils.camouGetBool('humanize', false)) {
|
||||
const trajectory = ChromeUtils.camouGetMouseTrajectory(this._lastTrackedPos.x, this._lastTrackedPos.y, x, y);
|
||||
// Dispatch intermediate points sequentially with a short delay. The
|
||||
// first/last pairs are skipped: the last pair is the exact
|
||||
// destination, which is dispatched explicitly below.
|
||||
for (let i = 2; i < trajectory.length - 2; i += 2) {
|
||||
const currentX = trajectory[i];
|
||||
const currentY = trajectory[i + 1];
|
||||
// Skip movement that is out of bounds
|
||||
if (currentX < 0 || currentY < 0 || currentX > boundingBox.width || currentY > boundingBox.height)
|
||||
continue;
|
||||
await sendOne('mousemove', currentX, currentY);
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
}
|
||||
// Always finish exactly on the requested destination.
|
||||
promises.push(sendOne('mousemove', x, y));
|
||||
} else {
|
||||
promises.push(sendOne(type, x, y));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
await watcher.dispose();
|
||||
@@ -603,6 +633,9 @@ export class PageHandler {
|
||||
|
||||
const watcher = new EventWatcher(this._pageEventSink, ['dragstart', 'juggler-drag-finalized'], this._pendingEventWatchers);
|
||||
await sendEvents(['mousemove']);
|
||||
// Camoufox: remember where the cursor landed so the next humanized
|
||||
// move starts its trajectory from the real previous position.
|
||||
this._lastTrackedPos = { x, y };
|
||||
|
||||
// The order of events after 'mousemove' is sent:
|
||||
// 1. [dragstart] - might or might NOT be emitted
|
||||
|
||||
Reference in New Issue
Block a user