# How to Handle Iframe Targeting and Nested Browsing Contexts in ego-browser

> Master iframe targeting and nested browsing contexts in ego-browser. Learn to discover CDP targets by URL and route commands effectively for advanced browser automation.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: how-to-guide
- Published: 2026-08-06

---

**To handle iframe targeting in ego-browser, use `browser.iframeTarget(substring)` to discover a CDP target by URL, then attach to it with `Target.attachToTarget` to create a session that routes all subsequent commands into the nested browsing context.**

Working with nested frames in browser automation is notoriously complex. The `ego-browser` package in the `citrolabs/ego-lite` repository simplifies this by exposing every iframe as a separate Chrome DevTools Protocol (CDP) target with its own `targetId`. This architecture lets you discover, attach to, and control iframes using the same high-level API you use for top-level pages.

## Discovering Iframe Targets with iframeTarget

The entry point for iframe handling is the `iframeTarget` method. According to the source code in [`driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/nav.ts), this helper scans all available CDP targets and returns the `targetId` of the first iframe whose URL contains your supplied substring.

The function is re-exported from [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts) for convenient access:

```javascript
import { browser } from 'ego-browser';

// Find an iframe with "checkout" in its URL
const iframeId = await browser.iframeTarget('checkout');
console.log('Found iframe target:', iframeId);

```

This is more reliable than DOM-based iframe detection because it operates at the browser target level, catching dynamically created iframes that may not yet be in the DOM tree.

## Attaching to a Nested Browsing Context

Once you have the `targetId`, you must create a CDP session bound to that specific target. The `cdp` helper—also exported from [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts)—sends the `Target.attachToTarget` command:

```javascript
import { cdp } from 'ego-browser';

const { sessionId } = await cdp('Target.attachToTarget', {
  targetId: iframeId,
  flatten: true,  // Required for modern session handling
});

console.log('Attached session:', sessionId);

```

The `sessionId` returned in the response is the key to **iframe targeting**. All subsequent CDP commands that include this `sessionId` are automatically routed to the iframe's browsing context rather than the main page.

## Executing Commands Inside Iframes

With an active session, you can run any CDP command in the nested context. The `ego-browser` runtime handles the routing transparently:

```javascript
// Evaluate JavaScript inside the iframe
const result = await cdp('Runtime.evaluate', {
  expression: 'document.title',
  returnByValue: true,
  sessionId,  // Routes to iframe context
});

console.log('Iframe title:', result.result.value);

```

This works for any CDP domain: `DOM.querySelector`, `Input.dispatchMouseEvent`, `Network.*`, and more. The `sessionId` parameter is what distinguishes **nested browsing context** commands from main page commands.

## Automatic Session Management in Higher-Level APIs

The runtime maintains an internal mapping of iframe targets to sessions. In [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts), the `iframeSessions` Map stores these relationships. This enables transparent operation through the high-level API without manual session tracking:

```javascript
// Attach once, then use standard API
const iframeId = await browser.iframeTarget('checkout');
await cdp('Target.attachToTarget', { 
  targetId: iframeId, 
  flatten: true 
});

// These now automatically run inside the iframe
const title = await page.evaluate(() => document.title);
const button = await page.locator('#submit');
await button.click();

```

The `page.locator` and `page.evaluate` methods query the `iframeSessions` map in [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) to determine the correct execution context for each operation.

## Cleaning Up Iframe Sessions

When finished with an iframe target, explicitly detach to free resources:

```javascript
await cdp('Target.detachFromTarget', { sessionId });

```

If omitted, the runtime in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) handles cleanup on process termination, but explicit detachment is recommended for long-running automation scripts with many frame transitions.

## Complete Working Example

Here's a full pattern for reliable **iframe targeting**:

```javascript
import { browser, cdp, page } from 'ego-browser';

async function interactWithIframe() {
  // Step 1: Discover
  const iframeId = await browser.iframeTarget('payment-form');
  
  // Step 2: Attach
  const { sessionId } = await cdp('Target.attachToTarget', {
    targetId: iframeId,
    flatten: true,
  });
  
  // Step 3: Interact (low-level CDP)
  await cdp('Input.insertText', {
    text: '4111111111111111',
    sessionId,
  });
  
  // Or use high-level API (automatic context detection)
  const submitButton = await page.locator('.pay-btn');
  await submitButton.click();
  
  // Step 4: Cleanup
  await cdp('Target.detachFromTarget', { sessionId });
}

await interactWithIframe();

```

## Key Source Files for Iframe Handling

| File | Purpose |
|------|---------|
| [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts) | Re-exports `iframeTarget` and generic `cdp` helper |
| [`driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/nav.ts) | Implements `iframeTarget` by filtering `Target.getTargets` |
| [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) | Manages CDP session lifecycle including attach/detach |
| [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) | Maintains `iframeSessions` Map for automatic context routing |

## Summary

- **`browser.iframeTarget(substring)`** in [`driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/nav.ts) discovers iframe CDP targets by URL pattern
- **`cdp('Target.attachToTarget', { targetId, flatten: true })`** creates a session for a nested browsing context
- **The `sessionId`** routes all subsequent commands to the iframe instead of the main page
- **[`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts)** enables transparent high-level API usage after attachment
- **Explicit `Target.detachFromTarget`** releases resources when done

## Frequently Asked Questions

### How does ego-browser identify iframes compared to Puppeteer or Playwright?

Unlike Puppeteer/Playwright, which abstract iframes as page-like objects, `ego-browser` exposes the underlying Chrome DevTools Protocol target structure directly. Every frame—including main pages, subframes, and worker contexts—is a separate CDP target with a unique `targetId`. The `iframeTarget` helper filters these targets by URL substring, giving you precise control without framework abstractions.

### Can I attach to multiple nested iframes simultaneously?

Yes. Each `Target.attachToTarget` call returns a distinct `sessionId`. The `iframeSessions` Map in [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) tracks these independently. You can maintain active sessions for multiple iframes and switch between them by using the appropriate `sessionId` in CDP calls, or rely on automatic resolution through `page.locator` when selectors uniquely identify elements in specific frames.

### What happens if an iframe navigates to a new URL after I attach?

The `sessionId` remains valid across navigations within the same `targetId`. However, if the iframe is destroyed and recreated (common with single-page applications), the original `targetId` becomes invalid. You must call `iframeTarget` again to discover the new target and create a fresh session. The runtime does not automatically remap destroyed frames.

### Is the flatten: true parameter required when attaching to iframes?

Yes. Modern CDP uses **flattened sessions** where commands directly specify `sessionId` rather than nesting through `Target.sendMessageToTarget`. The `ego-browser` implementation in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) assumes flattened mode. Omitting `flatten: true` will cause session routing failures for all subsequent commands.