How Does Ego-Lite Track JavaScript Dialogs: CDP Event Handling Explained
Ego-Lite tracks JavaScript dialogs by maintaining a per-session Map called pendingDialogs in the browser runtime that listens to Chrome DevTools Protocol (CDP) events Page.javascriptDialogOpening and Page.javascriptDialogClosed, exposing the state through a pendingDialog() accessor used by navigation and screenshot helpers.
The citrolabs/ego-lite repository provides a lightweight browser automation framework that handles modal interruptions without blocking execution. Understanding how it tracks JavaScript dialogs reveals the tight integration between the Node.js runtime and Chrome's DevTools Protocol, enabling agents to detect alerts, confirms, prompts, and before-unload dialogs in real time.
The Per-Session Dialog Storage Mechanism
At the core of ego-lite's dialog detection is a centralized state container defined in src/browser-runtime.ts. On line 24, the runtime initializes a Map named pendingDialogs that persists throughout the browser session.
This map uses the CDP session ID as its key, ensuring that dialog state remains isolated when managing multiple browser contexts or tabs simultaneously. Each entry stores the raw dialog parameters received from the browser, including the dialog type (alert, confirm, prompt, or beforeunload), message text, and default prompt value.
Processing CDP Dialog Events
The runtime captures dialog lifecycle events through the handleMessage function located at lines 66-76 of src/browser-runtime.ts. This function acts as the central message router for all CDP traffic and specifically watches for two critical events:
-
Page.javascriptDialogOpening(lines 66-70): When the browser fires this event, the runtime extractsdata.paramsand stores it inpendingDialogsunder the current session ID, flagging that a modal is blocking the page. -
Page.javascriptDialogClosed(lines 71-75): When the dialog dismisses—whether through user action or automation—the runtime deletes the corresponding entry from the map, clearing the pending state.
This event-driven architecture ensures the internal state always mirrors the browser's actual UI state without polling or synthetic waits.
Retrieving Dialog State
Consumer modules access the tracked dialog through the pendingDialog(sessionId?) method defined at lines 98-104 of src/browser-runtime.ts. This public API returns a shallow copy of the stored dialog object for the specified session, or null when no dialog is present.
The optional sessionId parameter allows multi-session agents to query specific browser contexts. When omitted, the method defaults to the current active session, providing a safe, read-only view of the dialog data without exposing the internal map reference.
Consumer Integration Patterns
Ego-lite integrates dialog awareness into high-level automation primitives, ensuring agents can react appropriately when modals interrupt page interactions.
Navigation Handling in nav.ts
The pageInfo() function in src/driver/nav.ts (lines 84-90) demonstrates defensive dialog checking. Before gathering standard page metrics like URL or title, it first calls pendingDialog(). If a dialog exists, the function immediately returns an object containing the dialog details instead of page information, allowing the caller to handle the modal before proceeding with navigation.
Screenshot Safety in observe.ts
In src/driver/observe.ts, the screenshot() function (lines 112-125) implements dialog-aware capture logic. If pendingDialog() returns a truthy value, the function recursively invokes itself with the raw: true option to bypass automatic viewport cropping. This prevents the screenshot from capturing a partially obscured or incorrectly calculated viewport while a modal dialog is open.
Practical Implementation Examples
Detect a dialog while gathering page information:
import { pageInfo } from 'ego-browser/src/driver/nav.js';
async function showInfo() {
const info = await pageInfo();
if ('dialog' in info) {
console.log('A dialog is open:', info.dialog);
} else {
console.log('Page URL:', info.url);
}
}
Wait for dialog dismissal before capturing the screen:
import { screenshot } from 'ego-browser/src/driver/observe.js';
import { pendingDialog } from 'ego-browser/src/browser-runtime.js';
async function safeScreenshot() {
// Wait until any open dialog disappears
while (pendingDialog()) {
await new Promise(r => setTimeout(r, 100));
}
const path = await screenshot({ fullPage: true });
console.log('Saved screenshot to', path);
}
Retrieve dialog details manually:
import { pendingDialog } from 'ego-browser/src/browser-runtime.js';
const dlg = pendingDialog();
if (dlg) {
console.log(`Dialog type: ${dlg.type}, message: ${dlg.message}`);
}
Summary
- Ego-lite tracks JavaScript dialogs using a
pendingDialogsMap insrc/browser-runtime.tskeyed by CDP session ID. - CDP events drive state updates:
Page.javascriptDialogOpeningstores dialog data (lines 66-70), whilePage.javascriptDialogClosedremoves it (lines 71-75). - The
pendingDialog()method (lines 98-104) provides safe, read-only access to current dialog state. - Navigation and screenshot utilities check for pending dialogs to prevent automation errors and ensure accurate page captures.
- Session-scoped storage ensures dialog tracking works correctly across multiple parallel browser contexts.
Frequently Asked Questions
What CDP events does ego-lite use to detect JavaScript dialogs?
Ego-lite listens to two Chrome DevTools Protocol events: Page.javascriptDialogOpening to store dialog parameters when a modal appears, and Page.javascriptDialogClosed to clear the state when the dialog dismisses. These events are handled in the handleMessage function at lines 66-76 of src/browser-runtime.ts.
How does ego-lite handle multiple browsing sessions with dialogs?
The pendingDialogs Map uses CDP session IDs as keys, isolating dialog state per browser context. When calling pendingDialog(sessionId?), you can specify a particular session, or the method will default to the current active session, ensuring that dialogs in one tab do not interfere with automation in another.
Can I check for dialogs without consuming them?
Yes. The pendingDialog() method returns a shallow copy of the dialog data without modifying the internal pendingDialogs Map or affecting the browser state. The dialog remains tracked and open until the user or automation dismisses it through CDP commands.
How does ego-lite prevent screenshots from capturing incorrectly when dialogs are open?
The screenshot() function in src/driver/observe.ts (lines 112-125) checks pendingDialog() before capturing. If a dialog is detected, it recursively calls itself with raw: true to disable viewport cropping, ensuring the screenshot captures the full page including the modal overlay rather than a cropped region.
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 →