Skip to content

Commit a7eac5c

Browse files
committed
feat: update action
1 parent c58b564 commit a7eac5c

File tree

4 files changed

+147
-3
lines changed

4 files changed

+147
-3
lines changed

packages/core/src/client/webcomponents/state/__tests__/context-popup.test.ts

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,31 @@ import { createSharedState } from '@vitejs/devtools-kit/utils/shared-state'
55
import { beforeEach, describe, expect, it, vi } from 'vitest'
66
import { createDocksContext } from '../context'
77

8-
const { requestDockPopupOpenMock } = vi.hoisted(() => {
8+
const {
9+
executeSetupScriptMock,
10+
registerMainFrameDockActionHandlerMock,
11+
requestDockPopupOpenMock,
12+
triggerMainFrameDockActionMock,
13+
} = vi.hoisted(() => {
914
return {
15+
executeSetupScriptMock: vi.fn(),
16+
registerMainFrameDockActionHandlerMock: vi.fn(),
1017
requestDockPopupOpenMock: vi.fn(),
18+
triggerMainFrameDockActionMock: vi.fn(),
1119
}
1220
})
1321

1422
vi.mock('../popup', () => {
1523
return {
24+
registerMainFrameDockActionHandler: registerMainFrameDockActionHandlerMock,
1625
requestDockPopupOpen: requestDockPopupOpenMock,
26+
triggerMainFrameDockAction: triggerMainFrameDockActionMock,
27+
}
28+
})
29+
30+
vi.mock('../setup-script', () => {
31+
return {
32+
executeSetupScript: executeSetupScriptMock,
1733
}
1834
})
1935

@@ -43,7 +59,12 @@ function createMockRpc(entries: DevToolsDockEntry[] = []): DevToolsRpcClient {
4359

4460
describe('dock popup entry switching', () => {
4561
beforeEach(() => {
62+
executeSetupScriptMock.mockReset()
63+
executeSetupScriptMock.mockResolvedValue(undefined)
64+
registerMainFrameDockActionHandlerMock.mockClear()
4665
requestDockPopupOpenMock.mockClear()
66+
triggerMainFrameDockActionMock.mockReset()
67+
triggerMainFrameDockActionMock.mockResolvedValue(undefined)
4768
})
4869

4970
it('routes popup entry through popup request event flow', async () => {
@@ -58,4 +79,77 @@ describe('dock popup entry switching', () => {
5879
expect(context.panel.store.open).toBe(false)
5980
expect(context.docks.selectedId).toBeNull()
6081
})
82+
83+
it('registers action handler bridge on main frame context', async () => {
84+
const actionEntry: DevToolsDockEntry = {
85+
type: 'action',
86+
id: 'action-main-bridge',
87+
title: 'Action',
88+
icon: 'test',
89+
action: {
90+
importFrom: 'test',
91+
importName: 'default',
92+
},
93+
}
94+
const rpc = createMockRpc([actionEntry])
95+
const context = await createDocksContext('embedded', rpc)
96+
97+
expect(registerMainFrameDockActionHandlerMock).toHaveBeenCalledTimes(1)
98+
const handler = registerMainFrameDockActionHandlerMock.mock.calls[0]?.[0]
99+
expect(handler).toBeTypeOf('function')
100+
101+
const result = await handler?.('action-main-bridge')
102+
103+
expect(result).toBe(true)
104+
expect(executeSetupScriptMock).toHaveBeenCalledTimes(1)
105+
expect(context.docks.selectedId).toBe('action-main-bridge')
106+
})
107+
108+
it('delegates popup action click to main frame handler', async () => {
109+
triggerMainFrameDockActionMock.mockResolvedValue(true)
110+
const actionEntry: DevToolsDockEntry = {
111+
type: 'action',
112+
id: 'action-popup-delegated',
113+
title: 'Action',
114+
icon: 'test',
115+
action: {
116+
importFrom: 'test',
117+
importName: 'default',
118+
},
119+
}
120+
const rpc = createMockRpc([actionEntry])
121+
const context = await createDocksContext('embedded', rpc)
122+
123+
const result = await context.docks.switchEntry('action-popup-delegated')
124+
125+
expect(result).toBe(true)
126+
expect(triggerMainFrameDockActionMock).toHaveBeenCalledWith('action-popup-delegated')
127+
expect(executeSetupScriptMock).not.toHaveBeenCalled()
128+
expect(context.panel.store.open).toBe(false)
129+
expect(context.docks.selectedId).toBeNull()
130+
})
131+
132+
it('falls back to local action handler when main frame delegation is unavailable', async () => {
133+
triggerMainFrameDockActionMock.mockResolvedValue(undefined)
134+
const actionEntry: DevToolsDockEntry = {
135+
type: 'action',
136+
id: 'action-popup-fallback',
137+
title: 'Action',
138+
icon: 'test',
139+
action: {
140+
importFrom: 'test',
141+
importName: 'default',
142+
},
143+
}
144+
const rpc = createMockRpc([actionEntry])
145+
const context = await createDocksContext('embedded', rpc)
146+
147+
const result = await context.docks.switchEntry('action-popup-fallback')
148+
149+
expect(result).toBe(true)
150+
expect(triggerMainFrameDockActionMock).toHaveBeenCalledWith('action-popup-fallback')
151+
expect(executeSetupScriptMock).toHaveBeenCalledTimes(1)
152+
expect(context.panel.store.open).toBe(true)
153+
expect(context.docks.selectedId).toBe('action-popup-fallback')
154+
})
61155
})

packages/core/src/client/webcomponents/state/context.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { computed, markRaw, reactive, ref, toRefs, watchEffect } from 'vue'
77
import { BUILTIN_ENTRIES } from '../constants'
88
import { docksGroupByCategories } from './dock-settings'
99
import { createDockEntryState, DEFAULT_DOCK_PANEL_STORE, sharedStateToRef, useDocksEntries } from './docks'
10-
import { requestDockPopupOpen } from './popup'
10+
import { registerMainFrameDockActionHandler, requestDockPopupOpen, triggerMainFrameDockAction } from './popup'
1111
import { executeSetupScript } from './setup-script'
1212

1313
const docksContextByRpc = new WeakMap<DevToolsRpcClient, DocksContext>()
@@ -63,6 +63,13 @@ export async function createDocksContext(
6363
if (!entry)
6464
return false
6565

66+
// If the action is in a popup, delegate to the main frame
67+
if (entry.type === 'action') {
68+
const delegated = await triggerMainFrameDockAction(entry.id)
69+
if (delegated != null)
70+
return false
71+
}
72+
6673
// If has import script, run it
6774
if (
6875
(entry.type === 'action')
@@ -128,6 +135,13 @@ export async function createDocksContext(
128135
clientType,
129136
})
130137

138+
registerMainFrameDockActionHandler(async (id) => {
139+
const entry = dockEntries.value.find(e => e.id === id)
140+
if (!entry || entry.type !== 'action')
141+
return false
142+
return switchEntry(entry.id)
143+
})
144+
131145
docksContextByRpc.set(rpc, docksContext)
132146
return docksContext
133147
}

packages/core/src/client/webcomponents/state/popup.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ type ColorMode = 'dark' | 'light'
1212
interface DockPopupEvents {
1313
'popup:open-requested': (context: DocksContext) => void
1414
}
15+
type MainFrameDockActionHandler = (entryId: string) => Promise<boolean> | boolean
1516

1617
const PANEL_MIN_SIZE = 20
1718
const PANEL_MAX_SIZE = 100
1819
const POPUP_MIN_WIDTH = 320
1920
const POPUP_MIN_HEIGHT = 240
2021
const POPUP_DOCK_ID = '~popup'
2122
const POPUP_WINDOW_ATTRIBUTE = 'data-vite-devtools-popup-window'
23+
const MAIN_FRAME_ACTION_HANDLER_KEY = '__VITE_DEVTOOLS_TRIGGER_DOCK_ACTION__'
2224

2325
const popupWindow = shallowRef<Window | null>(null)
2426
const isPopupOpen = shallowRef(false)
@@ -198,6 +200,40 @@ export function isRunningInDockPopupWindow(): boolean {
198200
return !!window.document?.documentElement?.hasAttribute?.(POPUP_WINDOW_ATTRIBUTE)
199201
}
200202

203+
export function registerMainFrameDockActionHandler(
204+
handler: MainFrameDockActionHandler,
205+
) {
206+
if (typeof window === 'undefined') {
207+
return
208+
}
209+
if (isRunningInDockPopupWindow()) {
210+
return
211+
}
212+
;(window as Window & { [MAIN_FRAME_ACTION_HANDLER_KEY]?: MainFrameDockActionHandler })[MAIN_FRAME_ACTION_HANDLER_KEY] = handler
213+
}
214+
215+
export async function triggerMainFrameDockAction(
216+
entryId: string,
217+
): Promise<boolean | undefined> {
218+
if (typeof window === 'undefined')
219+
return undefined
220+
if (!isRunningInDockPopupWindow())
221+
return undefined
222+
223+
try {
224+
const opener = window.opener as (Window & { [MAIN_FRAME_ACTION_HANDLER_KEY]?: MainFrameDockActionHandler }) | null
225+
if (!opener || opener.closed)
226+
return undefined
227+
const handler = opener[MAIN_FRAME_ACTION_HANDLER_KEY]
228+
if (typeof handler !== 'function')
229+
return undefined
230+
return await handler(entryId)
231+
}
232+
catch {
233+
return undefined
234+
}
235+
}
236+
201237
export function isDockPopupEntryVisible(): boolean {
202238
return isDockPopupSupported() && !isPopupOpen.value && !isRunningInDockPopupWindow()
203239
}

packages/kit/src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const DEVTOOLS_DIRNAME = '.devtools'
88
export const DEVTOOLS_CONNECTION_META_FILENAME = '.connection.json'
99
export const DEVTOOLS_RPC_DUMP_MANIFEST_FILENAME = '.rpc-dump/index.json'
1010
export const DEVTOOLS_DOCK_IMPORTS_FILENAME = '.client-imports.js'
11-
export const DEVTOOLS_DOCK_IMPORTS_VIRTUAL_ID = '/.devtools/client-imports'
11+
export const DEVTOOLS_DOCK_IMPORTS_VIRTUAL_ID = '/.devtools-client-imports.js'
1212
export const DEVTOOLS_RPC_DUMP_DIRNAME = '.rpc-dump'
1313

1414
export const DEFAULT_CATEGORIES_ORDER: Record<string, number> = {

0 commit comments

Comments
 (0)