How Ego-Browser Handles JavaScript Dialogs (Alert, Confirm, Prompt)
Ego-Browser tracks JavaScript dialogs via Chrome DevTools Protocol events Page.javascriptDialogOpening and Page.javascriptDialogClosed, storing active dialogs in a session-scoped pendingDialogs Map with a public pendingDialog() accessor for downstream consumers.
JavaScript dialogs (alert(), confirm(), prompt()) block page interaction and break standard automation workflows. Ego-Browser, a lightweight browser automation framework from citrolabs/ego-lite, implements a robust, session-aware dialog tracking system that lets agents detect and adapt to these blocking conditions in real time. This article examines the implementation details, key source files, and practical usage patterns.
CDP Event Handling in browser-runtime.ts
The core dialog tracking logic lives in src/runtime/browser-runtime.ts. Here, the handleMessage function routes incoming Chrome DevTools Protocol messages and maintains dialog state.
Storing Dialog State
When a Page.javascriptDialogOpening event arrives, ego-browser extracts the dialog parameters and persists them:
// Conceptual flow from browser-runtime.ts
pendingDialogs.set(sessionId, {
type: dialog.type, // "alert" | "confirm" | "prompt"
message: dialog.message,
defaultPrompt: dialog.defaultPrompt // for prompt dialogs only
});
The pendingDialogs Map uses the CDP session ID as its key, ensuring isolation across concurrent browser sessions.
Cleaning Up Closed Dialogs
On Page.javascriptDialogClosed, the corresponding entry is removed:
pendingDialogs.delete(sessionId);
The Map is also cleared automatically when a session detaches or its target is destroyed, preventing memory leaks from stale dialog entries.
The pendingDialog() Public API
Ego-Browser exports a pendingDialog(sessionId?) utility from browser-runtime.ts that consumers use to check for blocking dialogs:
import { pendingDialog } from "browser-runtime";
const dlg = pendingDialog(); // Defaults to current state's sessionId
// Returns: { type, message, defaultPrompt } | null
The function returns a shallow copy of the stored dialog object, protecting internal state from external mutation. If no dialog is active for the given session, it returns null.
Consumer Integrations: Adaptive Behavior Based on Dialog State
Two critical helpers in ego-browser consume pendingDialog() to modify their behavior when a dialog blocks the page.
pageInfo() in driver/nav.ts
The pageInfo() navigation helper checks for dialogs before gathering page metrics. In src/driver/nav.ts:
import { pageInfo } from "driver/nav";
const info = await pageInfo();
if ("dialog" in info) {
// Dialog is blocking normal page state
console.log("Dialog open:", info.dialog.type, info.dialog.message);
} else {
// Standard page info available
console.log("Page dimensions:", info.w, info.h);
}
When pendingDialog() returns a truthy value, pageInfo() short-circuits and returns { dialog } instead of the usual { url, w, h, scale, ... } metrics object. This pattern lets calling code branch immediately without additional state checks.
screenshot() in driver/observe.ts
The screenshot helper in src/driver/observe.ts uses dialog detection to avoid capture failures. Standard screenshots often fail or exclude modal dialogs that overlay the viewport. Ego-browser handles this transparently:
import { screenshot } from "driver/observe";
const path = await screenshot(); // Automatically adapts to dialog presence
Internally, screenshot() checks pendingDialog():
- No dialog: Proceed with normal screenshot logic
- Dialog detected: Set
raw: trueto capture the full viewport including the blocking dialog
This best-effort fallback ensures automation continues without throwing errors when dialogs appear unexpectedly.
Best-Effort Page Event Enablement
Dialog tracking depends on Page.enable CDP commands, which some internal Chrome pages (e.g., chrome:// URLs) reject. Ego-browser wraps this initialization in try/catch:
// From browser-runtime.ts - defensive enablement
try {
await sendCDP("Page.enable");
} catch {
// Continue without dialog tracking; don't break other features
}
This graceful degradation ensures core automation works even on restricted pages, with dialog tracking available where supported.
Summary
- Event-driven architecture: CDP's
Page.javascriptDialogOpeningandPage.javascriptDialogClosedprovide the foundation for real-time dialog detection - Session-scoped storage: The
pendingDialogsMap inbrowser-runtime.tsisolates state per CDP session - Public accessor:
pendingDialog()returns immutable snapshots of active dialog state - Adaptive consumers:
pageInfo()andscreenshot()indriver/nav.tsanddriver/observe.tsbranch their behavior based on dialog presence - Resilient design: Defensive
Page.enablehandling and automatic cleanup prevent errors and memory leaks
Frequently Asked Questions
How does ego-browser distinguish between alert, confirm, and prompt dialogs?
The type field in the dialog object returned by pendingDialog() contains the string "alert", "confirm", or "prompt" as reported by Chrome DevTools Protocol. This matches the JavaScript API that created the dialog, letting your code respond appropriately—for example, knowing that only prompt dialogs have a defaultPrompt value.
Can I dismiss or interact with dialogs programmatically through ego-browser?
The current implementation in browser-runtime.ts focuses on detection and observation rather than control. The pendingDialog() accessor is read-only; actual dismissal requires sending CDP commands directly (e.g., Page.handleJavaScriptDialog) or using UI automation to click dialog buttons. The detection API informs when such intervention is needed.
What happens to pending dialogs when a page navigates or closes?
The pendingDialogs Map automatically clears entries when the CDP session detaches or the target destroys, as handled in browser-runtime.ts. This prevents stale state from persisting across navigations or leaking memory during long-running automation sessions with many page lifecycles.
Does dialog tracking work on all page types?
Dialog tracking is best-effort—initialization attempts Page.enable but catches exceptions for restricted pages like chrome:// URLs. Standard http:// and https:// pages support full dialog tracking. Internal Chrome pages may lack CDP Page domain events, so pendingDialog() returns null unconditionally in those 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 →