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:

  • type equals "iframe"
  • url contains 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 interactions
  • resolveAxSession(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.

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:

  1. Inject the correct session based on the provided targetId
  2. Route the command to the specific iframe rather than the top-level page
  3. 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 targetId remains 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 iframeSessions for proper resource disposal

Summary

  • Discovery: Use iframeTarget(urlSubstring) in src/driver/nav.ts to locate iframe targets via Target.getTargets CDP command
  • Session resolution: The resolveFrameSession and resolveAxSession helpers in src/element-resolver.ts map targetId to active CDP sessions via the iframeSessions registry
  • Navigation: Pass the targetId to the cdp wrapper from src/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:

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 →