diff --git a/packages/initialization-worker/src/parts/PrepareBoth/PrepareBoth.ts b/packages/initialization-worker/src/parts/PrepareBoth/PrepareBoth.ts index 65d982492c..eccde31c5f 100644 --- a/packages/initialization-worker/src/parts/PrepareBoth/PrepareBoth.ts +++ b/packages/initialization-worker/src/parts/PrepareBoth/PrepareBoth.ts @@ -6,9 +6,13 @@ import * as DebuggerCreateRpcConnection from '../DebuggerCreateRpcConnection/Deb import { DevtoolsProtocolDebugger, DevtoolsProtocolRuntime } from '../DevtoolsProtocol/DevtoolsProtocol.ts' import * as MonkeyPatchElectronScript from '../MonkeyPatchElectronScript/MonkeyPatchElectronScript.ts' import { PortReadStream } from '../PortReadStream/PortReadStream.ts' +import * as SetWindowContentSize from '../SetWindowContentSize/SetWindowContentSize.ts' import * as WaitForDebuggerListening from '../WaitForDebuggerListening/WaitForDebuggerListening.ts' import * as WaitForDevtoolsListening from '../WaitForDevtoolsListening/WaitForDevtoolsListening.ts' +const windowWidth = 1024 +const windowHeight = 768 + export const prepareBoth = async ( secretsPath: string, headlessMode: boolean, @@ -58,6 +62,8 @@ export const prepareBoth = async ( // Wait for the page to be created by the initialization worker's connectDevtools const { dispose, sessionId, targetId } = await connectDevtoolsPromise + await SetWindowContentSize.setWindowContentSize(electronRpc, electronObjectId, targetId, windowWidth, windowHeight) + await Promise.all([electronRpc.dispose(), dispose()]) return { diff --git a/packages/initialization-worker/src/parts/SetWindowContentSize/SetWindowContentSize.ts b/packages/initialization-worker/src/parts/SetWindowContentSize/SetWindowContentSize.ts new file mode 100644 index 0000000000..e02cb9717c --- /dev/null +++ b/packages/initialization-worker/src/parts/SetWindowContentSize/SetWindowContentSize.ts @@ -0,0 +1,49 @@ +import { DevtoolsProtocolRuntime } from '../DevtoolsProtocol/DevtoolsProtocol.ts' + +const setWindowContentSizeScript = `async function (targetId, width, height) { + const electron = this + const { BrowserWindow, webContents } = electron + const targetWebContents = webContents.fromDevToolsTargetId?.(targetId) + const browserWindow = targetWebContents + ? BrowserWindow.fromWebContents(targetWebContents) + : BrowserWindow.getAllWindows()[0] + if (!browserWindow) { + throw new Error('browser window not found') + } + + if (browserWindow.isFullScreen()) { + await new Promise((resolve) => { + browserWindow.once('leave-full-screen', resolve) + browserWindow.setFullScreen(false) + }) + } + if (browserWindow.isMaximized()) { + await new Promise((resolve) => { + browserWindow.once('unmaximize', resolve) + browserWindow.unmaximize() + }) + } + + browserWindow.setContentSize(width, height, false) + const [actualWidth, actualHeight] = browserWindow.getContentSize() + if (actualWidth !== width || actualHeight !== height) { + throw new Error( + \`expected browser window content size \${width}x\${height}, got \${actualWidth}x\${actualHeight}\` + ) + } +}` + +export const setWindowContentSize = async ( + electronRpc: { invoke(method: string, params?: unknown): Promise }, + electronObjectId: string, + targetId: string, + width: number, + height: number, +): Promise => { + await DevtoolsProtocolRuntime.callFunctionOn(electronRpc, { + arguments: [{ value: targetId }, { value: width }, { value: height }], + awaitPromise: true, + functionDeclaration: setWindowContentSizeScript, + objectId: electronObjectId, + }) +} diff --git a/packages/initialization-worker/test/PrepareBoth.test.ts b/packages/initialization-worker/test/PrepareBoth.test.ts index f19febc3e6..a3fe52f9cc 100644 --- a/packages/initialization-worker/test/PrepareBoth.test.ts +++ b/packages/initialization-worker/test/PrepareBoth.test.ts @@ -1,5 +1,7 @@ import { expect, jest, test } from '@jest/globals' +const mockSetWindowContentSize = jest.fn(async (..._args: unknown[]) => {}) + jest.unstable_mockModule('../src/parts/WaitForDebuggerListening/WaitForDebuggerListening.ts', () => { return { WaitForDebuggerListening: {}, @@ -59,6 +61,12 @@ jest.unstable_mockModule('../src/parts/DevtoolsProtocol/DevtoolsProtocol.ts', () } }) +jest.unstable_mockModule('../src/parts/SetWindowContentSize/SetWindowContentSize.ts', () => { + return { + setWindowContentSize: mockSetWindowContentSize, + } +}) + const { prepareBoth } = await import('../src/parts/PrepareBoth/PrepareBoth.ts') test('prepareBoth returns real electron process id from runtime evaluation', async () => { @@ -79,4 +87,5 @@ test('prepareBoth returns real electron process id from runtime evaluation', asy ) expect(result.pid).toBe(9876) + expect(mockSetWindowContentSize).toHaveBeenCalledWith(expect.anything(), 'electron-object', 'target-id', 1024, 768) }) diff --git a/packages/initialization-worker/test/SetWindowContentSize.test.ts b/packages/initialization-worker/test/SetWindowContentSize.test.ts new file mode 100644 index 0000000000..bd4d63f0b3 --- /dev/null +++ b/packages/initialization-worker/test/SetWindowContentSize.test.ts @@ -0,0 +1,32 @@ +import { expect, test } from '@jest/globals' +import * as SetWindowContentSize from '../src/parts/SetWindowContentSize/SetWindowContentSize.ts' + +test('setWindowContentSize resizes the window for the target web contents', async () => { + const calls: Array<{ method: string; params: any }> = [] + const electronRpc = { + invoke: async (method: string, params: any) => { + calls.push({ method, params }) + return { + result: { + result: { + type: 'undefined', + }, + }, + } + }, + } + + await SetWindowContentSize.setWindowContentSize(electronRpc, 'electron-object', 'target-id', 1024, 768) + + expect(calls).toEqual([ + { + method: 'Runtime.callFunctionOn', + params: { + arguments: [{ value: 'target-id' }, { value: 1024 }, { value: 768 }], + awaitPromise: true, + functionDeclaration: expect.stringContaining('browserWindow.setContentSize(width, height, false)'), + objectId: 'electron-object', + }, + }, + ]) +}) diff --git a/packages/video-recording-worker/src/parts/Ffmpeg/Ffmpeg.ts b/packages/video-recording-worker/src/parts/Ffmpeg/Ffmpeg.ts index 3014b92ab5..b2938a1fc1 100644 --- a/packages/video-recording-worker/src/parts/Ffmpeg/Ffmpeg.ts +++ b/packages/video-recording-worker/src/parts/Ffmpeg/Ffmpeg.ts @@ -21,9 +21,7 @@ export const start = async (platform: string, outFile: string): Promise => throw new Error(`ffmpeg binary not found at ${ffmpegPath}`) } const fps = 25 - const width = 1024 - const height = 768 - const options = GetFfmpegOptions.getFfmpegOptions(fps, width, height, outFile) + const options = GetFfmpegOptions.getFfmpegOptions(fps, outFile) const childProcess = spawn(ffmpegPath, options, { stdio: ['pipe', 'pipe', 'pipe'], }) diff --git a/packages/video-recording-worker/src/parts/GetFfmpegOptions/GetFfmpegOptions.ts b/packages/video-recording-worker/src/parts/GetFfmpegOptions/GetFfmpegOptions.ts index 5005df9be5..00ac06a427 100644 --- a/packages/video-recording-worker/src/parts/GetFfmpegOptions/GetFfmpegOptions.ts +++ b/packages/video-recording-worker/src/parts/GetFfmpegOptions/GetFfmpegOptions.ts @@ -1,9 +1,7 @@ import * as Assert from '../Assert/Assert.ts' -export const getFfmpegOptions = (fps: number, width: number, height: number, outFile: string): readonly string[] => { +export const getFfmpegOptions = (fps: number, outFile: string): readonly string[] => { Assert.number(fps) - Assert.number(width) - Assert.number(height) Assert.string(outFile) const args = [ '-loglevel', @@ -42,8 +40,6 @@ export const getFfmpegOptions = (fps: number, width: number, height: number, out '1M', '-threads', '1', - '-vf', - `pad=${width}:${height}:0:0:gray,crop=${width}:${height}:0:0`, outFile, ] return args diff --git a/packages/video-recording-worker/test/GetFfmpegOptions.test.ts b/packages/video-recording-worker/test/GetFfmpegOptions.test.ts index 91f555afda..6eb66b3e59 100644 --- a/packages/video-recording-worker/test/GetFfmpegOptions.test.ts +++ b/packages/video-recording-worker/test/GetFfmpegOptions.test.ts @@ -2,7 +2,7 @@ import { expect, test } from '@jest/globals' import * as GetFfmpegOptions from '../src/parts/GetFfmpegOptions/GetFfmpegOptions.ts' test('getFfmpegOptions returns array of strings', () => { - const result = GetFfmpegOptions.getFfmpegOptions(25, 1024, 768, '/tmp/test.webm') + const result = GetFfmpegOptions.getFfmpegOptions(25, '/tmp/test.webm') expect(Array.isArray(result)).toBe(true) expect(result.length).toBeGreaterThan(0) for (const arg of result) { @@ -12,35 +12,30 @@ test('getFfmpegOptions returns array of strings', () => { test('getFfmpegOptions includes fps in arguments', () => { const fps = 30 - const result = GetFfmpegOptions.getFfmpegOptions(fps, 1024, 768, '/tmp/test.webm') + const result = GetFfmpegOptions.getFfmpegOptions(fps, '/tmp/test.webm') expect(result).toContain('30') }) test('getFfmpegOptions includes output file in arguments', () => { const outFile = '/tmp/output.webm' - const result = GetFfmpegOptions.getFfmpegOptions(25, 1024, 768, outFile) + const result = GetFfmpegOptions.getFfmpegOptions(25, outFile) expect(result).toContain(outFile) }) -test('getFfmpegOptions includes video filter with dimensions', () => { - const width = 1920 - const height = 1080 - const result = GetFfmpegOptions.getFfmpegOptions(25, width, height, '/tmp/test.webm') - const filterArg = result.find((arg) => arg.includes('pad=') && arg.includes('crop=')) - expect(filterArg).toBeDefined() - expect(filterArg).toContain(`pad=${width}:${height}`) - expect(filterArg).toContain(`crop=${width}:${height}`) +test('getFfmpegOptions preserves the dimensions provided by Chrome', () => { + const result = GetFfmpegOptions.getFfmpegOptions(25, '/tmp/test.webm') + expect(result).not.toContain('-vf') }) test('getFfmpegOptions includes required codec arguments', () => { - const result = GetFfmpegOptions.getFfmpegOptions(25, 1024, 768, '/tmp/test.webm') + const result = GetFfmpegOptions.getFfmpegOptions(25, '/tmp/test.webm') expect(result).toContain('-c:v') expect(result).toContain('vp8') expect(result).toContain('mjpeg') }) test('getFfmpegOptions includes error loglevel', () => { - const result = GetFfmpegOptions.getFfmpegOptions(25, 1024, 768, '/tmp/test.webm') + const result = GetFfmpegOptions.getFfmpegOptions(25, '/tmp/test.webm') expect(result).toContain('-loglevel') expect(result).toContain('error') })