How ego-lite's Pointer Driver Handles Clicks, Hovers, Drags, and Wheel Events
ego-lite's pointer driver translates high-level interaction helpers into Chrome DevTools Protocol (CDP) mouse events, converting semantic actions like click, hover, drag, and wheel into precise Input.dispatchMouseEvent commands for the embedded browser.
The driver lives in package/ego-browser/src/driver/pointer.ts and serves as the low-level bridge between ego-lite's user-facing API and the actual browser runtime. Understanding its implementation reveals how the automation layer achieves reliable, CDP-native pointer control.
Pointer Driver Architecture
The pointer driver follows a consistent three-phase pattern across all operations:
- Element resolution – selectors, XPath expressions, or
@refhandles are resolved to concrete DOM node IDs viasrc/element-resolver.ts - Coordinate calculation – the target element's center is computed, or explicit
[x, y]coordinates are used - CDP event dispatch –
Input.dispatchMouseEventpayloads are constructed and sent viaego.sendCDPMessagedefined insrc/browser-runtime.ts
Transient resolution failures surface as ElementResolutionError (marked transient) for automatic retry, while permanent failures throw immediately.
Click and Double-Click Handling
The click(selector, options) method implements the standard mouse press lifecycle.
// From src/driver/pointer.ts
click: async (selector, options = {}) => {
const { x, y } = await resolveAndCenter(selector);
await pointer.down({ x, y, button: options.button ?? 'left' });
await pointer.up({ x, y, button: options.button ?? 'left' });
}
Under the hood, this generates:
Input.dispatchMouseEventwithtype: 'mousePressed'Input.dispatchMouseEventwithtype: 'mouseReleased'
Double-click (dblclick) performs two sequential click calls with a short delay, setting the clickCount: 2 parameter in the CDP payload.
await dblclick('#icon'); // Resolves to two press/release pairs
Hover (Mouse Movement)
The hover(selectorOrCoords, options) method moves the virtual pointer without pressing any buttons.
await hover('nav > a.home'); // Element-based hover
await hover([100, 200]); // Absolute coordinate hover
This dispatches a single type: 'mouseMoved' event. When an element selector is provided, the driver calculates the bounding box center; raw coordinates bypass resolution entirely.
Drag-and-Drop Support
The drag([source, target], options) method implements the full drag lifecycle as implemented in src/driver/pointer.ts:
await drag(['#item-1', '#target-area'], {
steps: 10, // Number of intermediate move events
});
The sequence preserves the same pointerId and pointerType across:
pointer.downon source coordinates →mousePressedsteps×pointer.moveinvocations → multiplemouseMovedeventspointer.upon target coordinates →mouseReleased
Intermediate steps enable smooth motion tracking, which some JavaScript drag implementations require for proper collision detection.
Wheel (Scroll) Events
The wheel(selectorOrCoords, { deltaX, deltaY }) method dispatches scroll events directly:
await wheel('#scrollable', { deltaY: 200 }); // Scroll down 200px
await wheel([500, 300], { deltaX: -100 }); // Horizontal scroll at coordinates
Unlike click or drag, wheel bypasses the down/up lifecycle and sends type: 'mouseWheel' with deltaX and deltaY values in pixels.
Low-Level Pointer API
The driver exposes raw pointer methods through helpers.pointer():
const ptr = await pointer();
await ptr.move([100, 200]); // Absolute positioning
await ptr.down({ button: 'right' }); // Right-click press
await ptr.up({ button: 'right' });
await ptr.wheel({ deltaY: 500 });
These primitives power the higher-level helpers and enable custom gesture sequences not covered by the standard API.
Public Helper Integration
The helper façade in src/helpers.ts forwards calls to the driver:
// From src/helpers.ts
click: (selector, options = {}) => pointer.click(selector, options),
hover: (target, options = {}) => pointer.hover(target, options),
drag: (targets, options = {}) => pointer.drag(targets, options),
wheel: (target, options) => pointer.wheel(target, options),
This two-layer design keeps the public surface minimal while maintaining full driver access for advanced use cases.
Error Handling and Retries
Resolution failures return structured errors:
- Transient: Element not yet in DOM, stale reference → automatic retry by caller
- Permanent: Invalid selector, element removed → immediate exception
The ElementResolutionError type in src/element-resolver.ts carries a transient boolean flag consumed by retry logic in src/helpers.ts.
Summary
- Event translation: All pointer actions map to
Input.dispatchMouseEventtypes:mousePressed,mouseReleased,mouseMoved,mouseWheel - Source location: Core implementation in
package/ego-browser/src/driver/pointer.ts - Resolution layer:
src/element-resolver.tshandles selector-to-DOM-node mapping - Transport layer:
src/browser-runtime.tsprovidesego.sendCDPMessagefor CDP communication - Test coverage:
src/driver/pointer.test.mjsvalidates click, hover, drag, and wheel behavior
Frequently Asked Questions
What CDP commands does ego-lite's pointer driver use?
The driver exclusively uses Input.dispatchMouseEvent with type values of mousePressed, mouseReleased, mouseMoved, and mouseWheel. These are sent through ego.sendCDPMessage as defined in src/browser-runtime.ts.
How does ego-lite handle element resolution before clicking?
The driver calls the shared element resolver at src/element-resolver.ts, which converts CSS selectors, XPath expressions, ARIA roles, or @ref handles into concrete Chrome DevTools Protocol node IDs. The resolved element's center coordinates are then used for the pointer action.
Can I perform drag operations with custom intermediate steps?
Yes. The drag method accepts a steps option in its options parameter. Higher step counts generate more mouseMoved events between source and target, enabling smooth motion that satisfies JavaScript drag-and-drop implementations requiring collision detection during movement.
What's the difference between wheel and scroll-via-drag in ego-lite?
wheel dispatches mouseWheel CDP events with deltaX/deltaY values, directly triggering scroll handlers without mouse button states. Drag-based scrolling requires pointer.down, pointer.move sequences, and pointer.up—simulating a user clicking and dragging a scrollbar or touch area.
Where are pointer events tested in the ego-lite codebase?
The test suite at package/ego-browser/src/driver/pointer.test.mjs covers click, double-click, hover, drag, and wheel functionality, validating both successful event dispatch and proper error handling for resolution failures.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →