[fix]: adopt pre-existing and nested cross-origin iframes
why
BrowserContext only adopts an OOPIF session when its main frame is already in a page's frame graph, or when the parent session later emits Page.frameAttached for it. Two cases never get that event, so the child session stays in pendingOopifByMainFrame forever and snapshots/act can't see inside the iframe:
- Bootstrapping against tabs that already contain a loaded cross-origin iframe (reconnect, or an extension service-worker restart): the parent's
Page.getFrameTreeomits remote frames, and no newframeAttachedfires. - An OOPIF nested inside another OOPIF.
Two related gaps:
- A parent
Page.frameAttachedthat arrives after the OOPIF was adopted gives ownership back to the parent session (FrameRegistry.onFrameAttachedtrusts the emitter). - A
frameAttachedthat adds the frame a pending OOPIF is waiting on (e.g. a same-site wrapper frame) didn't retry the other pending OOPIFs.
Repro: open a page with a cross-origin iframe, then start or reconnect Stagehand against that tab. The iframe's frame id never appears in the page's frame tree, and its contents are missing from snapshots.
what changed
- The child-attach path falls back to the page that owns the child root's
parentId, taken from the child's ownPage.getFrameTree. If that page isn't known yet, the parent id is recorded, and the child is adopted once the page registers, a parent OOPIF is adopted, or aframeAttachedreports the parent frame. This also covers nesting. - Adopted OOPIF sessions are tracked per frame, so a later parent
frameAttachedno longer takes ownership while the child session is alive. - Consolidated the owner lookup into
findPageOwningFrameand the adoption steps intoadoptOopif. - Regenerated the embedded Go extension asset (
packages/extension/artifacts/stagehand-extension.zipāsdk-go/internal/extensionassets; manifest version 1.1.1 matches). Go isn't installed locally, so I copied the zip by hand instead of runningextensionpack. Please let CI'sextensionpack --checkconfirm it.
test plan
- New
packages/extension/tests/understudy-context-oopif-adoption.test.tsdrives the realBrowserContext+CdpConnectionover a scripted fake transport. Cases: pre-existing iframe; iframe attaching before its page registers; late parentframeAttachedkeeps the child owner; nested OOPIF; waiting OOPIFs adopted once their wrapper frame is reported. Each fails onmainwithout its part of the fix. - The same logic was validated against real headless Chrome as a patch on 3.5.0.
pnpm check(43/43) and extension unit tests (1008 passed). Go and Python checks weren't run locally.
š¤ Generated with Claude Code
Summary by cubic
Fixes adoption of cross-origin iframes that were already loaded when Stagehand attached (reconnect, extension restarts) or that are nested inside another cross-origin iframe, so snapshots and actions can see inside them.
Bug Fixes
- The child attach path now falls back to the page that owns the child's parent frame, and retries all pending OOPIFs once that page registers or reports the frame.
- A late parent
Page.frameAttachedno longer takes ownership of an already-adopted OOPIF session. - Tests drive the real
BrowserContextover a scripted fake transport; each case fails onmain. - The embedded Go extension asset was regenerated, including a merge from
mainthat resolved its zip; CI'sextensionpack --checkshould verify it.