# How Does Ego-Lite Track JavaScript Dialogs: CDP Event Handling Explained

> Discover how Ego-Lite tracks JavaScript dialogs using CDP event handling. Learn about session maps, pending dialogs, and accessor usage for navigation and screenshots.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: deep-dive
- Published: 2026-08-23

---

**Ego-Lite tracks JavaScript dialogs by maintaining a per-session `Map` called `pendingDialogs` in the browser runtime that listens to Chrome DevTools Protocol (CDP) events `Page.javascriptDialogOpening` and `Page.javascriptDialogClosed`, exposing the state through a `pendingDialog()` accessor used by navigation and screenshot helpers.**

The `citrolabs/ego-lite` repository provides a lightweight browser automation framework that handles modal interruptions without blocking execution. Understanding how it tracks JavaScript dialogs reveals the tight integration between the Node.js runtime and Chrome's DevTools Protocol, enabling agents to detect alerts, confirms, prompts, and before-unload dialogs in real time.

## The Per-Session Dialog Storage Mechanism

At the core of ego-lite's dialog detection is a centralized state container defined in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts). On line 24, the runtime initializes a `Map` named `pendingDialogs` that persists throughout the browser session.

This map uses the CDP session ID as its key, ensuring that dialog state remains isolated when managing multiple browser contexts or tabs simultaneously. Each entry stores the raw dialog parameters received from the browser, including the dialog type (alert, confirm, prompt, or beforeunload), message text, and default prompt value.

## Processing CDP Dialog Events

The runtime captures dialog lifecycle events through the `handleMessage` function located at lines 66-76 of [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts). This function acts as the central message router for all CDP traffic and specifically watches for two critical events:

- **`Page.javascriptDialogOpening`** (lines 66-70): When the browser fires this event, the runtime extracts `data.params` and stores it in `pendingDialogs` under the current session ID, flagging that a modal is blocking the page.

- **`Page.javascriptDialogClosed`** (lines 71-75): When the dialog dismisses—whether through user action or automation—the runtime deletes the corresponding entry from the map, clearing the pending state.

This event-driven architecture ensures the internal state always mirrors the browser's actual UI state without polling or synthetic waits.

## Retrieving Dialog State

Consumer modules access the tracked dialog through the `pendingDialog(sessionId?)` method defined at lines 98-104 of [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts). This public API returns a shallow copy of the stored dialog object for the specified session, or `null` when no dialog is present.

The optional `sessionId` parameter allows multi-session agents to query specific browser contexts. When omitted, the method defaults to the current active session, providing a safe, read-only view of the dialog data without exposing the internal map reference.

## Consumer Integration Patterns

Ego-lite integrates dialog awareness into high-level automation primitives, ensuring agents can react appropriately when modals interrupt page interactions.

### Navigation Handling in nav.ts

The `pageInfo()` function in [`src/driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/nav.ts) (lines 84-90) demonstrates defensive dialog checking. Before gathering standard page metrics like URL or title, it first calls `pendingDialog()`. If a dialog exists, the function immediately returns an object containing the dialog details instead of page information, allowing the caller to handle the modal before proceeding with navigation.

### Screenshot Safety in observe.ts

In [`src/driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/observe.ts), the `screenshot()` function (lines 112-125) implements dialog-aware capture logic. If `pendingDialog()` returns a truthy value, the function recursively invokes itself with the `raw: true` option to bypass automatic viewport cropping. This prevents the screenshot from capturing a partially obscured or incorrectly calculated viewport while a modal dialog is open.

## Practical Implementation Examples

Detect a dialog while gathering page information:

```typescript
import { pageInfo } from 'ego-browser/src/driver/nav.js';

async function showInfo() {
  const info = await pageInfo();
  if ('dialog' in info) {
    console.log('A dialog is open:', info.dialog);
  } else {
    console.log('Page URL:', info.url);
  }
}

```

Wait for dialog dismissal before capturing the screen:

```typescript
import { screenshot } from 'ego-browser/src/driver/observe.js';
import { pendingDialog } from 'ego-browser/src/browser-runtime.js';

async function safeScreenshot() {
  // Wait until any open dialog disappears
  while (pendingDialog()) {
    await new Promise(r => setTimeout(r, 100));
  }
  const path = await screenshot({ fullPage: true });
  console.log('Saved screenshot to', path);
}

```

Retrieve dialog details manually:

```typescript
import { pendingDialog } from 'ego-browser/src/browser-runtime.js';

const dlg = pendingDialog();
if (dlg) {
  console.log(`Dialog type: ${dlg.type}, message: ${dlg.message}`);
}

```

## Summary

- **Ego-lite tracks JavaScript dialogs using a `pendingDialogs` Map** in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) keyed by CDP session ID.
- **CDP events drive state updates**: `Page.javascriptDialogOpening` stores dialog data (lines 66-70), while `Page.javascriptDialogClosed` removes it (lines 71-75).
- **The `pendingDialog()` method** (lines 98-104) provides safe, read-only access to current dialog state.
- **Navigation and screenshot utilities** check for pending dialogs to prevent automation errors and ensure accurate page captures.
- **Session-scoped storage** ensures dialog tracking works correctly across multiple parallel browser contexts.

## Frequently Asked Questions

### What CDP events does ego-lite use to detect JavaScript dialogs?

Ego-lite listens to two Chrome DevTools Protocol events: `Page.javascriptDialogOpening` to store dialog parameters when a modal appears, and `Page.javascriptDialogClosed` to clear the state when the dialog dismisses. These events are handled in the `handleMessage` function at lines 66-76 of [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts).

### How does ego-lite handle multiple browsing sessions with dialogs?

The `pendingDialogs` Map uses CDP session IDs as keys, isolating dialog state per browser context. When calling `pendingDialog(sessionId?)`, you can specify a particular session, or the method will default to the current active session, ensuring that dialogs in one tab do not interfere with automation in another.

### Can I check for dialogs without consuming them?

Yes. The `pendingDialog()` method returns a shallow copy of the dialog data without modifying the internal `pendingDialogs` Map or affecting the browser state. The dialog remains tracked and open until the user or automation dismisses it through CDP commands.

### How does ego-lite prevent screenshots from capturing incorrectly when dialogs are open?

The `screenshot()` function in [`src/driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/observe.ts) (lines 112-125) checks `pendingDialog()` before capturing. If a dialog is detected, it recursively calls itself with `raw: true` to disable viewport cropping, ensuring the screenshot captures the full page including the modal overlay rather than a cropped region.