Skip to content

Commit 2ff546b

Browse files
committed
fix(in-page-channel): connect panels nested in popup windows
Panel iframes have no opener of their own, so their handshake never reached the inspected page through the popup window's opener. Discover openers across the ancestor chain, deduplicate targets, and cover direct and nested popup handshakes with regression tests.
1 parent 1bd966f commit 2ff546b

4 files changed

Lines changed: 46 additions & 11 deletions

File tree

packages/devframe/src/in-page-channel/in-page-channel.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,39 @@ function createWindowPair(origin = 'https://app.test'): { hostWin: FakeWindow, p
675675
const fastHello = { helloIntervalMs: 5, heartbeat: false as const }
676676

677677
describe('in-page channel handshake', () => {
678+
it.each([0, 1, 2])('connects through a popup opener with %i nested panel frames', async (depth) => {
679+
const { hostWin, panelWin } = createWindowPair()
680+
let popupWin = panelWin
681+
for (let i = 0; i < depth; i++) {
682+
const parent = createFakeWindow(hostWin.location.origin)
683+
popupWin.parent = parent
684+
popupWin = parent
685+
}
686+
popupWin.parent = popupWin
687+
popupWin.opener = hostWin
688+
const pageScript = createPageScriptChannel<TestProtocol>({
689+
name: 'devframes:test',
690+
window: asWindow(hostWin),
691+
heartbeat: false,
692+
functions: defaultPageScriptFunctions,
693+
})
694+
const panel = connectPanelChannel<TestProtocol>({
695+
name: 'devframes:test',
696+
window: asWindow(panelWin),
697+
...fastHello,
698+
functions: defaultPanelFunctions,
699+
})
700+
try {
701+
await panel.whenConnected(200)
702+
expect(panel.pageScript?.instanceId).toBe(pageScript.instanceId)
703+
await expect(panel.call('echo', 'popup')).resolves.toBe('popup')
704+
}
705+
finally {
706+
panel.close()
707+
pageScript.close()
708+
}
709+
})
710+
678711
it('connects a panel to the page script and survives page-script restarts', async () => {
679712
const { hostWin, panelWin } = createWindowPair()
680713
const pageScript = createPageScriptChannel<TestProtocol>({

packages/devframe/src/in-page-channel/panel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const DEFAULT_EVENT_BUFFER_LIMIT = 64
3838
* Connect the panel endpoint of an in-page channel.
3939
*
4040
* The panel initiates: it posts a versioned hello to every window a
41-
* same-tab page script can live in (its ancestor chain and its `opener`),
41+
* page script can live in (its ancestor chain and those windows' openers),
4242
* retrying with backoff until one answers with a dedicated port, so boot
4343
* order never matters, and a reload of either side is just a re-handshake
4444
* (`WindowProxy` references survive navigations). While `connecting`,

packages/devframe/src/in-page-channel/protocol.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ export function resolveAllowedOrigins(allowedOrigins: string[] | undefined, win:
9292
}
9393

9494
/**
95-
* Default handshake targets of a panel: its ancestor chain plus its
96-
* `opener`, every same-tab window a page script can live in. `WindowProxy`
95+
* Default handshake targets of a panel: its ancestor chain plus the
96+
* `opener` of each window, including a popup containing the panel iframe. `WindowProxy`
9797
* references stay valid across navigations, so hellos posted to these reach
9898
* a page script even after the host page reloads.
9999
*/
@@ -111,13 +111,15 @@ export function defaultHandshakeTargets(win: Window): Window[] {
111111
catch {
112112
// Walking stopped by the browser; keep what we have.
113113
}
114-
try {
115-
const opener = win.opener as Window | null
116-
if (opener && opener !== win)
117-
targets.push(opener)
118-
}
119-
catch {
120-
// Inaccessible opener; ignore.
114+
for (const current of [win, ...targets]) {
115+
try {
116+
const opener = current.opener as Window | null
117+
if (opener && opener !== win && !targets.includes(opener))
118+
targets.push(opener)
119+
}
120+
catch {
121+
// Inaccessible opener; continue with the other ancestors.
122+
}
121123
}
122124
return targets
123125
}

packages/devframe/src/in-page-channel/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ export interface ConnectPanelChannelOptions<Protocol extends InPageChannelProtoc
274274
window?: Window | false
275275
/**
276276
* Windows the hello is posted to. Defaults to the panel's ancestor chain
277-
* plus its `opener`, the places a same-tab page script can live. When
277+
* plus those windows' openers, the places a page script can live. When
278278
* empty and no `transport` is given, the endpoint stays `connecting` and
279279
* warns once.
280280
*/

0 commit comments

Comments
 (0)