# How ego-browser Handles JavaScript Dialogs: Alerts, Confirms, and Prompts

> Learn how ego-browser handles JavaScript dialogs. Discover its use of Chrome DevTools Protocol events for managing alerts, confirms, and prompts during runtime.

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

---

**ego-browser tracks JavaScript dialogs via Chrome DevTools Protocol events `Page.javascriptDialogOpening` and `Page.javascriptDialogClosed`, storing active dialogs in a session-scoped Map for runtime inspection.**

Handling modal dialogs like `alert()`, `confirm()`, and `prompt()` is critical for browser automation. The **ego-browser** runtime in the `citrolabs/ego-lite` repository monitors these blocking interactions through Chrome DevTools Protocol (CDP) events, providing agents with real-time visibility into dialog state without interrupting session flow.

## CDP Event Routing for Dialog Detection

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 inspects every incoming CDP message and routes dialog-specific events to internal state management.

### Tracking Dialog Opening and Closing

When the browser fires a JavaScript dialog, CDP emits `Page.javascriptDialogOpening` with parameters including `type`, `message`, and `defaultPrompt`. The runtime stores these parameters in a `Map` called `pendingDialogs`, keyed by the current CDP session ID. When the dialog closes—whether dismissed by user action or script—CDP emits `Page.javascriptDialogClosed`, and the runtime removes the corresponding entry from the map.

This design ensures that **pendingDialogs** always reflects the current modal state for each active session.

### Automatic Cleanup on Session End

The map is cleared automatically when a session detaches or its target is destroyed. This prevents stale dialog entries from persisting across page navigations or browser restarts, maintaining accurate state for long-running automation tasks.

## Accessing Active Dialogs with pendingDialog()

The runtime exposes a public accessor `pendingDialog(sessionId = state.sessionId)` in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts). This function returns a shallow copy of the stored dialog object or `null` if no dialog is pending for the specified session.

```typescript
// Detect a pending dialog before acting on a page
import { pendingDialog } from "browser-runtime";

const dlg = pendingDialog();               // → {type:"alert",message:"…"} or null
if (dlg) {
  console.log(`Dialog of type ${dlg.type} is open: ${dlg.message}`);
}

```

By default, the function uses the current session ID from the global state, making it easy to check for blocking dialogs without passing explicit identifiers.

## Adapting Automation Workflows to Dialog State

Higher-level helpers in ego-browser use `pendingDialog()` to adapt their behavior when a modal blocks the main thread.

### Short-Circuiting Page Metrics in pageInfo

The `pageInfo` function in [`driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/nav.ts) checks for active dialogs before gathering page metrics. If `pendingDialog()` returns an object, `pageInfo` short-circuits and returns `{ dialog }` instead of the usual viewport dimensions and navigation data.

```typescript
// Using pageInfo to get either page metrics or the dialog object
import { pageInfo } from "driver/nav";

const info = await pageInfo();
if ("dialog" in info) {
  // A dialog blocks normal navigation; handle or dismiss it via the UI
  console.log("Dialog detected:", info.dialog);
} else {
  console.log("Page size:", info.w, info.h);
}

```

This allows automation scripts to detect when a user interaction is required before proceeding with navigation.

### Fallback Screenshot Strategy in screenshot

The `screenshot` helper in [`driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/observe.ts) uses `pendingDialog()` to determine capture strategy. If a dialog is open, the helper falls back to **raw** screenshot mode by setting `raw: true`, ensuring the modal itself appears in the captured image rather than attempting a standard viewport capture that would fail or hang.

```typescript
// Screenshot that includes a dialog if one is present
import { screenshot } from "driver/observe";

const path = await screenshot();   // automatically falls back to raw mode when a dialog exists
console.log(`Saved screenshot to ${path}`);

```

## Graceful Degradation for Internal Pages

Enabling page events via `Page.enable` is wrapped in a try/catch block because some internal browser pages (such as `chrome://` URLs) reject this CDP command. Dialog tracking continues as a best-effort feature without throwing fatal errors, ensuring that other automation helpers remain functional even when full CDP page instrumentation is unavailable.

## Summary

- **Event-driven detection**: ego-browser listens to `Page.javascriptDialogOpening` and `Page.javascriptDialogClosed` CDP events in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) to maintain accurate dialog state.
- **Session-scoped storage**: The `pendingDialogs` Map isolates dialog state by CDP session ID and cleans up automatically on session end.
- **Runtime inspection**: The `pendingDialog()` accessor provides a safe, read-only view of active dialogs for any session.
- **Workflow adaptation**: Functions like `pageInfo` in [`driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/nav.ts) and `screenshot` in [`driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/observe.ts) check for dialogs to avoid hangs and capture accurate page state.
- **Resilient operation**: Dialog tracking degrades gracefully on internal pages where `Page.enable` is restricted.

## Frequently Asked Questions

### How does ego-browser detect when a JavaScript alert opens?

ego-browser detects alerts through the Chrome DevTools Protocol event `Page.javascriptDialogOpening`, which fires whenever the browser displays an alert, confirm, or prompt. The `handleMessage` function in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) captures this event and stores the dialog details in the `pendingDialogs` Map.

### What happens to dialog tracking when a browser session ends?

When a CDP session detaches or its target is destroyed, ego-browser automatically clears the corresponding entry from the `pendingDialogs` Map. This ensures that no stale dialog references persist across page navigations or browser restarts.

### Can I capture a screenshot when a confirm dialog is blocking the page?

Yes. The `screenshot` function in [`driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/observe.ts) checks `pendingDialog()` before capturing. If a dialog is detected, it automatically switches to raw screenshot mode to include the modal overlay in the image, preventing the hang that would occur with standard viewport capture methods.

### How do I check if a prompt dialog is currently open?

Import `pendingDialog` from `browser-runtime` and call it with the target session ID (or leave blank to use the current session). The function returns an object containing `type`, `message`, and `defaultPrompt` if a dialog is active, or `null` if the page is clear.