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:
- Identify the iframe context – The resolver queries
refMap.get(refId)to retrieve the storedframeIdassociated with the element reference. - Resolve the effective session – The
resolveFrameSession(frameId, sessionId, iframeSessions)function looks up the iframe's CDP session in theiframeSessionsMap, falling back to the parent page's session if no specific mapping exists. - 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.
- Handle stale references – If a cached
backendNodeIdbecomes invalid, the system falls back to role-based lookup viafindBackendNodeIdByRoleNameorfindBackendNodeIdsByRoleName, passing theframeIdandiframeSessionsto 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
resolveFrameSessionfunction insrc/element-resolver.tsmapsframeIdvalues to their corresponding session IDs, ensuring commands route correctly. - Element references stored in
refMapincludeframeIdmetadata, enabling automatic context detection during resolution. - Fallback mechanisms like
findBackendNodeIdByRoleNamerespect iframe boundaries by acceptingframeIdandiframeSessionsparameters. - The
browser.iframeTargetutility 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →