fix(understudy): prune descendant frame ownership and cached frame wrappers on detach and close

PR
2d ago
+434-30

Why

Closing a page or detaching a frame subtree in BrowserContext left child frame entries in frameOwnerPage and retained cached Frame wrappers, accumulating stale references across repeated page opens and closes (#3240). Process-swap detaches could also remove frames before their replacement session took ownership.

What changed

  • In FrameRegistry, collect and return all detached subtree frame IDs, track swapping state during process swaps so topology is preserved until the replacement session attaches, and clean up session frames in detachSession.
  • In Page, invalidate WebMCP state, frame cache, and frame ordinals for the detached frame and all its descendants. In dispose(), clear event disposers and caches safely even if individual disposers throw.
  • In BrowserContext, sync frame ownership during tree seeding, prune descendant frame entries from frameOwnerPage on detachment and page destruction, and dispose open pages during context shutdown.
  • Added 4 unit tests in understudy-context-oopif-adoption.test.ts covering 100-page nested-frame open/close cycles, subtree detach cleanup, process swaps, and resilient error recovery.

Test plan

  • pnpm exec vitest run packages/extension/tests/understudy-context-oopif-adoption.test.ts passes (23/23 tests, including 100 nested page open/close cycles leaving zero stale entries in frameOwnerPage).
  • pnpm exec vitest run packages/extension/tests/understudy-context-domain-policy.test.ts packages/extension/tests/page-webmcp-tools.test.ts packages/extension/tests/page-webmcp-invocations.test.ts packages/extension/tests/page-cdp-events.test.ts passes (89/89 tests).
  • pnpm -F @browserbasehq/stagehand-extension typecheck and oxfmt check pass with 0 errors.

Closes #3240


Summary by cubic

Fixes stale frame ownership and cached Frame wrappers accumulating in BrowserContext when pages close or frame subtrees detach, which piled up across repeated page open/close cycles (#3240). Process-swap detaches now keep frame topology intact until the replacement session takes ownership.

  • FrameRegistry returns all detached subtree frame IDs, tracks mid-swap frames, and prunes session-owned frames in detachSession.
  • Page invalidates WebMCP state, frame cache, and frame ordinals for the whole detached subtree; dispose() clears caches and disposers safely even if an individual disposer throws.
  • BrowserContext syncs frame ownership during tree seeding, prunes descendant entries from frameOwnerPage on detach and page destruction, and disposes open pages during context shutdown.
  • Added 4 unit tests covering 100 nested-frame open/close cycles, subtree detach cleanup, process swaps, and close-time error recovery.

Closes #3240.