How Ego‑Browser Handles JavaScript Dialogs (alert, confirm, prompt)
Ego‑Browser intercepts JavaScript dialogs via Chrome DevTools Protocol events and stores them in a session-scoped Map, exposing a pendingDialog() accessor that helpers like pageInfo() and screenshot() use to adapt their behavior when alerts, confirms, or prompts block the page.
The citrolabs/ego-lite repository provides a headless browser automation framework that must gracefully handle modal JavaScript dialogs. Instead of letting alert(), confirm(), or prompt() calls hang indefinitely, ego-browser tracks these events through the Chrome DevTools Protocol (CDP) and exposes detection mechanisms that downstream consumers use to avoid interaction deadlocks.
Capturing Dialog Events in browser-runtime.ts
All CDP message routing happens inside browser-runtime.ts. The core logic resides in handleMessage, which inspects incoming protocol events to maintain an accurate view of the browser state.
The pendingDialogs Map
When handleMessage receives a Page.javascriptDialogOpening event, it extracts the dialog parameters—including type, message, and defaultPrompt—and stores them in an internal Map named pendingDialogs. The key for each entry is the current CDP session ID, ensuring isolation across multiple browser contexts.
// Conceptual flow inside browser-runtime.ts
if (method === 'Page.javascriptDialogOpening') {
pendingDialogs.set(sessionId, {
type: params.type, // "alert", "confirm", or "prompt"
message: params.message,
defaultPrompt: params.defaultPrompt
});
}
When the page closes the dialog (either programmatically or via user action), the Page.javascriptDialogClosed event triggers removal of that session’s entry from the Map, preventing stale state.
Session Lifecycle Management
The Map is automatically cleared when a session detaches or its target is destroyed. This best-effort cleanup ensures that crashed or closed tabs do not leave phantom dialog entries behind, maintaining consistency for long-running automation scripts.
Detecting Active Dialogs with pendingDialog()
browser-runtime.ts exports a public accessor function named pendingDialog(sessionId = state.sessionId) that returns a shallow copy of the stored dialog object or null if no dialog is pending for that session. This function serves as the canonical check for any component needing to react to modal interference.
import { pendingDialog } from "browser-runtime";
const dlg = pendingDialog(); // → {type:"alert", message:"…"} or null
if (dlg) {
console.log(`Blocking dialog detected: ${dlg.type}`);
}
Because the function returns a copy rather than the internal reference, consumers cannot accidentally mutate the pendingDialogs Map.
Practical Usage in Navigation and Observation
Higher-level modules in driver/nav.ts and driver/observe.ts leverage pendingDialog() to short-circuit operations that would otherwise fail or produce meaningless results while a modal is open.
Short-Circuiting pageInfo in driver/nav.ts
The pageInfo() function checks pendingDialog() before gathering page metrics. If a dialog is active, it immediately returns a { dialog } object containing the pending dialog details instead of the usual viewport dimensions and scroll positions.
import { pageInfo } from "driver/nav";
const info = await pageInfo();
if ("dialog" in info) {
// Navigation is blocked by a modal
console.log("Active dialog:", info.dialog);
} else {
// Safe to use page metrics
console.log(`Page dimensions: ${info.w}x${info.h}`);
}
Fallback Screenshots in driver/observe.ts
The screenshot() helper uses pendingDialog() to determine the capture strategy. When a dialog exists, it falls back to a raw screenshot (passing raw: true) that captures the modal overlay itself rather than attempting a standard clipped capture that would omit the blocking dialog.
import { screenshot } from "driver/observe";
// Automatically switches to raw mode if a dialog is present
const path = await screenshot();
console.log(`Screenshot saved to ${path}`);
Robustness and Error Handling
Dialog tracking is wrapped in defensive programming. Enabling page events via Page.enable is executed inside a try/catch block because certain internal browser pages (such as chrome-extension URLs or internal about: pages) reject the command. This ensures that dialog monitoring continues on standard web pages without crashing the entire runtime when encountering restricted contexts.
Summary
- Event-Driven Tracking:
browser-runtime.tslistens toPage.javascriptDialogOpeningandPage.javascriptDialogClosedCDP events to maintain apendingDialogsMap keyed by session ID. - Safe Accessor: The exported
pendingDialog()function returns a shallow copy of active dialog data ornull, preventing external mutation of internal state. - Navigation Awareness:
pageInfo()indriver/nav.tsreturns a{ dialog }object instead of page metrics when a modal blocks the context. - Visual Capture:
screenshot()indriver/observe.tsautomatically falls back to raw capture mode to include the dialog in the image. - Session Safety: Entries are cleared on session detach or target destruction, and
Page.enableerrors are caught to handle restricted internal pages gracefully.
Frequently Asked Questions
How does ego-browser detect when a JavaScript dialog opens?
Ego-browser subscribes to the Page.javascriptDialogOpening event via Chrome DevTools Protocol. When this event fires, handleMessage in browser-runtime.ts extracts the dialog type and message and stores them in the pendingDialogs Map under the current session ID.
What happens if I call pageInfo() while an alert() is blocking the page?
The pageInfo() function in driver/nav.ts calls pendingDialog() before gathering metrics. If an alert, confirm, or prompt is active, it returns an object containing only the { dialog } property with the dialog details, allowing your automation logic to handle or dismiss the modal before proceeding.
Can ego-browser take screenshots that include the dialog box?
Yes. The screenshot() function in driver/observe.ts checks pendingDialog() before capturing. If a dialog is present, it automatically switches to raw screenshot mode, which captures the entire viewport including the modal overlay, ensuring you can see the blocking dialog in the output image.
Is dialog tracking isolated between browser tabs?
Absolutely. The pendingDialogs Map uses CDP session IDs as keys. Each tab or target maintains its own session, so concurrent automation across multiple pages does not leak dialog state between contexts.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →