How Iframe Targets Are Resolved and Navigated in Ego-Browser: A Complete Technical Guide
Ego-Browser treats every iframe as a separate Chrome DevTools Protocol (CDP) Target, using a three-step pipeline of discovery via Target.getTargets, session mapping through an internal iframeSessions registry, and navigation via the cdp wrapper.
In modern browser automation, handling nested iframes is one of the most complex challenges. The citrolabs/ego-lite project solves this through a clean abstraction over Chrome's CDP that separates discovery from session management. Understanding how iframe target resolution works in ego-browser is essential for building reliable automation scripts that interact with cross-origin frames, embedded widgets, and sandboxed content.
Iframe Target Discovery in src/driver/nav.ts
The first step in resolving an iframe target is discovery. Ego-Browser locates the correct CDP Target using URL pattern matching against all available targets in the browser instance.
The iframeTarget(urlSubstring) function at lines 60-68 of [src/driver/nav.ts](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/nav.ts) implements this:
// Conceptual implementation based on source
async function iframeTarget(urlSubstring: string): Promise<string | null> {
// Query CDP for all targets
const { targetInfos } = await cdp('Target.getTargets');
// Filter for iframe types with matching URL
const match = targetInfos.find(
t => t.type === 'iframe' && t.url.includes(urlSubstring)
);
return match?.targetId ?? null;
}
This approach leverages the Target.getTargets CDP command, which returns all reachable targets including pages, background scripts, service workers, and iframes. The function filters for entries where:
typeequals"iframe"urlcontains the supplied substring
The matching targetId is returned, or null if no iframe matches. This design allows fuzzy matching without requiring exact URLs, which is practical since iframe URLs often contain dynamic parameters or hash fragments.
Session Mapping in src/element-resolver.ts
A targetId alone cannot execute CDP commands. Ego-Browser must map the target to an active CDP session—a dedicated communication channel for that specific target.
This mapping logic resides in lines 240-250 of [src/element-resolver.ts](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts). The file maintains an internal iframeSessions Map that associates frame IDs with their corresponding session objects.
Two key helper functions handle the resolution:
resolveFrameSession(frameId, sessionId, iframeSessions)– Resolves a session for standard frame interactionsresolveAxSession(frameId, sessionId, iframeSessions)– Resolves a session for accessibility tree operations
// Simplified representation of the resolution logic
function resolveFrameSession(
frameId: string,
sessionId: string,
iframeSessions: Map<string, CDPSession>
): CDPSession {
// Check dedicated iframe session map first
if (iframeSessions.has(frameId)) {
return iframeSessions.get(frameId)!;
}
// Fallback to default session
return getDefaultSession();
}
The iframeSessions registry is populated automatically when new iframes are attached to the page. This abstraction ensures that script authors never manually manage session lifecycle or target attachment events.
Navigation and Command Execution
With the session resolved, any CDP command can target the iframe directly. The cdp function exported from src/helpers.ts at line 81 automates this routing.
To navigate within an iframe, invoke:
await cdp('Page.navigate', { url: 'https://example.com/new-page' }, iframeTargetId);
The cdp wrapper performs three critical operations:
- Inject the correct session based on the provided
targetId - Route the command to the specific iframe rather than the top-level page
- Handle response resolution and error propagation
This design means the same cdp function works transparently for both main page and iframe targets—the session resolution happens automatically.
Complete Working Example
Here's a practical pattern for iframe navigation in ego-browser:
import { cdp, iframeTarget, waitForLoadState } from 'ego-browser';
// Step 1: Discover the iframe target
const iframeId = await iframeTarget('example.com');
if (!iframeId) {
throw new Error('Target iframe not found in active targets');
}
// Step 2: Navigate within the discovered iframe
await cdp('Page.navigate', {
url: 'https://example.com/internal-page'
}, iframeId);
// Step 3: Wait for navigation to complete
await waitForLoadState('load', { target: iframeId });
// Step 4: Execute further commands in iframe context
const title = await cdp('Runtime.evaluate', {
expression: 'document.title'
}, iframeId);
The target option in waitForLoadState ensures the load state waiter monitors the correct target's lifecycle events.
Architecture Comparison: Ego-Browser vs. Traditional Approaches
| Aspect | Traditional Playwright/Puppeteer | Ego-Browser Approach |
|---|---|---|
| Frame identification | Element handle + contentFrame() | Direct CDP target discovery |
| Session management | Implicit via element handles | Explicit iframeSessions registry |
| Navigation scope | Context-dependent | Explicit targetId parameter |
| Cross-origin frames | Requires frame detachment/reattachment | Native CDP target routing |
Ego-Browser's explicit target-passing model eliminates ambiguity about which frame receives commands, reducing flaky tests caused by frame detachment or navigation race conditions.
Performance and Reliability Considerations
The CDP-based approach in ego-browser provides several operational advantages for iframe handling:
- No DOM polling for frame elements – Discovery operates on the target registry, not rendered DOM
- Survives frame reattachment – The
targetIdremains valid across same-origin navigations - Native cross-origin support – CDP sessions bypass origin restrictions that plague element-based frame access
- Deterministic cleanup – Sessions are explicitly tracked in
iframeSessionsfor proper resource disposal
Summary
- Discovery: Use
iframeTarget(urlSubstring)insrc/driver/nav.tsto locate iframe targets viaTarget.getTargetsCDP command - Session resolution: The
resolveFrameSessionandresolveAxSessionhelpers insrc/element-resolver.tsmaptargetIdto active CDP sessions via theiframeSessionsregistry - Navigation: Pass the
targetIdto thecdpwrapper fromsrc/helpers.ts; automatic session injection routes commands to the correct iframe - Design benefit: Explicit target parameters eliminate ambiguity and race conditions common in implicit frame handling models
Frequently Asked Questions
How does ego-browser handle iframes that haven't finished loading?
The iframeTarget() function only returns targets already present in the CDP target list. For dynamically created iframes, implement a polling loop or listen for Target.targetCreated CDP events, then call iframeTarget() once the frame appears.
Can I interact with cross-origin iframes using this approach?
Yes. Because ego-browser uses CDP sessions rather than DOM access, cross-origin restrictions do not apply. The targetId provides direct protocol-level access regardless of origin boundaries, making this approach suitable for embedded third-party widgets and sandboxed content.
What happens if an iframe navigates to a new origin?
The targetId remains stable for same-origin navigations. For cross-origin navigations, Chrome typically creates a new process and target. Your script should re-discover the iframe using iframeTarget() after major navigation events, or implement an event listener for Target.targetInfoChanged.
Is the iframeSessions map exposed to user scripts?
No, iframeSessions is an internal implementation detail in src/element-resolver.ts. User scripts interact only through the cdp function and iframeTarget helper, which handle session lookup transparently. This encapsulation prevents accidental session corruption and ensures consistent lifecycle management.
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 →