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:

  1. Element resolution – selectors, XPath expressions, or @ref handles are resolved to concrete DOM node IDs via src/element-resolver.ts
  2. Coordinate calculation – the target element's center is computed, or explicit [x, y] coordinates are used
  3. CDP event dispatchInput.dispatchMouseEvent payloads are constructed and sent via ego.sendCDPMessage defined in src/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.dispatchMouseEvent with type: 'mousePressed'
  • Input.dispatchMouseEvent with type: '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:

  1. pointer.down on source coordinates → mousePressed
  2. steps × pointer.move invocations → multiple mouseMoved events
  3. pointer.up on 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.dispatchMouseEvent types: mousePressed, mouseReleased, mouseMoved, mouseWheel
  • Source location: Core implementation in package/ego-browser/src/driver/pointer.ts
  • Resolution layer: src/element-resolver.ts handles selector-to-DOM-node mapping
  • Transport layer: src/browser-runtime.ts provides ego.sendCDPMessage for CDP communication
  • Test coverage: src/driver/pointer.test.mjs validates 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →