[fix]: ctx.addInitScript() iframe issues#1664
Conversation
🦋 Changeset detectedLatest commit: 40d65b9 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
No issues found across 6 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Architecture diagram
sequenceDiagram
participant B as Browser / CDP
participant C as V3Context
participant S as CDPSession (Page/OOPIF)
participant P as Piercer / ExecutionContext
Note over B,S: Target Attachment & Init Script Flow
B-->>C: Target.attachedToTarget (Target Paused)
C->>C: NEW: installTargetSessionListeners()
rect rgb(23, 37, 84)
Note right of C: Session Initialization (onAttachedToTarget)
C->>S: Page.enable()
C->>S: Runtime.enable()
C->>S: NEW: Target.setAutoAttach(waitForDebuggerOnStart: true, flatten: true)
Note right of S: Ensures nested OOPIFs also pause on start
opt Has Init Scripts
C->>S: CHANGED: Page.addScriptToEvaluateOnNewDocument(source, runImmediately: true)
end
C->>S: NEW: Runtime.runIfWaitingForDebugger()
Note right of S: Target resumes ONLY after scripts are registered
end
Note over C,P: Piercer Installation Flow
C->>S: NEW: Page.getFrameTree()
S-->>C: frameTree (All nested frames)
loop Each Frame in Tree
C->>P: NEW: waitForMainWorld(frameId)
P-->>C: executionContextId
C->>S: Runtime.evaluate(v3ScriptContent, contextId)
C->>S: Runtime.evaluate(reRenderScriptContent, contextId)
end
B-->>C: NEW: Page.frameNavigated (Event)
C->>P: Re-install piercer in new main world context
alt Unhappy Path: Session Closed
S-->>C: Error: Session with given id not found
C->>C: Cleanup session listeners & tracking
end
Greptile OverviewGreptile SummaryThis PR changes Key issues to address before merge are around lifecycle cleanup and piercer installation tracking: the new per-session Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
autonumber
participant Chrome as Chrome/CDP
participant Conn as CdpConnection (root)
participant Ctx as V3Context
participant Sess as Target Session (page/iframe)
participant Page as Page (owner)
Chrome->>Conn: Target.attachedToTarget(targetInfo, sessionId)
Conn->>Ctx: onAttachedToTarget(targetInfo, sessionId)
Ctx->>Conn: getSession(sessionId)
Conn-->>Ctx: Sess
Ctx->>Sess: Target.setAutoAttach(autoAttach=true, waitForDebuggerOnStart=true, flatten=true)
Ctx->>Sess: Page.enable
Ctx->>Sess: Runtime.enable
loop for each ctx initScript
Ctx->>Sess: Page.addScriptToEvaluateOnNewDocument(source, runImmediately=true)
end
Ctx->>Sess: Page.addScriptToEvaluateOnNewDocument(v3ScriptContent, runImmediately=true)
Ctx->>Sess: Runtime.runIfWaitingForDebugger (resume)
alt top-level page target
Ctx->>Page: Page.create(conn, session, targetId)
Ctx->>Ctx: installFrameEventBridges(sessionId, Page)
Ctx->>Page: applyInitScriptsToPage(seedOnly=scriptsInstalled)
else OOPIF/iframe target
Ctx->>Sess: Page.getFrameTree
Sess-->>Ctx: childMainFrameId
Ctx->>Page: adoptOopifSession(session, childMainFrameId) (if owner found)
Ctx->>Ctx: installFrameEventBridges(sessionId, owner)
end
Chrome-->>Sess: Target.detachedFromTarget(sessionId, targetId)
Sess->>Ctx: onDetachedFromTarget(sessionId, targetId)
Ctx->>Page: detachOopifSession(sessionId)
Ctx->>Ctx: cleanup maps / sets
|
There was a problem hiding this comment.
No issues found across 4 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Architecture diagram
sequenceDiagram
participant Ctx as V3Context
participant Conn as CdpConnection
participant RootSess as CDPSession (Page)
participant SubSess as CDPSession (OOPIF/Frame)
participant Browser as Browser Target
Note over Ctx,Browser: Target Initialization & Auto-Attach Cascade
Conn->>Ctx: Target.targetCreated (Page)
Ctx->>RootSess: Initialize Session
rect rgb(23, 37, 84)
Note right of Ctx: NEW: Session Bootstrap (Ordered for Timing)
Ctx->>RootSess: Page.enable / Runtime.enable
Ctx->>RootSess: CHANGED: Target.setAutoAttach (waitForDebuggerOnStart: true)
loop For each Init Script
Ctx->>RootSess: NEW: Page.addScriptToEvaluateOnNewDocument (runImmediately: true)
end
Ctx->>RootSess: NEW: Register Piercer via addScriptToEvaluateOnNewDocument
Ctx->>RootSess: CHANGED: Runtime.runIfWaitingForDebugger (Resume)
end
Browser-->>RootSess: Target.attachedToTarget (Nested OOPIF)
rect rgb(23, 37, 84)
Note over Ctx,SubSess: NEW: Hierarchical Attachment Propagation
RootSess->>Ctx: NEW: Target.attachedToTarget Event
Ctx->>SubSess: Initialize Session (Recursive)
Ctx->>SubSess: Page.enable / Runtime.enable
Ctx->>SubSess: NEW: Target.setAutoAttach (waitForDebuggerOnStart: true)
Note over SubSess,Browser: Frame is paused by DevTools
Ctx->>SubSess: Page.addScriptToEvaluateOnNewDocument (Init Scripts)
Ctx->>SubSess: Page.addScriptToEvaluateOnNewDocument (Piercer Script)
Ctx->>SubSess: CHANGED: Runtime.runIfWaitingForDebugger (Resume Subframe)
end
Note over Ctx,SubSess: Cleanup Flow
SubSess->>Ctx: Target.detachedFromTarget
Ctx->>Ctx: Remove Session Listeners & Cleanup Sets
Additional Comments (1)
Prompt To Fix With AIThis is a comment left during a code review.
Path: packages/core/lib/v3/understudy/context.ts
Line: 617:636
Comment:
`_targetSessionListeners` never cleaned up on session detach. In long-lived contexts with many session attach/detach cycles, this could accumulate stale session IDs.
```suggestion
private onDetachedFromTarget(
sessionId: SessionId,
targetId: string | null,
): void {
const owner = this.sessionOwnerPage.get(sessionId);
if (owner) {
owner.detachOopifSession(sessionId);
this.sessionOwnerPage.delete(sessionId);
}
if (targetId && this.pagesByTarget.has(targetId)) {
this.cleanupByTarget(targetId);
}
for (const [fid, sid] of Array.from(
this.pendingOopifByMainFrame.entries(),
)) {
if (sid === sessionId) this.pendingOopifByMainFrame.delete(fid);
}
this._targetSessionListeners.delete(sessionId);
}
```
How can I resolve this? If you propose a fix, please make it concise. |
Additional Comments (2)
Prompt To Fix With AIThis is a comment left during a code review.
Path: packages/core/lib/v3/understudy/context.ts
Line: 458:463
Comment:
**Double resume call**
`resume()` is added to `installPromises` (line ~504) *and* called again unconditionally in the `finally` block (line ~607). Since `resume()` is guarded by a local `resumed` flag, this won’t deadlock, but it also means the `finally` resume can run before the init-script `Promise.allSettled(installPromises)` completes if an exception is thrown after the `try` (e.g. `ensurePiercer()` early-returns), defeating the stated goal of “resume after init scripts are registered”. Consider making `finally` resume conditional on `resumed` state *and* ensuring the only resume happens after init-script registration finishes, even on early returns.
How can I resolve this? If you propose a fix, please make it concise.
These tests use a fixed Prompt To Fix With AIThis is a comment left during a code review.
Path: packages/core/lib/v3/tests/iframe-ctx-addInitScript.spec.ts
Line: 38:40
Comment:
**Flaky sleep-based wait**
These tests use a fixed `setTimeout(1000)` to “wait for iframe to load”. With the suite now unskipped, this will be flaky across CI / Browserbase. Prefer a deterministic wait (e.g. wait until `page.frames().length > 1`, wait for a known selector in the iframe, or poll the computed background color in the child frame until it matches).
How can I resolve this? If you propose a fix, please make it concise. |
There was a problem hiding this comment.
1 issue found across 3 files
Confidence score: 4/5
- This PR is likely safe to merge; the only noted issue is low-to-moderate severity and localized in session cleanup logic.
- In
packages/core/lib/v3/understudy/context.ts,_piercerInstalledis deleted using rawsessionIdwhile insertion usessessionKey(session), which could leave stale entries ifsession.idis null or differs, leading to inconsistent cleanup. - Pay close attention to
packages/core/lib/v3/understudy/context.ts- ensure consistent keying for_piercerInstalledinsert/delete.
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="packages/core/lib/v3/understudy/context.ts">
<violation number="1" location="packages/core/lib/v3/understudy/context.ts:639">
P2: Inconsistent key used when deleting from `_piercerInstalled`. The set is populated using `sessionKey(session)` which returns `session.id ?? "root"`, but deletion uses raw `sessionId`. Use consistent keying to ensure proper cleanup: `this._piercerInstalled.delete(this.sessionKey({ id: sessionId } as CDPSessionLike));` or use raw `sessionId` consistently in both add and delete operations.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Client as User / Test Code
participant Ctx as V3Context
participant Conn as CdpConnection
participant Session as CDPSession (Page/OOPIF)
participant Browser as Browser Target
Note over Client,Browser: Context & Script Setup
Client->>Ctx: addInitScript(source)
Ctx->>Ctx: Store source in initScripts[]
Note over Ctx,Browser: Target Discovery & Initialization
Conn->>Ctx: Target.attachedToTarget (TargetInfo, SessionId)
Ctx->>Ctx: NEW: installTargetSessionListeners()
Ctx->>Session: Page.enable()
Ctx->>Session: Runtime.enable()
Note over Ctx,Session: Configuration (Before Code Execution)
Ctx->>Session: NEW: Target.setAutoAttach(waitForDebuggerOnStart: true)
Note right of Session: Ensures sub-frames (OOPIFs) pause before execution
loop For each script in initScripts
Ctx->>Session: NEW: Page.addScriptToEvaluateOnNewDocument(runImmediately: true)
end
Ctx->>Session: NEW: Register Shadow-DOM Piercer (Init Script)
Note right of Session: runImmediately: true ensures shadow-hooks lead page scripts
Note over Ctx,Browser: Execution Resume
Ctx->>Session: CHANGED: Runtime.runIfWaitingForDebugger()
Session-->>Browser: Target starts executing scripts
Note over Session,Browser: Nested Iframe / OOPIF Flow
Browser->>Session: Target.attachedToTarget (Nested Iframe)
Session->>Ctx: NEW: Propagate Event (Session-tracked listener)
Ctx->>Ctx: Recursive Attachment Flow (Paused by auto-attach)
Ctx->>Ctx: Repeat Session Initialization for Sub-target
Note over Ctx,Browser: Cleanup
Browser-->>Session: Target.targetDestroyed
Session-->>Ctx: Event
Ctx->>Ctx: NEW: cleanupByTarget(targetId) & clear session listeners
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Review SummaryThis PR successfully fixes Key Changes Review1. Init Script Registration with
|
There was a problem hiding this comment.
No issues found across 3 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Architecture diagram
sequenceDiagram
participant CTX as V3Context
participant CDP as CDP Session (Page/OOPIF)
participant BROWSER as Browser Engine
participant JS as Page Runtime
Note over CTX,BROWSER: Target Lifecycle & Init Script Injection
BROWSER->>CTX: Target.attachedToTarget (New Page or OOPIF)
rect rgb(23, 37, 84)
Note right of CTX: NEW: Bootstrap Session (initSession)
CTX->>CDP: Page.enable / Runtime.enable
CTX->>CDP: CHANGED: Target.setAutoAttach<br/>(autoAttach: true, waitForDebuggerOnStart: true)
Note over CDP,BROWSER: Pauses execution of the new target immediately
loop For each Init Script
CTX->>CDP: CHANGED: Page.addScriptToEvaluateOnNewDocument<br/>(source, runImmediately: true)
end
CTX->>CDP: NEW: Page.addScriptToEvaluateOnNewDocument<br/>(piercerScript, runImmediately: true)
CTX->>CDP: CHANGED: Runtime.runIfWaitingForDebugger
Note over BROWSER,JS: Target Resumes
end
alt NEW: Piercer Pre-registered successfully
CTX->>CTX: Mark session piercer as installed
else Registration failed
CTX->>JS: Fallback: installV3PiercerIntoSession (standard eval)
end
Note over CTX,BROWSER: OOPIF / Nested Iframe Flow
BROWSER->>CDP: Target.attachedToTarget (Sub-target)
Note over CDP: NEW: auto-attach cascade ensures<br/>nested session is also paused
CDP-->>CTX: Emit attachedToTarget for nested session
CTX->>CTX: NEW: installTargetSessionListeners(nestedSession)
CTX->>CTX: Recursively execute Bootstrap Session (above)
Note over CTX,JS: Result: Scripts run in all frames before page JS executes.
deactivate CTX
Note over CTX: NEW: Cleanup on Detach
BROWSER->>CTX: Target.detachedFromTarget
CTX->>CTX: Remove session listeners & tracking sets
|
@seanmcguire12 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
No issues found across 3 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Architecture diagram
sequenceDiagram
participant CTX as V3Context
participant CDP as CDP Session (Page/OOPIF)
participant BROWSER as Browser Engine
participant JS as Page Runtime
Note over CTX,BROWSER: Target Lifecycle & Init Script Injection
BROWSER->>CTX: Target.attachedToTarget (New Page or OOPIF)
rect rgb(23, 37, 84)
Note right of CTX: NEW: Bootstrap Session (initSession)
CTX->>CDP: Page.enable / Runtime.enable
CTX->>CDP: CHANGED: Target.setAutoAttach<br/>(autoAttach: true, waitForDebuggerOnStart: true)
Note over CDP,BROWSER: Pauses execution of the new target immediately
loop For each Init Script
CTX->>CDP: CHANGED: Page.addScriptToEvaluateOnNewDocument<br/>(source, runImmediately: true)
end
CTX->>CDP: NEW: Page.addScriptToEvaluateOnNewDocument<br/>(piercerScript, runImmediately: true)
CTX->>CDP: CHANGED: Runtime.runIfWaitingForDebugger
Note over BROWSER,JS: Target Resumes
end
alt NEW: Piercer Pre-registered successfully
CTX->>CTX: Mark session piercer as installed
else Registration failed
CTX->>JS: Fallback: installV3PiercerIntoSession (standard eval)
end
Note over CTX,BROWSER: OOPIF / Nested Iframe Flow
BROWSER->>CDP: Target.attachedToTarget (Sub-target)
Note over CDP: NEW: auto-attach cascade ensures<br/>nested session is also paused
CDP-->>CTX: Emit attachedToTarget for nested session
CTX->>CTX: NEW: installTargetSessionListeners(nestedSession)
CTX->>CTX: Recursively execute Bootstrap Session (above)
Note over CTX,JS: Result: Scripts run in all frames before page JS executes.
deactivate CTX
Note over CTX: NEW: Cleanup on Detach
BROWSER->>CTX: Target.detachedFromTarget
CTX->>CTX: Remove session listeners & tracking sets
There was a problem hiding this comment.
No issues found across 3 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Architecture diagram
sequenceDiagram
participant CTX as V3Context
participant CDP as CDP Session (Page/OOPIF)
participant BROWSER as Browser Engine
participant JS as Page Runtime
Note over CTX,BROWSER: Target Lifecycle & Init Script Injection
BROWSER->>CTX: Target.attachedToTarget (New Page or OOPIF)
rect rgb(23, 37, 84)
Note right of CTX: NEW: Bootstrap Session (initSession)
CTX->>CDP: Page.enable / Runtime.enable
CTX->>CDP: CHANGED: Target.setAutoAttach<br/>(autoAttach: true, waitForDebuggerOnStart: true)
Note over CDP,BROWSER: Pauses execution of the new target immediately
loop For each Init Script
CTX->>CDP: CHANGED: Page.addScriptToEvaluateOnNewDocument<br/>(source, runImmediately: true)
end
CTX->>CDP: NEW: Page.addScriptToEvaluateOnNewDocument<br/>(piercerScript, runImmediately: true)
CTX->>CDP: CHANGED: Runtime.runIfWaitingForDebugger
Note over BROWSER,JS: Target Resumes
end
alt NEW: Piercer Pre-registered successfully
CTX->>CTX: Mark session piercer as installed
else Registration failed
CTX->>JS: Fallback: installV3PiercerIntoSession (standard eval)
end
Note over CTX,BROWSER: OOPIF / Nested Iframe Flow
BROWSER->>CDP: Target.attachedToTarget (Sub-target)
Note over CDP: NEW: auto-attach cascade ensures<br/>nested session is also paused
CDP-->>CTX: Emit attachedToTarget for nested session
CTX->>CTX: NEW: installTargetSessionListeners(nestedSession)
CTX->>CTX: Recursively execute Bootstrap Session (above)
Note over CTX,JS: Result: Scripts run in all frames before page JS executes.
deactivate CTX
Note over CTX: NEW: Cleanup on Detach
BROWSER->>CTX: Target.detachedFromTarget
CTX->>CTX: Remove session listeners & tracking sets
| private installTargetSessionListeners(session: CDPSessionLike): void { | ||
| const sessionId = session.id; | ||
| if (!sessionId) return; | ||
| if (this._targetSessionListeners.has(sessionId)) return; | ||
| this._targetSessionListeners.add(sessionId); | ||
|
|
||
| session.on<Protocol.Target.AttachedToTargetEvent>( | ||
| "Target.attachedToTarget", | ||
| (evt) => { | ||
| void this.onAttachedToTarget(evt.targetInfo, evt.sessionId); | ||
| }, | ||
| ); | ||
| session.on<Protocol.Target.DetachedFromTargetEvent>( | ||
| "Target.detachedFromTarget", | ||
| (evt) => { | ||
| this.onDetachedFromTarget(evt.sessionId, evt.targetId ?? null); | ||
| }, | ||
| ); | ||
| session.on<Protocol.Target.TargetDestroyedEvent>( | ||
| "Target.targetDestroyed", | ||
| (evt) => { | ||
| this.cleanupByTarget(evt.targetId); | ||
| }, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Listeners never removed
installTargetSessionListeners() registers 3 Target.* event handlers on each child session, but onDetachedFromTarget() only deletes the sessionId from _targetSessionListeners and does not remove the handlers. If Chrome reuses the same session object (or if detach/reattach happens in a long-lived context), this can lead to duplicated handler execution and retained references. Store the bound handler functions per sessionId and call session.off(...) (or the equivalent) during detach/cleanup.
Also appears at packages/core/lib/v3/understudy/context.ts:634-636 where only the set entries are deleted.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/lib/v3/understudy/context.ts
Line: 61:85
Comment:
**Listeners never removed**
`installTargetSessionListeners()` registers 3 `Target.*` event handlers on each child session, but `onDetachedFromTarget()` only deletes the sessionId from `_targetSessionListeners` and does not remove the handlers. If Chrome reuses the same session object (or if detach/reattach happens in a long-lived context), this can lead to duplicated handler execution and retained references. Store the bound handler functions per `sessionId` and call `session.off(...)` (or the equivalent) during detach/cleanup.
Also appears at `packages/core/lib/v3/understudy/context.ts:634-636` where only the set entries are deleted.
How can I resolve this? If you propose a fix, please make it concise.| private async ensurePiercer(session: CDPSessionLike): Promise<boolean> { | ||
| const key = this.sessionKey(session); | ||
| if (this._piercerInstalled.has(key)) return true; | ||
| const id = session.id ?? ""; | ||
| if (this._piercerInstalled.has(id)) return true; | ||
|
|
||
| const installed = await installV3PiercerIntoSession(session); | ||
| if (installed) { | ||
| this._piercerInstalled.add(key); | ||
| this._piercerInstalled.add(id); | ||
| } | ||
| return installed; |
There was a problem hiding this comment.
Piercer short-circuit bug
ensurePiercer() uses const id = session.id ?? "" and short-circuits when _piercerInstalled contains that id. For the root session (where session.id is commonly undefined), this uses the empty string key, so once any root call marks it installed, all future root-session checks will short-circuit even if the piercer was never actually installed (or was cleared incorrectly). Use a consistent non-empty key for root (e.g. session.id ?? "root") and make sure the same key shape is used everywhere _piercerInstalled is add/has/delete (including the pre-register path in onAttachedToTarget).
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/lib/v3/understudy/context.ts
Line: 164:172
Comment:
**Piercer short-circuit bug**
`ensurePiercer()` uses `const id = session.id ?? ""` and short-circuits when `_piercerInstalled` contains that id. For the root session (where `session.id` is commonly `undefined`), this uses the empty string key, so once any root call marks it installed, *all* future root-session checks will short-circuit even if the piercer was never actually installed (or was cleared incorrectly). Use a consistent non-empty key for root (e.g. `session.id ?? "root"`) and make sure the same key shape is used everywhere `_piercerInstalled` is `add`/`has`/`delete` (including the pre-register path in `onAttachedToTarget`).
How can I resolve this? If you propose a fix, please make it concise.| .send("Target.setAutoAttach", { | ||
| autoAttach: true, | ||
| waitForDebuggerOnStart: true, | ||
| flatten: true, | ||
| }) |
There was a problem hiding this comment.
do you want to exclude service workers here, pausing bg workers doesnt always work well
why
context.addInitScript()were not being applied in the following scenarios:what changed
runImmediately: truetoPage.addScriptToEvaluateOnNewDocumentcalls to so that scripts execute before any other code runsTarget.setAutoAttachwithwaitForDebuggerOnStart: trueto nested sessions, ensuring OOPIFs are paused before they can executeRuntime.runIfWaitingForDebugger) to happen after init scripts are registered to prevent init scripts being missedTarget.attachedToTargetlisteners to properly handle nested iframe attachment chainsTarget.targetCreatedfallback attach logic, relying instead on the properly configured auto-attach cascaderunImmediately: truefrompage.installInitScriptOnSessionto avoid double-execution when addInitScript is called after pages are liveensurePiercer()on success. post-resume sequential CDP round-trips were delayingPage.create/installFrameEventBridgesenough to miss SPIF attachment events_targetSessionListeners,_sessionInit, and_piercerInstalledsets inonDetachedFromTargetto prevent accumulationiframe-ctx-addInitScript.spec.tsso that (1) they are accurately comparing frames, and (2) they are not using hardcoded timeoutstest plan
addInitScriptiframe cases #1663Summary by cubic
Fixes context.addInitScript so init scripts run in OOPIFs and in popup pages with SPIF iframes. Scripts now execute before any page code and reliably propagate across nested targets.
Written for commit 40d65b9. Summary will update on new commits. Review in cubic