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

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)
This commit is contained in:
Yashwant Kotipalli
2026-05-03 17:43:00 -07:00
parent bf65487162
commit 369c7f230f
2 changed files with 162 additions and 1 deletions
+8 -1
View File
@@ -149,9 +149,16 @@ export class TabSession {
* Use this for operations that work on both Page and Frame (locator, evaluate, etc.).
*/
getActiveFrameOrPage(): Page | Frame {
// Auto-recover from detached frames (iframe removed/navigated)
// Auto-recover from detached frames (iframe removed/navigated). Clear
// refs alongside the activeFrame — same staleness condition as
// onMainFrameNavigated() below: refs were captured against a frame
// that no longer exists. Without this, refMap entries linger against
// a dead frame after silently falling back to the main page; the
// next snapshot's role+name keys collide with stale entries and the
// resolver picks one at random.
if (this.activeFrame?.isDetached()) {
this.activeFrame = null;
this.clearRefs();
}
return this.activeFrame ?? this.page;
}