# How Iframe Targets Are Resolved and Navigated in Ego-Browser: A Complete Technical Guide

> Discover how Ego Browser resolves and navigates iframe targets with a detailed technical guide. Learn about target discovery, session mapping, and navigation.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: deep-dive
- Published: 2026-08-02

---

**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`](https://github.com/citrolabs/ego-lite/blob/main/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/src/driver/nav.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/nav.ts)** implements this:

```typescript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/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/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

```typescript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)** at **line 81** automates this routing.

To navigate within an iframe, invoke:

```javascript
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:

```javascript
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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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.