How ego-browser Resolves Elements Inside IFrames: CDP Session Isolation Explained

ego-browser isolates each iframe in its own Chrome DevTools Protocol (CDP) session and uses a frame-aware resolver to route commands to the correct browsing context, enabling seamless interaction with nested elements.

Working with nested iframes in browser automation requires careful context management to prevent DOM queries from leaking into the wrong document. The citrolabs/ego-lite repository solves this through ego-browser, which implements isolated CDP sessions for each iframe. This architecture ensures that element resolution commands—whether triggered by CSS selectors, role-based locators, or @ref identifiers—always target the correct DOM context without manual session juggling.

The Architecture of IFrame Element Resolution

Per-IFrame Session Isolation

Unlike single-context automation tools, ego-browser creates a dedicated CDP session for every iframe. When the resolver encounters a selector or @ref belonging to an iframe, it routes all subsequent CDP commands—such as DOM.getBoxModel, DOM.resolveNode, or Accessibility.getFullAXTree—through the iframe's specific session rather than the top-level page. This isolation prevents selector collisions and ensures accurate coordinate calculations for mouse events inside nested frames.

Frame-Aware Reference Storage

The system maintains a refMap that associates each captured element reference (@ref) with its containing frameId. This mapping enables the resolver to immediately identify which browsing context owns the element before executing any DOM queries, as implemented in the resolution pipeline.

The Element Resolution Pipeline

When browser.click, browser.hover, or low-level resolution functions receive an iframe element, they execute this workflow:

  1. Identify the iframe context – The resolver queries refMap.get(refId) to retrieve the stored frameId associated with the element reference.
  2. Resolve the effective session – The resolveFrameSession(frameId, sessionId, iframeSessions) function looks up the iframe's CDP session in the iframeSessions Map, falling back to the parent page's session if no specific mapping exists.
  3. Execute CDP commands in context – All subsequent calls use the effective session ID returned by step 2, ensuring commands evaluate against the iframe's DOM rather than the top-level document.
  4. Handle stale references – If a cached backendNodeId becomes invalid, the system falls back to role-based lookup via findBackendNodeIdByRoleName or findBackendNodeIdsByRoleName, passing the frameId and iframeSessions to maintain context during the accessibility tree query.

Core Resolution Functions in element-resolver.ts

The implementation resides primarily in src/element-resolver.ts.

resolveFrameSession (lines 240-247) This helper selects the appropriate CDP session from the iframeSessions Map. It accepts a frameId, the parent sessionId, and the session mapping, returning the effective session ID for command routing.

resolveElementCenter (lines 70-85, 99-119) Before calling DOM.getBoxModel to calculate click coordinates, this function extracts the correct iframe session via resolveFrameSession. It returns the center coordinates (x, y) along with the sessionId used for the operation.

resolveElementObjectId (lines 56-71, 78-95) Similarly resolves the proper session before invoking DOM.resolveNode to obtain a JavaScript object reference for the element, ensuring the node resolution occurs within the correct browsing context.

resolveAxSession (lines 491-503) Dedicated to Accessibility tree queries, this function ensures that Accessibility.getFullAXTree and related commands execute within the iframe's session when resolving elements by role or accessibility properties.

Locating and Managing IFrame Sessions

To facilitate manual session management, ego-browser exposes browser.iframeTarget(frameSelector) in src/driver/nav.ts (lines 256-267) and src/helpers.ts (lines 781-782). This utility returns the target ID of an iframe whose URL matches a provided substring, allowing callers to create or reuse sessions for specific nested contexts.

Code Examples

High-Level API Usage

// Click a button inside an iframe whose URL contains "login"
const iframeId = await browser.iframeTarget('login'); // Returns session ID for the iframe
const sessions = new Map();                          // Map frameId → sessionId
sessions.set(iframeId, iframeId);                   // Store the iframe session

// Use a ref captured inside the iframe
await browser.click('@42', { iframeSessions: sessions });

// Or use a locator directly; resolver switches sessions automatically
await browser.click('css:button.submit', { iframeSessions: sessions });

Low-Level Resolver Access

import { resolveElementCenter } from './element-resolver.js';

const { x, y, sessionId } = await resolveElementCenter(
  cdp,                     // CDP client instance
  mainSessionId,           // Top-level page session
  refMap,                  // Map of @refs → element data (includes frameId)
  '@15',                   // Reference ID inside an iframe
  iframeSessions           // Map<frameId, iframeSessionId>
);

await cdp.sendRaw('Input.dispatchMouseEvent', {
  type: 'mousePressed',
  x, y,
  button: 'left',
}, sessionId);

Summary

  • ego-browser isolates each iframe in a dedicated CDP session to prevent context leakage between nested browsing contexts.
  • The resolveFrameSession function in src/element-resolver.ts maps frameId values to their corresponding session IDs, ensuring commands route correctly.
  • Element references stored in refMap include frameId metadata, enabling automatic context detection during resolution.
  • Fallback mechanisms like findBackendNodeIdByRoleName respect iframe boundaries by accepting frameId and iframeSessions parameters.
  • The browser.iframeTarget utility provides explicit session acquisition for iframes matching URL patterns.

Frequently Asked Questions

How does ego-browser track which iframe contains an element?

Each captured element reference in the refMap stores a frameId field indicating its containing iframe. When resolving an element by @ref, the system retrieves this metadata via refMap.get(refId) to determine the correct CDP session.

What happens if a backendNodeId becomes stale inside an iframe?

If the cached backendNodeId is invalid, the resolver falls back to role-based lookup using findBackendNodeIdByRoleName or findBackendNodeIdsByRoleName. These functions accept frameId and iframeSessions parameters to ensure the accessibility tree query runs within the correct iframe session.

Can I manually specify an iframe session for automation commands?

Yes. Use browser.iframeTarget(frameSelector) (implemented in src/driver/nav.ts) to obtain a session ID for an iframe matching a URL substring. Pass this via the iframeSessions Map option to browser.click, browser.hover, or other helpers to override automatic detection.

Where is the iframe session resolution logic implemented?

The core logic resides in src/element-resolver.ts, specifically within resolveFrameSession (lines 240-247) for session selection, and resolveElementCenter (lines 70-119) and resolveElementObjectId (lines 56-95) for element-specific resolution. Accessibility-specific session handling is implemented in resolveAxSession (lines 491-503).

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 →