fix(window): pump hidden-window frames from the renderer on Windows background launches (#34197)

* fix(window): repaint a never-shown background window on Windows so CDP screenshots return

Windows composites nothing for an idle window that was never shown, so
Page.captureScreenshot under ORCA_BACKGROUND_LAUNCH=1 waited forever once the
status-bar usage chip stopped animating (#33799).

* fix(window): pump hidden-window frames from the renderer on Windows background launches

Main-process webContents.invalidate() did not make a never-shown Windows window
produce a frame CDP can capture (verified on Windows 11). A one-frame root
background flip from the preload does, so main stamps a preload flag for
ORCA_BACKGROUND_LAUNCH=1 on win32 and tells the preload to stop on first show.

* fix(window): stop the hidden-frame pump only on main's show signal, including after reloads

A never-shown Windows window can report visible after a desktop change, so the
preload no longer stops on visibilitychange; main re-sends the stop after any
reload once the window has been shown.

* fix(window): register the frame-pump stop with on('show') so every window double supports it

createMainWindow suites simulate win32 under the runner's ORCA_BACKGROUND_LAUNCH=1,
so they install the pump stop; their window doubles implement on() but not once().
This commit is contained in:
Neil
2026-10-26 21:13:42 -07:00
committed by GitHub
parent 8d62374000
commit e882d8eda7
8 changed files with 351 additions and 1 deletions
+13 -1
View File
@@ -20,6 +20,11 @@ import { mainWindowLoadErrorCode } from './main-window-load-error-code'
import { installMainWindowFocusLifecycle } from './main-window-focus-lifecycle'
import { installMainWindowShortcutRouting } from './main-window-shortcut-routing'
import { installMainWindowStateLifecycle } from './main-window-state-lifecycle'
import { stopHiddenWindowFramePumpOnShow } from './hidden-window-frame-pump'
import {
formatHiddenWindowFramePumpArgument,
shouldPumpHiddenWindowFrames
} from '../../shared/hidden-window-frame-pump'
import {
forceRepaint,
installMacosVisibilityRepaint,
@@ -97,6 +102,7 @@ export function createMainWindow(
const platformBlurOptions =
blur && process.platform === 'win32' ? { backgroundMaterial: 'acrylic' as const } : {}
const pumpHiddenFrames = shouldPumpHiddenWindowFrames(process.env, process.platform)
const mainWindow = new BrowserWindow({
width: savedBounds?.width ?? defaultBounds.width,
height: savedBounds?.height ?? defaultBounds.height,
@@ -135,7 +141,10 @@ export function createMainWindow(
sandbox: true,
webviewTag: true,
// Pass host identity at birth so the renderer has it before first snapshot, without waiting for IPC registration.
additionalArguments: [formatBrowserClientHostIdArgument(getBrowserClientHostId())]
additionalArguments: [
formatBrowserClientHostIdArgument(getBrowserClientHostId()),
...(pumpHiddenFrames ? [formatHiddenWindowFramePumpArgument()] : [])
]
}
})
const rendererWebContentsId = mainWindow.webContents.id
@@ -183,6 +192,9 @@ export function createMainWindow(
store
})
installMainWindowWebviewSecurity(mainWindow)
if (pumpHiddenFrames) {
stopHiddenWindowFramePumpOnShow(mainWindow)
}
const focus = installMainWindowFocusLifecycle({
isWindowClosing: state.isWindowClosing,
mainWindow,
@@ -0,0 +1,51 @@
import { EventEmitter } from 'node:events'
import { describe, expect, it, vi } from 'vitest'
import { HIDDEN_WINDOW_FRAME_PUMP_STOP_CHANNEL } from '../../shared/hidden-window-frame-pump'
import { stopHiddenWindowFramePumpOnShow } from './hidden-window-frame-pump'
function fakeWindow(destroyed = false) {
const windowEvents = new EventEmitter()
const contentsEvents = new EventEmitter()
const send = vi.fn()
const window = {
on: (event: 'show', listener: () => void) => windowEvents.on(event, listener),
webContents: {
isDestroyed: () => destroyed,
on: (event: 'did-finish-load', listener: () => void) => contentsEvents.on(event, listener),
send
}
}
return { window, windowEvents, contentsEvents, send }
}
describe('stopHiddenWindowFramePumpOnShow', () => {
it('keeps pumping across reloads while the window has never been shown', () => {
const { window, contentsEvents, send } = fakeWindow()
stopHiddenWindowFramePumpOnShow(window)
contentsEvents.emit('did-finish-load')
expect(send).not.toHaveBeenCalled()
})
it('stops on the first show and again after a later reload restarts the pump', () => {
const { window, windowEvents, contentsEvents, send } = fakeWindow()
stopHiddenWindowFramePumpOnShow(window)
windowEvents.emit('show')
windowEvents.emit('show')
contentsEvents.emit('did-finish-load')
expect(send).toHaveBeenCalledTimes(2)
expect(send).toHaveBeenCalledWith(HIDDEN_WINDOW_FRAME_PUMP_STOP_CHANNEL)
})
it('sends nothing to destroyed contents', () => {
const { window, windowEvents, send } = fakeWindow(true)
stopHiddenWindowFramePumpOnShow(window)
windowEvents.emit('show')
expect(send).not.toHaveBeenCalled()
})
})
@@ -0,0 +1,31 @@
import { HIDDEN_WINDOW_FRAME_PUMP_STOP_CHANNEL } from '../../shared/hidden-window-frame-pump'
type FramePumpWindow = {
on(event: 'show', listener: () => void): unknown
webContents: {
isDestroyed?(): boolean
on(event: 'did-finish-load', listener: () => void): unknown
send(channel: string): void
}
}
/** The preload pumps from birth; stop it on first show and after any later reload restarts it. */
export function stopHiddenWindowFramePumpOnShow(window: FramePumpWindow): void {
let shown = false
const stop = (): void => {
if (window.webContents.isDestroyed?.() !== true) {
window.webContents.send(HIDDEN_WINDOW_FRAME_PUMP_STOP_CHANNEL)
}
}
window.on('show', () => {
if (!shown) {
shown = true
stop()
}
})
window.webContents.on('did-finish-load', () => {
if (shown) {
stop()
}
})
}
@@ -0,0 +1,132 @@
import { describe, expect, it, vi } from 'vitest'
import {
HIDDEN_WINDOW_FRAME_PUMP_INTERVAL_MS,
formatHiddenWindowFramePumpArgument
} from '../shared/hidden-window-frame-pump'
import {
installHiddenWindowFramePump,
startHiddenWindowFramePump
} from './hidden-window-frame-pump'
function harness(initial?: { value: string; priority: string }) {
const props = new Map<string, { value: string; priority: string }>()
if (initial) {
props.set('background-color', initial)
}
const history: string[] = []
const style = {
getPropertyValue: (name: string) => props.get(name)?.value ?? '',
getPropertyPriority: (name: string) => props.get(name)?.priority ?? '',
setProperty: (name: string, value: string | null, priority = '') => {
props.set(name, { value: value ?? '', priority })
history.push(`set ${value} ${priority}`.trim())
return undefined
},
removeProperty: (name: string) => {
props.delete(name)
history.push('remove')
return ''
}
}
const frames: (() => void)[] = []
let tick: (() => void) | null = null
let intervalMs = 0
let cancelled = false
const stop = startHiddenWindowFramePump({
rootStyle: () => style,
requestAnimationFrame: (callback) => frames.push(callback),
every: (callback, ms) => {
tick = callback
intervalMs = ms
return () => {
cancelled = true
}
}
})
const runFrame = () => frames.shift()?.()
return {
props,
history,
frames,
stop,
tick: () => tick?.(),
runFrame,
intervalMs: () => intervalMs,
cancelled: () => cancelled
}
}
describe('startHiddenWindowFramePump', () => {
it('damages exactly one frame per interval and removes the style it added', () => {
const h = harness()
expect(h.intervalMs()).toBe(HIDDEN_WINDOW_FRAME_PUMP_INTERVAL_MS)
h.tick()
h.runFrame()
expect(h.props.get('background-color')?.value).toBe('rgb(1, 1, 1)')
h.runFrame()
expect(h.props.has('background-color')).toBe(false)
expect(h.history).toEqual(['set rgb(1, 1, 1)', 'remove'])
})
it('restores an existing inline background and its priority exactly', () => {
const h = harness({ value: 'red', priority: 'important' })
h.tick()
h.runFrame()
h.runFrame()
expect(h.props.get('background-color')).toEqual({ value: 'red', priority: 'important' })
})
it('does not start a second flip while one is in flight', () => {
const h = harness()
h.tick()
h.tick()
expect(h.frames).toHaveLength(1)
})
it('cancels the interval on stop but still restores a flip in flight', () => {
const h = harness()
h.tick()
h.runFrame()
h.stop()
h.runFrame()
h.tick()
expect(h.cancelled()).toBe(true)
expect(h.props.has('background-color')).toBe(false)
expect(h.frames).toHaveLength(0)
})
})
describe('installHiddenWindowFramePump', () => {
function deps() {
const every = vi.fn((_callback: () => void, _ms: number) => vi.fn())
return { rootStyle: () => null, requestAnimationFrame: vi.fn(), every }
}
it('does not start without the flag main stamps', () => {
const d = deps()
const onStopRequest = vi.fn()
installHiddenWindowFramePump(['--other'], onStopRequest, d)
expect(d.every).not.toHaveBeenCalled()
expect(onStopRequest).not.toHaveBeenCalled()
})
it('starts with the flag and hands main the stop', () => {
const d = deps()
let stop: (() => void) | undefined
installHiddenWindowFramePump([formatHiddenWindowFramePumpArgument()], (s) => (stop = s), d)
const cancel = d.every.mock.results[0]?.value
stop?.()
expect(cancel).toHaveBeenCalledOnce()
})
})
+62
View File
@@ -0,0 +1,62 @@
import {
HIDDEN_WINDOW_FRAME_PUMP_INTERVAL_MS,
hasHiddenWindowFramePumpArgument
} from '../shared/hidden-window-frame-pump'
// Near-black so the single damaged frame is indistinguishable even if the window were visible.
const FLIP_COLOR = 'rgb(1, 1, 1)'
type FramePumpStyle = Pick<
CSSStyleDeclaration,
'getPropertyValue' | 'getPropertyPriority' | 'setProperty' | 'removeProperty'
>
export type HiddenWindowFramePumpDeps = {
rootStyle: () => FramePumpStyle | null
requestAnimationFrame: (callback: () => void) => unknown
/** Runs callback every ms; returns the cancel function. */
every: (callback: () => void, ms: number) => () => void
}
/** Damage one frame per interval by flipping the root background, then restore the inline style exactly. */
export function startHiddenWindowFramePump(deps: HiddenWindowFramePumpDeps): () => void {
let stopped = false
let flipping = false
const cancel = deps.every(() => {
const style = deps.rootStyle()
if (stopped || flipping || !style) {
return
}
flipping = true
deps.requestAnimationFrame(() => {
const previousValue = style.getPropertyValue('background-color')
const previousPriority = style.getPropertyPriority('background-color')
style.setProperty('background-color', FLIP_COLOR, previousPriority)
deps.requestAnimationFrame(() => {
if (previousValue) {
style.setProperty('background-color', previousValue, previousPriority)
} else {
style.removeProperty('background-color')
}
flipping = false
})
})
}, HIDDEN_WINDOW_FRAME_PUMP_INTERVAL_MS)
return () => {
if (!stopped) {
stopped = true
cancel()
}
}
}
/** Only main's stop ends the pump; Windows can report a never-shown window visible after a desktop change. */
export function installHiddenWindowFramePump(
argv: readonly string[],
onStopRequest: (stop: () => void) => void,
deps: HiddenWindowFramePumpDeps
): void {
if (hasHiddenWindowFramePumpArgument(argv)) {
onStopRequest(startHiddenWindowFramePump(deps))
}
}
+15
View File
@@ -1,4 +1,6 @@
import { contextBridge, ipcRenderer } from 'electron'
import { HIDDEN_WINDOW_FRAME_PUMP_STOP_CHANNEL } from '../shared/hidden-window-frame-pump'
import { installHiddenWindowFramePump } from './hidden-window-frame-pump'
import type { PreloadApi } from './api-types'
import { installBrowserFindListener } from './preload-runtime-support'
import { appApi } from './api/app-bridge'
@@ -197,3 +199,16 @@ if (process.contextIsolated) {
} else {
window.api = api
}
installHiddenWindowFramePump(
process.argv,
(stop) => ipcRenderer.once(HIDDEN_WINDOW_FRAME_PUMP_STOP_CHANNEL, stop),
{
rootStyle: () => document.documentElement?.style ?? null,
requestAnimationFrame: (callback) => window.requestAnimationFrame(callback),
every: (callback, ms) => {
const handle = window.setInterval(callback, ms)
return () => window.clearInterval(handle)
}
}
)
@@ -0,0 +1,23 @@
import { describe, expect, it } from 'vitest'
import {
formatHiddenWindowFramePumpArgument,
hasHiddenWindowFramePumpArgument,
shouldPumpHiddenWindowFrames
} from './hidden-window-frame-pump'
describe('shouldPumpHiddenWindowFrames', () => {
it('pumps only a Windows background launch', () => {
expect(shouldPumpHiddenWindowFrames({ ORCA_BACKGROUND_LAUNCH: '1' }, 'win32')).toBe(true)
expect(shouldPumpHiddenWindowFrames({ ORCA_BACKGROUND_LAUNCH: '1' }, 'darwin')).toBe(false)
expect(shouldPumpHiddenWindowFrames({ ORCA_BACKGROUND_LAUNCH: '1' }, 'linux')).toBe(false)
expect(shouldPumpHiddenWindowFrames({}, 'win32')).toBe(false)
expect(shouldPumpHiddenWindowFrames({ ORCA_BACKGROUND_LAUNCH: '0' }, 'win32')).toBe(false)
})
it('reads back only the argument main stamped', () => {
expect(hasHiddenWindowFramePumpArgument(['--x', formatHiddenWindowFramePumpArgument()])).toBe(
true
)
expect(hasHiddenWindowFramePumpArgument(['--orca-browser-client-host-id=abc'])).toBe(false)
})
})
+24
View File
@@ -0,0 +1,24 @@
/**
* Windows produces no compositor frame for an idle never-shown window, and main-process
* webContents.invalidate() does not change that, so CDP Page.captureScreenshot under
* ORCA_BACKGROUND_LAUNCH=1 waits forever. A one-frame renderer style flip does produce a frame.
*/
const HIDDEN_WINDOW_FRAME_PUMP_ARGUMENT = '--orca-hidden-window-frame-pump'
export const HIDDEN_WINDOW_FRAME_PUMP_STOP_CHANNEL = 'window:hidden-frame-pump-stop'
export const HIDDEN_WINDOW_FRAME_PUMP_INTERVAL_MS = 750
export function shouldPumpHiddenWindowFrames(
env: Readonly<Record<string, string | undefined>>,
platform: NodeJS.Platform
): boolean {
return platform === 'win32' && env.ORCA_BACKGROUND_LAUNCH === '1'
}
export function formatHiddenWindowFramePumpArgument(): string {
return HIDDEN_WINDOW_FRAME_PUMP_ARGUMENT
}
export function hasHiddenWindowFramePumpArgument(argv: readonly string[]): boolean {
return argv.includes(HIDDEN_WINDOW_FRAME_PUMP_ARGUMENT)
}