# How Ego‑Browser Manages JavaScript Dialogs (alert, confirm, prompt): CDP Event Handling Explained

> Discover how ego-browser handles JavaScript dialogs using CDP events. Learn about event tracking and session-scoped Map storage for seamless alert, confirm, and prompt management.

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

---

**Ego‑Browser tracks JavaScript dialogs through CDP events `Page.javascriptDialogOpening` and `Page.javascriptDialogClosed`, storing active dialogs in a session-scoped `Map` that higher‑level helpers query to adapt their behavior.**

The **ego‑browser** runtime, as implemented in `citrolabs/ego-lite`, provides first-class support for detecting and handling browser-side JavaScript dialogs. Rather than leaving dialogs as blocking UI elements that break automation workflows, the library surfaces them through a clean programmatic interface. This article explains the complete dialog management architecture, from low-level CDP message routing to practical usage in navigation and screenshot helpers.

## CDP Event Routing and Dialog State Tracking

At the core of Ego‑Browser's dialog handling is the message router in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts). The `handleMessage` function intercepts every Chrome DevTools Protocol message and routes dialog-specific events to stateful storage.

### Storing Dialogs on `javascriptDialogOpening`

When `Page.javascriptDialogOpening` fires, the runtime extracts dialog parameters and stores them in an internal `Map` named `pendingDialogs`:

- **Key**: the current CDP session ID
- **Value**: an object containing `type` (`alert`, `confirm`, or `prompt`), `message`, `defaultPrompt`, and other CDP-provided fields

```typescript
// The dialog object structure stored in pendingDialogs
{
  type: "confirm",           // "alert" | "confirm" | "prompt"
  message: "Proceed?",
  defaultPrompt: "",         // for prompt dialogs
  // ... additional CDP fields
}

```

### Clearing Dialogs on `javascriptDialogClosed`

On `Page.javascriptDialogClosed`, the entry for that session ID is removed from `pendingDialogs`. The map is also cleared automatically when a session detaches or its target is destroyed, preventing stale state from accumulating.

### Resilient Page Event Enablement

The `Page.enable` CDP command—required to receive dialog events—is wrapped in a try/catch block. Some internal Chrome pages reject this command; dialog tracking continues on a best-effort basis without propagating errors that would disrupt other runtime operations.

## Session‑Scoped Dialog Access with `pendingDialog()`

The `pendingDialog()` function, exported from [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts), provides controlled read access to active dialog state:

```typescript
import { pendingDialog } from "browser-runtime";

// Query dialog for the current session
const dlg = pendingDialog();              // → dialog object or null

// Query dialog for a specific session
const dlgForSession = pendingDialog(sessionId);

```

**Key characteristics**:

- Returns a **shallow copy** of the stored dialog object to prevent external mutation
- Defaults to `state.sessionId` when no argument is provided
- Returns `null` when no dialog is pending for the specified session

## Dialog Detection in Navigation: `pageInfo()`

The `pageInfo` function in [`driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/nav.ts) integrates dialog awareness into page state inspection. Before gathering metrics like dimensions and scroll position, it calls `pendingDialog()`:

```typescript
import { pageInfo } from "driver/nav";

const info = await pageInfo();

if ("dialog" in info) {
  // A dialog blocks normal page interaction
  console.log("Active dialog:", info.dialog.type, info.dialog.message);
  // Handle or dismiss via external UI automation
} else {
  // Normal page metrics available
  console.log("Viewport:", info.w, info.h);
}

```

**Design rationale**: Returning `{ dialog }` instead of page metrics allows calling code to branch immediately on dialog presence, avoiding brittle timeout-based detection.

## Screenshot Fallback with Dialog Capture

The `screenshot` helper in [`driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/observe.ts) demonstrates adaptive behavior based on dialog state. When `pendingDialog()` returns a non-null value, the helper falls back to **raw screenshot mode**:

```typescript
import { screenshot } from "driver/observe";

// Automatically captures dialog if present, normal viewport otherwise
const path = await screenshot();
console.log(`Screenshot saved: ${path}`);

```

**Implementation detail**: The `raw: true` flag bypasses normal viewport framing, capturing the full visible surface including any modal dialog overlay. This prevents silent screenshot failures when a `confirm()` or `prompt()` blocks the main content area.

## Complete Dialog Handling Workflow

Here is a unified example combining detection, inspection, and adaptive action:

```typescript
import { pendingDialog } from "browser-runtime";
import { pageInfo } from "driver/nav";
import { screenshot } from "driver/observe";

async function inspectWithDialogHandling() {
  // 1. Check for dialog before navigation
  const preemptiveCheck = pendingDialog();
  if (preemptiveCheck) {
    console.warn(`Uncleared dialog: ${preemptiveCheck.message}`);
  }

  // 2. Navigate and get page state (dialog-aware)
  const info = await pageInfo();

  if ("dialog" in info) {
    // Dialog is blocking the page
    console.log(`Blocked by ${info.dialog.type}: "${info.dialog.message}"`);

    // Capture dialog appearance for debugging
    const dialogScreenshot = await screenshot();  // raw mode automatic
    return { blocked: true, dialog: info.dialog, screenshot: dialogScreenshot };
  }

  // 3. Normal flow: no dialog present
  console.log(`Page ready: ${info.w}x${info.h}`);
  const normalScreenshot = await screenshot();
  return { blocked: false, size: info, screenshot: normalScreenshot };
}

```

## Key Files in the Dialog Architecture

| File | Role |
|------|------|
| [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) | CDP message routing, `pendingDialogs` Map, `pendingDialog()` accessor, automatic cleanup on session end |
| [`driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/nav.ts) | `pageInfo()` function that short-circuits to `{dialog}` when dialogs are active |
| [`driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/observe.ts) | `screenshot()` helper that selects raw capture mode when `pendingDialog()` indicates an active dialog |

## Summary

- **CDP events drive state**: `Page.javascriptDialogOpening` and `Page.javascriptDialogClosed` are the sole sources of dialog truth, processed in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts)
- **Session-scoped storage**: the `pendingDialogs` Map isolates dialog state per CDP session with automatic cleanup
- **Explicit accessor**: `pendingDialog()` exposes read-only dialog state to higher-level modules
- **Adaptive helpers**: `pageInfo()` and `screenshot()` check dialog presence and modify behavior—returning dialog objects or switching capture modes—rather than failing silently

## Frequently Asked Questions

### How does ego‑browser distinguish between alert, confirm, and prompt dialogs?

The `type` field in the stored dialog object comes directly from the CDP `Page.javascriptDialogOpening` event. Ego‑Browser preserves this value without transformation, so calling code receives `"alert"`, `"confirm"`, or `"prompt"` exactly as Chrome reports it.

### Can multiple dialogs be pending simultaneously across different sessions?

Yes. The `pendingDialogs` Map uses session ID as its key, so each CDP session maintains independent dialog state. The `pendingDialog(sessionId)` signature allows querying any session; the parameterless variant defaults to the currently active session.

### What happens if `Page.enable` fails during initialization?

The enablement call is wrapped in a try/catch. If a page (such as `chrome://` internals) rejects the command, dialog tracking is simply unavailable for that target. Other runtime functionality continues normally; no exception propagates to caller code.

### Does ego‑browser provide methods to programmatically accept or dismiss dialogs?

No. The current implementation is **detection-only**. Dialogs must be handled through external UI automation or separate CDP `Page.handleJavaScriptDialog` calls issued outside the [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) module. The `pendingDialog()` accessor provides the information needed to decide when such intervention is required.