fix: Target.attachToTarget returns unique session id per call#2077
fix: Target.attachToTarget returns unique session id per call#2077soilSpoon wants to merge 1 commit intolightpanda-io:mainfrom
Conversation
4e40f44 to
8139d73
Compare
Target.attachToTarget was reusing the existing bc.session_id when one was already set (via the `orelse` pattern). The CDP spec requires a fresh unique sessionId per attachToTarget call, as a single target can have multiple sessions attached simultaneously. This broke Playwright's ctx.newCDPSession(page) flow: the returned sessionId was the same as the page's existing session, so Playwright's createChildSession() overwrote _sessions[sid] with a new empty object, orphaning the page session's pending callbacks and triggering assert(!object.id). Changes: - doAttachtoTarget: always call session_id_gen.next(). First attach sets bc.session_id (primary), subsequent attach sets bc.alt_session_id. - Copy session IDs into fixed buffers (session_id_buf / alt_session_id_buf) because session_id_gen.next() reuses its internal buffer and previous slices would be invalidated. - attachToBrowserTarget: also copy into session_id_buf for safety. - isValidSessionId: accept both primary and alt session IDs. - callInspector / onInspectorResponse: route V8 inspector replies back to the requesting session via inspector_reply_session. - runtime.zig sendInspector: pass cmd.input.session_id to callInspector. - Add test: "attachToTarget returns unique sessionId per call" verifying two successive attach calls produce different session IDs. Amp-Thread-ID: https://ampcode.com/threads/T-019d5277-a92a-736b-bcb4-8aa78109c21d Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d5277-a92a-736b-bcb4-8aa78109c21d Co-authored-by: Amp <amp@ampcode.com>
8139d73 to
942ac9b
Compare
|
While implementing this fix I noticed that I saw in #1096 that multi-context support is actively in progress. Could you share where things stand with that work? Specifically:
Happy to update this PR to avoid conflicts if needed. Thanks! |
|
Hey @soilSpoon, You are right about your analysis. Returning the same session ID every time has worked well for us so far, even though we do not follow the specs. Multi-context support is being added step by step in I am not in favor of adding a second alternative session ID. As you said, it only fixes the issue for a 2nd call, but will break if the client asks for more. Question: did returning the same session ID actually break your use case? |
Yes — here's the concrete scenario: const page = await context.newPage();
await page.goto('https://example.com');
const session = await context.newCDPSession(page); // Target.attachToTarget → returns SID-1 (same as page session)
await page.goto('https://example.com/other'); // assert(!object.id) crashCaptured via CDP proxy: Playwright's You're right that
Happy to go whichever direction makes the most sense for the roadmap. |
Problem
Target.attachToTargetreturns the existingbc.session_idwhen one is already set, instead of generating a new unique session ID. The CDP spec requires a fresh, uniquesessionIdper call — a single target can have multiple sessions attached simultaneously.How it breaks
When a CDP client (e.g. Playwright) calls
Target.attachToTargetfrom an existing page session (SID-1), lightpanda returns the sameSID-1. The client then creates a child session keyed bySID-1, overwriting the original page session's entry. All pending callbacks on the original session become orphaned — responses arrive but no callback exists to handle them.Captured via CDP proxy:
Root cause
In
doAttachtoTarget:Chrome reference
Chrome's
TargetHandler::AttachToTarget()always creates a new session withbase::UnguessableToken::Create()and stores it in an unboundedstd::map<string, unique_ptr<Session>>. Multiple sessions per target is explicitly supported.Fix
1. Always generate a new session ID (
target.zig)bc.session_id(primary)bc.alt_session_id, leaving primary intact2. Copy IDs into fixed buffers (
target.zig)session_id_genuses a zero-allocationIncrementinggenerator that reuses its internal buffer — the slice returned bynext()is invalidated by the next call. Bothsession_idandalt_session_idare now copied into 14-byte fixed arrays (session_id_buf/alt_session_id_buf).3. Accept alt session in validation (
CDP.zig)isValidSessionIdnow checks bothsession_idandalt_session_id.4. Route inspector responses to the requesting session (
CDP.zig,runtime.zig)V8 inspector has one session per
BrowserContext. When a command arrives viaalt_session_id, the response must be tagged with that session, not the primary.callInspectorstores the requesting session ininspector_reply_session;onInspectorResponsereads it back. This is safe becausecallInspector→runMicrotasksis synchronous.Limitations
alt_session_idslot (primary + 1 alt). Chrome supports unlimited concurrent sessions viastd::map. For lightpanda's current single-BrowserContext / single-page architecture, one alt slot is sufficient. A future multi-session refactor would replace this with a session list.connectOverCDPdepends on other unimplemented features (e.g.Target.createBrowserContext). The fix is verified via unit tests and raw CDP client testing.Verification
New test:
"cdp.target: attachToTarget returns unique sessionId per call"attachToTargetcalls return different session IDssession_idremains unchanged after second attach