Skip to content

fix(browse): clear refs when iframe auto-detaches in getActiveFrameOrPage#1311

Open
yashkot007 wants to merge 1 commit intogarrytan:mainfrom
yashkot007:fix/clear-refs-on-iframe-detach
Open

fix(browse): clear refs when iframe auto-detaches in getActiveFrameOrPage#1311
yashkot007 wants to merge 1 commit intogarrytan:mainfrom
yashkot007:fix/clear-refs-on-iframe-detach

Conversation

@yashkot007
Copy link
Copy Markdown

@yashkot007 yashkot007 commented May 4, 2026

Summary

TabSession.getActiveFrameOrPage() (tab-session.ts:151) auto-recovers from detached iframes by setting activeFrame = null and silently falling back to the main page. The matching clearRefs() call is missing.

Asymmetric handling of two equivalent staleness conditions:

// Main-frame nav: refs + frame both cleared ✓
onMainFrameNavigated(): void {
  this.clearRefs();
  this.activeFrame = null;
  this.loadedHtml = null;
  this.loadedHtmlWaitUntil = undefined;
}

// Iframe detach: frame cleared, refs left behind ✗ (the bug)
getActiveFrameOrPage(): Page | Frame {
  if (this.activeFrame?.isDetached()) {
    this.activeFrame = null;
    // ← this.clearRefs() missing
  }
  return this.activeFrame ?? this.page;
}

Both paths see the same staleness condition — refs were captured against a frame that no longer exists. The main-frame path correctly clears both pieces of state; the iframe-detach path leaves refMap intact.

Why this matters

The lazy click-time check in resolveRef (tab-session.ts:97) partially saves us — entry.locator.count() on a detached-frame locator throws or returns 0, so a click against a stale ref errors out cleanly with Ref X is stale.

But the user has no signal that frame context silently changed underfoot:

  1. User did frame foosnapshot → got @e1, @e2, @e3 (refs against the iframe)
  2. Iframe detaches (page navigates the iframe URL, or removes the element)
  3. getActiveFrameOrPage() silently nulls activeFrame, falls back to this.page
  4. Next snapshot runs against the main page, generates new refs
  5. But the OLD iframe refs are still in refMap with the same role+name keys
  6. New refs collide with stale ones; resolver picks one at random
  7. User's click @e1 may target the wrong element on the main page

TODOS.md line 816-820 documents this region:

Iframe support — SHIPPED

$B frame ships in v0.12.1.0. […] Frame context cleared on navigation, tab switch, resume. Detached frame auto-recovery. Page-only operations […]

The fix restores the documented intent — auto-recovery should leave the session in a clean state, not a half-cleared one.

Fix

 getActiveFrameOrPage(): Page | Frame {
   if (this.activeFrame?.isDetached()) {
     this.activeFrame = null;
+    this.clearRefs();
   }
   return this.activeFrame ?? this.page;
 }

One line. Same symmetry as the main-frame nav path right below it.

Regression test

browse/test/tab-session-frame-detach.test.ts — pure-logic tests against TabSession directly, no browser launch (same pattern as tab-isolation.test.ts).

Four cases:

  1. Bug repro: refs cleared when getActiveFrameOrPage detects detached iframe (fails on main, passes with the fix)
  2. No regression on attached frame: refs preserved when active frame is still attached
  3. No regression on no-frame path: refs preserved when no frame is set (page-level snapshot)
  4. Design symmetry: onMainFrameNavigated() and the iframe-detach path reach the same clean end state — locks the symmetry so a future refactor splitting the two paths fails before merge

Test plan

  • New regression test: 4/4 pass
  • bun run build clean
  • Lazy click-time fallback still works — refs newly cleared, so the user simply gets a fresh snapshot context instead of a half-stale one

Related

Independent of #1309 (lastConsoleFlushed undeclared) and #1310 (concurrent state-write ENOENT). All three from the same stress-test session, but each touches a different file / subsystem and can land in any order.

🤖 Generated with Claude Code


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

…Page

Asymmetric cleanup between two equivalent staleness conditions:

  onMainFrameNavigated()  →  clearRefs() + activeFrame = null  ✓
  getActiveFrameOrPage()  →  activeFrame = null  (refs NOT cleared)  ✗

Both paths see the same staleness condition — refs were captured
against a frame that no longer exists. The main-frame path correctly
clears both pieces of state. The iframe-detach path nulls the frame
but leaves the refMap intact.

The lazy click-time check in `resolveRef` (tab-session.ts:97) partially
saves us — `entry.locator.count()` on a detached-frame locator throws
or returns 0, so the click errors out as "Ref X is stale". But the
user has no signal that frame context silently changed underfoot: the
next `snapshot` runs against `this.page` (main) while old iframe refs
still litter `refMap` with the same role+name keys. New refs collide
with stale ones, the resolver picks one at random, the user clicks
the wrong element.

TODOS.md line 816-820 documents "Detached frame auto-recovery" as a
shipped iframe-support feature in v0.12.1.0. This restores the
documented intent — the recovery should leave the session in a clean
state, not a half-cleared one.

Fix: 1 line — add `this.clearRefs()` next to `this.activeFrame = null`
inside the if-branch.

Test plan:
  - [x] New regression test: 4/4 pass
        - refs cleared when getActiveFrameOrPage detects detached iframe
        - refs preserved when active frame is still attached (no regression)
        - refs preserved when no frame set (page-level path untouched)
        - matches onMainFrameNavigated symmetry — both paths reach the
          same clean end state
  - [x] `bun run build` clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant