autokap 1.3.22 → 1.3.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +25 -0
- package/dist/xvfb-process.d.ts +28 -4
- package/dist/xvfb-process.js +65 -4
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -954,6 +954,31 @@ export class Browser {
|
|
|
954
954
|
}
|
|
955
955
|
}, { styleId: CAPTURE_HIDE_STYLE_ID, css: getCaptureHideCSS() });
|
|
956
956
|
instance.page = await instance.context.newPage();
|
|
957
|
+
// Cloud Run only: force the page window to fullscreen via CDP. The
|
|
958
|
+
// --kiosk launch flag is silently ignored by Chromium for windows
|
|
959
|
+
// created by Playwright via Target.createTarget (CDP) — kiosk only
|
|
960
|
+
// applies to the initial window opened by Chrome at startup, and
|
|
961
|
+
// Playwright doesn't open one (it uses --no-startup-window). Without
|
|
962
|
+
// explicit fullscreen, the CDP-created window has the full chrome UI
|
|
963
|
+
// (tab strip, address bar, omnibox, extension icons) which leaks into
|
|
964
|
+
// ffmpeg x11grab captures regardless of matchbox WM. Browser.setWindowBounds
|
|
965
|
+
// with windowState=fullscreen hides the chrome from the page side and
|
|
966
|
+
// matchbox honors the fullscreen request on the X side.
|
|
967
|
+
if (isLinuxWithGpu) {
|
|
968
|
+
try {
|
|
969
|
+
const cdp = await instance.context.newCDPSession(instance.page);
|
|
970
|
+
const { windowId } = await cdp.send('Browser.getWindowForTarget');
|
|
971
|
+
await cdp.send('Browser.setWindowBounds', {
|
|
972
|
+
windowId,
|
|
973
|
+
bounds: { windowState: 'fullscreen' },
|
|
974
|
+
});
|
|
975
|
+
logger.info(`[capture] Cloud clip capture: window ${windowId} forced to fullscreen via CDP`);
|
|
976
|
+
await cdp.detach().catch(() => undefined);
|
|
977
|
+
}
|
|
978
|
+
catch (err) {
|
|
979
|
+
logger.warn(`[capture] Cloud clip capture: CDP fullscreen request failed: ${err.message}`);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
957
982
|
// Cloud Run only: query the WebGL UNMASKED_RENDERER_WEBGL string once
|
|
958
983
|
// per browser launch and log it. This is the source of truth for which
|
|
959
984
|
// GPU backend Chromium picked — flag combinations alone are not enough,
|
package/dist/xvfb-process.d.ts
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Xvfb (X virtual framebuffer) process wrapper
|
|
2
|
+
* Xvfb (X virtual framebuffer) process wrapper, plus a minimal window
|
|
3
|
+
* manager (matchbox) so headed Chromium's --kiosk flag actually takes
|
|
4
|
+
* effect.
|
|
3
5
|
*
|
|
4
6
|
* Spins up a virtual X display that headed Chromium can render to. Used by
|
|
5
7
|
* cloud clip capture so the recording surface is reachable by ffmpeg via
|
|
6
8
|
* `x11grab` — bypassing the slow `Page.captureScreenshot` CDP path that
|
|
7
9
|
* software-rasterized Linux compositors cap at ~6 fps on heavy React UIs.
|
|
8
10
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* Why matchbox: Chromium's --kiosk / --start-fullscreen flags request
|
|
12
|
+
* fullscreen mode from the WM via _NET_WM_STATE_FULLSCREEN. Without a WM
|
|
13
|
+
* to honor that request, Chromium silently falls back to a normal
|
|
14
|
+
* decorated window with the full chrome (tab bar, address bar, infobars)
|
|
15
|
+
* visible — and ffmpeg x11grab captures all of it, ruining clips.
|
|
16
|
+
* matchbox-window-manager (~100KB, designed for embedded kiosks)
|
|
17
|
+
* auto-fullscreens every window with no decorations. With matchbox,
|
|
18
|
+
* --kiosk takes effect and the page fills the whole framebuffer cleanly.
|
|
19
|
+
*
|
|
20
|
+
* Lifecycle: Xvfb + matchbox both run for the entire browser process
|
|
21
|
+
* lifetime. ffmpeg recording starts/stops per BEGIN_CLIP/END_CLIP and
|
|
22
|
+
* grabs from the same display.
|
|
12
23
|
*/
|
|
13
24
|
export interface XvfbProcessOptions {
|
|
14
25
|
/** Display number (without leading colon). E.g. 99 → DISPLAY=:99. */
|
|
@@ -21,10 +32,23 @@ export interface XvfbProcessOptions {
|
|
|
21
32
|
export declare class XvfbProcess {
|
|
22
33
|
private readonly opts;
|
|
23
34
|
private process;
|
|
35
|
+
private wmProcess;
|
|
24
36
|
private exited;
|
|
25
37
|
constructor(opts: XvfbProcessOptions);
|
|
26
38
|
/** DISPLAY string suitable for `process.env.DISPLAY` (e.g. `:99`). */
|
|
27
39
|
get display(): string;
|
|
28
40
|
start(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Start matchbox-window-manager. Required for Chromium's --kiosk to
|
|
43
|
+
* actually take effect: kiosk requests fullscreen via the WM, and
|
|
44
|
+
* without a WM Chromium falls back to a normal decorated window with
|
|
45
|
+
* the full chrome leaking into x11grab captures. matchbox is tiny
|
|
46
|
+
* (~100KB), auto-fullscreens every window, and removes decorations.
|
|
47
|
+
* `-use_titlebar no` and `-use_cursor no` are belt-and-suspenders;
|
|
48
|
+
* matchbox already defaults to no decorations, but the explicit flags
|
|
49
|
+
* also stop it from drawing its own cursor (which would superimpose
|
|
50
|
+
* on Chromium's cursor and confuse the cursor overlay script).
|
|
51
|
+
*/
|
|
52
|
+
private startWindowManager;
|
|
29
53
|
stop(): Promise<void>;
|
|
30
54
|
}
|
package/dist/xvfb-process.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Xvfb (X virtual framebuffer) process wrapper
|
|
2
|
+
* Xvfb (X virtual framebuffer) process wrapper, plus a minimal window
|
|
3
|
+
* manager (matchbox) so headed Chromium's --kiosk flag actually takes
|
|
4
|
+
* effect.
|
|
3
5
|
*
|
|
4
6
|
* Spins up a virtual X display that headed Chromium can render to. Used by
|
|
5
7
|
* cloud clip capture so the recording surface is reachable by ffmpeg via
|
|
6
8
|
* `x11grab` — bypassing the slow `Page.captureScreenshot` CDP path that
|
|
7
9
|
* software-rasterized Linux compositors cap at ~6 fps on heavy React UIs.
|
|
8
10
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* Why matchbox: Chromium's --kiosk / --start-fullscreen flags request
|
|
12
|
+
* fullscreen mode from the WM via _NET_WM_STATE_FULLSCREEN. Without a WM
|
|
13
|
+
* to honor that request, Chromium silently falls back to a normal
|
|
14
|
+
* decorated window with the full chrome (tab bar, address bar, infobars)
|
|
15
|
+
* visible — and ffmpeg x11grab captures all of it, ruining clips.
|
|
16
|
+
* matchbox-window-manager (~100KB, designed for embedded kiosks)
|
|
17
|
+
* auto-fullscreens every window with no decorations. With matchbox,
|
|
18
|
+
* --kiosk takes effect and the page fills the whole framebuffer cleanly.
|
|
19
|
+
*
|
|
20
|
+
* Lifecycle: Xvfb + matchbox both run for the entire browser process
|
|
21
|
+
* lifetime. ffmpeg recording starts/stops per BEGIN_CLIP/END_CLIP and
|
|
22
|
+
* grabs from the same display.
|
|
12
23
|
*/
|
|
13
24
|
import { spawn } from 'node:child_process';
|
|
14
25
|
import fs from 'node:fs/promises';
|
|
@@ -16,9 +27,11 @@ import { logger } from './logger.js';
|
|
|
16
27
|
const XVFB_READY_TIMEOUT_MS = 5_000;
|
|
17
28
|
const XVFB_READY_POLL_MS = 50;
|
|
18
29
|
const XVFB_STOP_GRACE_MS = 2_000;
|
|
30
|
+
const WM_STARTUP_GRACE_MS = 200;
|
|
19
31
|
export class XvfbProcess {
|
|
20
32
|
opts;
|
|
21
33
|
process = null;
|
|
34
|
+
wmProcess = null;
|
|
22
35
|
exited = false;
|
|
23
36
|
constructor(opts) {
|
|
24
37
|
this.opts = opts;
|
|
@@ -72,6 +85,7 @@ export class XvfbProcess {
|
|
|
72
85
|
await fs.access(socketPath);
|
|
73
86
|
logger.info(`[xvfb] ready on display ${this.display} (${this.opts.width}×${this.opts.height}) ` +
|
|
74
87
|
`after ${Date.now() - startedAt}ms`);
|
|
88
|
+
await this.startWindowManager();
|
|
75
89
|
return;
|
|
76
90
|
}
|
|
77
91
|
catch {
|
|
@@ -81,11 +95,58 @@ export class XvfbProcess {
|
|
|
81
95
|
}
|
|
82
96
|
throw new Error(`Xvfb did not become ready within ${XVFB_READY_TIMEOUT_MS}ms`);
|
|
83
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Start matchbox-window-manager. Required for Chromium's --kiosk to
|
|
100
|
+
* actually take effect: kiosk requests fullscreen via the WM, and
|
|
101
|
+
* without a WM Chromium falls back to a normal decorated window with
|
|
102
|
+
* the full chrome leaking into x11grab captures. matchbox is tiny
|
|
103
|
+
* (~100KB), auto-fullscreens every window, and removes decorations.
|
|
104
|
+
* `-use_titlebar no` and `-use_cursor no` are belt-and-suspenders;
|
|
105
|
+
* matchbox already defaults to no decorations, but the explicit flags
|
|
106
|
+
* also stop it from drawing its own cursor (which would superimpose
|
|
107
|
+
* on Chromium's cursor and confuse the cursor overlay script).
|
|
108
|
+
*/
|
|
109
|
+
async startWindowManager() {
|
|
110
|
+
this.wmProcess = spawn('matchbox-window-manager', ['-use_titlebar', 'no', '-use_cursor', 'no'], {
|
|
111
|
+
env: { ...process.env, DISPLAY: this.display },
|
|
112
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
113
|
+
detached: false,
|
|
114
|
+
});
|
|
115
|
+
this.wmProcess.stderr?.on('data', (chunk) => {
|
|
116
|
+
const text = String(chunk).trim();
|
|
117
|
+
if (text)
|
|
118
|
+
logger.warn(`[matchbox-wm] ${text}`);
|
|
119
|
+
});
|
|
120
|
+
this.wmProcess.on('exit', (code, signal) => {
|
|
121
|
+
// Don't mark this.exited — Xvfb is still alive. Just log if abnormal.
|
|
122
|
+
if (code !== 0 && code !== null) {
|
|
123
|
+
logger.warn(`[matchbox-wm] exited unexpectedly: code=${code} signal=${signal}`);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
this.wmProcess.on('error', (err) => {
|
|
127
|
+
logger.error(`[matchbox-wm] spawn error: ${err.message}`);
|
|
128
|
+
});
|
|
129
|
+
// Brief grace so matchbox can register as the WM with the X server
|
|
130
|
+
// before Chromium opens its first window.
|
|
131
|
+
await new Promise(r => setTimeout(r, WM_STARTUP_GRACE_MS));
|
|
132
|
+
logger.info(`[matchbox-wm] started on display ${this.display}`);
|
|
133
|
+
}
|
|
84
134
|
async stop() {
|
|
85
135
|
if (!this.process)
|
|
86
136
|
return;
|
|
87
137
|
const proc = this.process;
|
|
88
138
|
this.process = null;
|
|
139
|
+
const wmProc = this.wmProcess;
|
|
140
|
+
this.wmProcess = null;
|
|
141
|
+
// Tear down matchbox first — it would die when Xvfb goes anyway, but
|
|
142
|
+
// killing it explicitly avoids a SIGPIPE / "X connection lost" warning
|
|
143
|
+
// in the logs.
|
|
144
|
+
if (wmProc) {
|
|
145
|
+
try {
|
|
146
|
+
wmProc.kill('SIGTERM');
|
|
147
|
+
}
|
|
148
|
+
catch { /* already dead */ }
|
|
149
|
+
}
|
|
89
150
|
proc.kill('SIGTERM');
|
|
90
151
|
await new Promise(resolve => {
|
|
91
152
|
const timer = setTimeout(() => {
|