diff --git a/src/renderer/src/lib/workspace-browser-tab-open.test.ts b/src/renderer/src/lib/workspace-browser-tab-open.test.ts index d414316b52f..765f452f8f7 100644 --- a/src/renderer/src/lib/workspace-browser-tab-open.test.ts +++ b/src/renderer/src/lib/workspace-browser-tab-open.test.ts @@ -4,6 +4,7 @@ import { toRuntimeExecutionHostId, toSshExecutionHostId } from '../../../shared/execution-host' +import { BROWSER_SCREENCAST_RUNTIME_CAPABILITY } from '../../../shared/protocol-version' import { canOpenWorkspaceBrowserTabOnRuntime, openWorkspaceBrowserTab @@ -43,7 +44,10 @@ function ownerState(hostId?: string, runtimeOwnerEnvironmentId?: string): Record function browserCapableRuntime(environmentId: string): Record { return { runtimeStatusByEnvironmentId: new Map([ - [environmentId, { status: { capabilities: ['browser.screencast.v1'] }, checkedAt: 1 }] + [ + environmentId, + { status: { capabilities: [BROWSER_SCREENCAST_RUNTIME_CAPABILITY] }, checkedAt: 1 } + ] ]) } } @@ -164,6 +168,27 @@ describe('openWorkspaceBrowserTab', () => { expect(mocks.state.createBrowserTab).not.toHaveBeenCalled() }) + it('does not fall back to a client browser when asserted runtime creation fails', async () => { + mocks.state = { + ...ownerState(toRuntimeExecutionHostId('hub-a')), + ...browserCapableRuntime('hub-a'), + createBrowserTab: vi.fn(), + defaultBrowserSessionProfileId: 'client-profile', + defaultBrowserSessionProfileIdByHostId: {} + } + mocks.createRemote.mockResolvedValue(false) + + await expect( + openWorkspaceBrowserTab({ + workspaceId: WORKSPACE_ID, + url: 'https://example.com/', + intent: { kind: 'url' }, + expectedRuntimeEnvironmentId: 'hub-a' + }) + ).rejects.toThrow('Unable to open URL.') + expect(mocks.state.createBrowserTab).not.toHaveBeenCalled() + }) + // Two dev servers on one host must not share a tab title. it('keeps the port in local-dev tab titles', async () => { const createBrowserTab = vi.fn() diff --git a/src/renderer/src/lib/workspace-browser-tab-open.ts b/src/renderer/src/lib/workspace-browser-tab-open.ts index 0ef67b1cfce..144b9cd60f6 100644 --- a/src/renderer/src/lib/workspace-browser-tab-open.ts +++ b/src/renderer/src/lib/workspace-browser-tab-open.ts @@ -8,6 +8,7 @@ import { type ExecutionHostId } from '../../../shared/execution-host' import { SEARCH_ENGINE_LABELS, type SearchEngine } from '../../../shared/browser-url' +import { BROWSER_SCREENCAST_RUNTIME_CAPABILITY } from '../../../shared/protocol-version' import { getClientCreationActionPolicy, type ClientCreationActionAvailability @@ -25,15 +26,21 @@ export type OpenWorkspaceBrowserTabRequest = { } function isExpectedRuntimeBrowserRoute( - availability: ClientCreationActionAvailability, + state: AppState, route: ReturnType, expectedRuntimeEnvironmentId: string ): boolean { - if (availability.state !== 'enabled' || availability.provider !== 'paired-runtime' || !route) { + if (!route) { return false } + const expectedEnvironmentId = expectedRuntimeEnvironmentId.trim() const environmentId = route.runtimeEnvironmentId?.trim() || null - if (environmentId !== expectedRuntimeEnvironmentId.trim()) { + const capabilities = + state.runtimeStatusByEnvironmentId?.get(expectedEnvironmentId)?.status?.capabilities + if ( + environmentId !== expectedEnvironmentId || + !capabilities?.includes(BROWSER_SCREENCAST_RUNTIME_CAPABILITY) + ) { return false } const host = parseExecutionHostId(route.executionHostId) @@ -48,9 +55,8 @@ export function canOpenWorkspaceBrowserTabOnRuntime( workspaceId: string, expectedRuntimeEnvironmentId: string ): boolean { - const availability = getClientCreationActionPolicy(state, workspaceId)['managed-browser'] const route = resolveWorktreeOperationRoute(state, workspaceId) - return isExpectedRuntimeBrowserRoute(availability, route, expectedRuntimeEnvironmentId) + return isExpectedRuntimeBrowserRoute(state, route, expectedRuntimeEnvironmentId) } // Why: concurrent URL tabs are indistinguishable under a shared "Open URL" @@ -166,7 +172,7 @@ export async function openWorkspaceBrowserTab( const expectedEnvironmentId = request.expectedRuntimeEnvironmentId?.trim() || null if ( expectedEnvironmentId && - !isExpectedRuntimeBrowserRoute(availability, route, expectedEnvironmentId) + !isExpectedRuntimeBrowserRoute(state, route, expectedEnvironmentId) ) { throw openFailure(presentation.error, 'asserted runtime cannot provide this managed browser') }