From 26bd10fd7f8aee044a3b76c4fa3f66d21486b659 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Mon, 14 Sep 2026 21:20:53 -0700 Subject: [PATCH] fix(browser): let an unavailable process identity reject instead of throwing installBrowserSessionPartitionPolicies returned Promise without being async, and configures the user agent policy before any suspension point. getBrowserProcessUserAgentIdentity throws when the process identity was never initialized, so that throw escaped synchronously past every caller's handler: `void install(...).catch(...)` in the registry, and a bare `void install(...)` in the route policies, which has no handler at all. Bookkeeping must never gate a user action. Session startup would have died on a failure its callers were already written to absorb and report. --- .../browser-session-partition-policies.ts | 6 +++- ...er-session-partition-proxy-install.test.ts | 33 ++++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/main/browser/browser-session-partition-policies.ts b/src/main/browser/browser-session-partition-policies.ts index 1d55e607b60..d55dbc30d78 100644 --- a/src/main/browser/browser-session-partition-policies.ts +++ b/src/main/browser/browser-session-partition-policies.ts @@ -96,7 +96,11 @@ function resolvePermissionNoticeUrl( export type BrowserPartitionDownloadPolicy = 'route' | 'deny' export type BrowserPartitionPermissionPolicy = 'browser' | 'deny' -export function installBrowserSessionPartitionPolicies( +// Why async despite no await: the user agent policy is configured before the first suspension, and +// getBrowserProcessUserAgentIdentity throws when the process identity was never initialized. Callers +// report failure through the promise (`void install(...).catch(...)`), so a synchronous throw would +// escape every one of them and gate browser-session startup on bookkeeping that is allowed to fail. +export async function installBrowserSessionPartitionPolicies( profile: BrowserSessionProfile, options: { downloads?: BrowserPartitionDownloadPolicy diff --git a/src/main/browser/browser-session-partition-proxy-install.test.ts b/src/main/browser/browser-session-partition-proxy-install.test.ts index c882b457be8..35f7b923732 100644 --- a/src/main/browser/browser-session-partition-proxy-install.test.ts +++ b/src/main/browser/browser-session-partition-proxy-install.test.ts @@ -25,6 +25,8 @@ const { sessionsByPartition, fromPartitionMock } = vi.hoisted(() => { return { sessionsByPartition, fromPartitionMock } }) +const identityState = vi.hoisted(() => ({ unavailable: false })) + vi.mock('electron', () => ({ session: { defaultSession: { resolveProxy: vi.fn(async () => 'DIRECT'), setProxy: vi.fn(async () => {}) }, @@ -47,10 +49,16 @@ vi.mock('./browser-session-ua', () => ({ installBrowserSessionUserAgentPolicy: vi.fn(() => vi.fn()) })) vi.mock('./browser-process-user-agent', () => ({ - getBrowserProcessUserAgentIdentity: () => ({ - mode: 'clean', - userAgent: 'Mozilla/5.0 Chrome/150.0.0.0 Safari/537.36' - }) + getBrowserProcessUserAgentIdentity: () => { + // The real one throws when the process identity was never initialized. + if (identityState.unavailable) { + throw new Error('Browser process user agent is not initialized') + } + return { + mode: 'clean', + userAgent: 'Mozilla/5.0 Chrome/150.0.0.0 Safari/537.36' + } + } })) vi.mock('./browser-webauthn-access', () => ({ allowsBrowserWebAuthnPermission: vi.fn(() => false), @@ -100,6 +108,23 @@ describe('installBrowserSessionPartitionPolicies proxy wiring', () => { afterEach(() => { vi.unstubAllEnvs() + identityState.unavailable = false + }) + + // The installer returns Promise, so every caller reports failure through the promise — + // `void install(...).catch(...)` at browser-session-registry.ts:136 and :336, and a bare + // `void install(...)` at browser-session-route-policies.ts:16. The user agent policy is + // configured synchronously before the first await, so a throw from there escapes all of them + // and takes down browser-session startup instead of being reported. + it('reports an unavailable process identity through the promise, not a synchronous throw', async () => { + const profile = nextProfile() + identityState.unavailable = true + + let installation: Promise | undefined + expect(() => { + installation = installBrowserSessionPartitionPolicies(profile) + }).not.toThrow() + await expect(installation).rejects.toThrow('Browser process user agent is not initialized') }) // Why (STA-4779): the installer is the single funnel every browser partition passes through.