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

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. 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. This function returns a shallow copy of the stored dialog object or null if no dialog is pending for the specified session.

// 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 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.

// 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 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.

// 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 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 and screenshot in 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 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 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.

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 →