clearcote 0.38.0 → 0.39.0

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/README.md CHANGED
@@ -203,7 +203,9 @@ if (!verdict.coherent) console.warn(verdict.warnings);
203
203
 
204
204
  It reads the (unmasked) WebGL vendor/renderer the page sees, flags a software rasterizer (a fatal headless tell — enable the canvas bridge or run headed on a real GPU) and an incoherent vendor/renderer pair. Pass a second arg (`claimedGpu`) to also assert the rendered family.
205
205
 
206
- The renderer *string* alone is not enough: a persona renames the backend, so a SwiftShader fallback stops looking like one. The check therefore also measures limits the string cannot move — `MAX_TEXTURE_SIZE`, the vertex/fragment uniform-vector pair, and an actual 16384-wide texture allocation — and reports them as `maxTextureSize`, `maxVertexUniformVectors`, `maxFragmentUniformVectors` and `canAllocate16kTexture`. A renderer naming a desktop GPU while `MAX_TEXTURE_SIZE` is below 16384 is a spoof over a software rasterizer and comes back `coherent: false`. **Headless on Linux with no GPU hits exactly this** — Chromium falls back to SwiftShader (8192) whatever the persona claims, and no launch flag changes it on a display-less host. Run headed under Xvfb, or use the canvas bridge.
206
+ The renderer *string* alone is not enough: a persona renames the backend, so a SwiftShader fallback stops looking like one. The check therefore also measures limits the string cannot move — `MAX_TEXTURE_SIZE`, the vertex/fragment uniform-vector pair, and an actual 16384-wide texture allocation — and reports them as `maxTextureSize`, `maxVertexUniformVectors`, `maxFragmentUniformVectors` and `canAllocate16kTexture`. A renderer naming a desktop GPU while `MAX_TEXTURE_SIZE` is below 16384 is a spoof over a software rasterizer and comes back `coherent: false`. **Headless on Linux with no GPU hits exactly this** when Chromium renders through SwiftShader (8192): a Windows persona always does, and a Linux persona does on a host without Mesa's EGL (`libegl1`). Run headed under Xvfb, or use the canvas bridge.
207
+
208
+ On a Linux host the SDK picks the software backend that matches the GPU the persona names: a Linux persona (Mesa/OpenGL) renders through Mesa's own GL — over the X display when one is reachable (headed, or headless with `DISPLAY` pointing at an Xvfb), otherwise over EGL when the host has Mesa's EGL (`libegl1` on Debian/Ubuntu) — and a Windows persona (Direct3D11) renders through SwiftShader, whose limits match Direct3D11. Your own `--use-angle=` / `--use-gl=` in `args` wins.
207
209
 
208
210
  ### Hardened launch defaults
209
211
 
@@ -0,0 +1,11 @@
1
+ /** One browser-level CDP WebSocket. `send()` resolves with its own reply; events are dropped. */
2
+ export interface CdpConnection {
3
+ send(method: string, params?: Record<string, unknown>, sessionId?: string): Promise<Record<string, unknown>>;
4
+ close(): void;
5
+ }
6
+ /**
7
+ * Open a CDP WebSocket (ws:// or wss://). Rejects when the endpoint refuses the upgrade or does not
8
+ * answer within `timeoutMs`; each `send()` gets the same budget for its reply.
9
+ */
10
+ export declare function connectCdp(wsUrl: string, timeoutMs: number): Promise<CdpConnection>;
11
+ //# sourceMappingURL=cdpws.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdpws.d.ts","sourceRoot":"","sources":["../src/cdpws.ts"],"names":[],"mappings":"AAeA,iGAAiG;AACjG,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7G,KAAK,IAAI,IAAI,CAAC;CACf;AA4BD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA+InF"}
package/dist/cdpws.js ADDED
@@ -0,0 +1,208 @@
1
+ // A minimal CDP client over a WebSocket, on Node's standard library only. Port of the Python SDK's
2
+ // `_cdpws.py`.
3
+ //
4
+ // serve() launches the engine itself, so there is no Playwright connection to borrow, and Node only
5
+ // has a global WebSocket from 22 on while the SDK supports 20. The headless window fit used to ride
6
+ // on that global, so on Node 20 it was silently skipped: the browser got its display but kept the
7
+ // engine's default window, about 1000px wide inside a 1920px screen. What the fit needs is small: a
8
+ // handful of request/response calls on the browser endpoint (flat sessions for the one page it
9
+ // reads), text frames only, never a subscription. That is all this implements, deliberately not a
10
+ // general client.
11
+ import { createHash, randomBytes } from "node:crypto";
12
+ import net from "node:net";
13
+ import tls from "node:tls";
14
+ const WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
15
+ const OP_CONTINUATION = 0x0, OP_TEXT = 0x1, OP_BINARY = 0x2, OP_CLOSE = 0x8, OP_PING = 0x9, OP_PONG = 0xa;
16
+ /** A client frame: FIN set, payload masked (RFC 6455 5.3 requires it of clients). */
17
+ function frame(opcode, payload) {
18
+ const n = payload.length;
19
+ let head;
20
+ if (n < 126) {
21
+ head = Buffer.from([0x80 | opcode, 0x80 | n]);
22
+ }
23
+ else if (n < 65536) {
24
+ head = Buffer.alloc(4);
25
+ head[0] = 0x80 | opcode;
26
+ head[1] = 0x80 | 126;
27
+ head.writeUInt16BE(n, 2);
28
+ }
29
+ else {
30
+ head = Buffer.alloc(10);
31
+ head[0] = 0x80 | opcode;
32
+ head[1] = 0x80 | 127;
33
+ head.writeBigUInt64BE(BigInt(n), 2);
34
+ }
35
+ const mask = randomBytes(4);
36
+ const masked = Buffer.alloc(n);
37
+ for (let i = 0; i < n; i++)
38
+ masked[i] = payload[i] ^ mask[i & 3];
39
+ return Buffer.concat([head, mask, masked]);
40
+ }
41
+ /**
42
+ * Open a CDP WebSocket (ws:// or wss://). Rejects when the endpoint refuses the upgrade or does not
43
+ * answer within `timeoutMs`; each `send()` gets the same budget for its reply.
44
+ */
45
+ export function connectCdp(wsUrl, timeoutMs) {
46
+ return new Promise((resolveOpen, rejectOpen) => {
47
+ let u;
48
+ try {
49
+ u = new URL(wsUrl);
50
+ }
51
+ catch {
52
+ return rejectOpen(new Error(`expected a ws:// CDP URL, got ${JSON.stringify(wsUrl)}`));
53
+ }
54
+ if ((u.protocol !== "ws:" && u.protocol !== "wss:") || !u.hostname) {
55
+ return rejectOpen(new Error(`expected a ws:// CDP URL, got ${JSON.stringify(wsUrl)}`));
56
+ }
57
+ const secure = u.protocol === "wss:";
58
+ const host = u.hostname.replace(/^\[(.*)\]$/, "$1"); // an IPv6 literal comes bracketed
59
+ const port = Number(u.port) || (secure ? 443 : 80);
60
+ const key = randomBytes(16).toString("base64");
61
+ const accept = createHash("sha1").update(key + WS_GUID).digest("base64");
62
+ let open = false;
63
+ let closed = false;
64
+ let buf = Buffer.alloc(0);
65
+ let parts = [];
66
+ let next = 0;
67
+ const pending = new Map();
68
+ const sock = secure
69
+ ? tls.connect({ host, port, servername: net.isIP(host) ? undefined : host })
70
+ : net.connect({ host, port });
71
+ const openTimer = setTimeout(() => fail(new Error("CDP connect timed out")), timeoutMs);
72
+ /** End the connection once: settle everything still waiting, then let the socket go. */
73
+ function fail(err, closeFrame) {
74
+ if (closed)
75
+ return;
76
+ closed = true;
77
+ clearTimeout(openTimer);
78
+ if (closeFrame) {
79
+ // Flush the close frame before letting go; an endpoint that never answers it is cut off.
80
+ sock.end(closeFrame);
81
+ setTimeout(() => sock.destroy(), 1000).unref();
82
+ }
83
+ else {
84
+ sock.destroy();
85
+ }
86
+ if (!open)
87
+ rejectOpen(err);
88
+ for (const p of pending.values())
89
+ p.reject(err);
90
+ pending.clear();
91
+ }
92
+ function onMessage(text) {
93
+ let m;
94
+ try {
95
+ m = JSON.parse(text);
96
+ }
97
+ catch {
98
+ return;
99
+ }
100
+ const p = m.id === undefined ? undefined : pending.get(m.id);
101
+ if (!p)
102
+ return; // an event, or a stale reply: this client subscribes to nothing
103
+ pending.delete(m.id);
104
+ if (m.error)
105
+ p.reject(new Error(m.error.message ?? JSON.stringify(m.error)));
106
+ else
107
+ p.resolve(m.result ?? {});
108
+ }
109
+ /** Consume every complete frame in `buf` (fragments joined; pings answered). */
110
+ function readFrames() {
111
+ while (!closed && buf.length >= 2) {
112
+ const fin = buf[0] & 0x80, opcode = buf[0] & 0x0f, masked = buf[1] & 0x80;
113
+ let n = buf[1] & 0x7f;
114
+ let off = 2;
115
+ if (n === 126) {
116
+ if (buf.length < 4)
117
+ return;
118
+ n = buf.readUInt16BE(2);
119
+ off = 4;
120
+ }
121
+ else if (n === 127) {
122
+ if (buf.length < 10)
123
+ return;
124
+ n = Number(buf.readBigUInt64BE(2));
125
+ off = 10;
126
+ }
127
+ const mask = masked ? off : -1;
128
+ if (masked)
129
+ off += 4;
130
+ if (buf.length < off + n)
131
+ return;
132
+ let payload = buf.subarray(off, off + n);
133
+ if (mask >= 0)
134
+ payload = Buffer.from(payload.map((b, i) => b ^ buf[mask + (i & 3)]));
135
+ buf = buf.subarray(off + n);
136
+ if (opcode === OP_CLOSE)
137
+ return fail(new Error("CDP connection closed"));
138
+ if (opcode === OP_PING) {
139
+ sock.write(frame(OP_PONG, payload));
140
+ continue;
141
+ }
142
+ if (opcode === OP_PONG)
143
+ continue;
144
+ if (opcode === OP_TEXT || opcode === OP_BINARY || opcode === OP_CONTINUATION) {
145
+ parts.push(payload);
146
+ if (fin) {
147
+ const text = Buffer.concat(parts).toString("utf8");
148
+ parts = [];
149
+ onMessage(text);
150
+ }
151
+ }
152
+ }
153
+ }
154
+ /** The upgrade response: `101` and the accept hash RFC 6455 4.1 derives from our key. */
155
+ function readHandshake() {
156
+ const end = buf.indexOf("\r\n\r\n");
157
+ if (end < 0)
158
+ return;
159
+ const lines = buf.subarray(0, end).toString("latin1").split("\r\n");
160
+ buf = buf.subarray(end + 4);
161
+ if (!/^HTTP\/1\.[01] 101\b/.test(lines[0]))
162
+ return fail(new Error(`CDP WebSocket upgrade refused: ${lines[0]}`));
163
+ const line = lines.slice(1).find((l) => /^sec-websocket-accept:/i.test(l));
164
+ const got = line?.slice(line.indexOf(":") + 1).trim();
165
+ if (got !== accept)
166
+ return fail(new Error("CDP WebSocket upgrade answered with the wrong accept key"));
167
+ open = true;
168
+ clearTimeout(openTimer);
169
+ resolveOpen(connection);
170
+ readFrames();
171
+ }
172
+ const connection = {
173
+ send(method, params = {}, sessionId) {
174
+ return new Promise((resolve, reject) => {
175
+ if (closed)
176
+ return reject(new Error("CDP connection closed"));
177
+ const id = ++next;
178
+ const t = setTimeout(() => { pending.delete(id); reject(new Error(`${method} timed out`)); }, timeoutMs);
179
+ pending.set(id, {
180
+ resolve: (v) => { clearTimeout(t); resolve(v); },
181
+ reject: (e) => { clearTimeout(t); reject(e); },
182
+ });
183
+ sock.write(frame(OP_TEXT, Buffer.from(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) }))));
184
+ });
185
+ },
186
+ close() {
187
+ fail(new Error("CDP connection closed"), frame(OP_CLOSE, Buffer.from([0x03, 0xe8]))); // 1000: normal closure
188
+ },
189
+ };
190
+ sock.on(secure ? "secureConnect" : "connect", () => {
191
+ const path = (u.pathname || "/") + u.search;
192
+ sock.write(`GET ${path} HTTP/1.1\r\nHost: ${u.host}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n` +
193
+ `Sec-WebSocket-Key: ${key}\r\nSec-WebSocket-Version: 13\r\n\r\n`);
194
+ });
195
+ sock.on("data", (chunk) => {
196
+ if (closed)
197
+ return;
198
+ buf = buf.length ? Buffer.concat([buf, chunk]) : chunk;
199
+ if (open)
200
+ readFrames();
201
+ else
202
+ readHandshake();
203
+ });
204
+ sock.on("error", (err) => fail(new Error(`CDP connection failed: ${err.message}`)));
205
+ sock.on("close", () => fail(new Error("CDP connection closed")));
206
+ });
207
+ }
208
+ //# sourceMappingURL=cdpws.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdpws.js","sourceRoot":"","sources":["../src/cdpws.ts"],"names":[],"mappings":"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"}
@@ -86,13 +86,19 @@ export declare function applyHeadlessGeometry(opts: Record<string, unknown>, see
86
86
  /**
87
87
  * The bounds correction, given what the window reported after the first attempt.
88
88
  *
89
- * Requested bounds and reported outerHeight are not the same quantity: on 149 the window reports
90
- * 33px less than the bounds height it was given, so fitting bounds to the work area lands 33px short
91
- * of maximized (real maximized captures have outer == avail). Rather than hardcode 33, measure the
92
- * shortfall and add it back — that self-tunes if the engine changes. Never asks for more than the
93
- * shortfall, so the window cannot be pushed past the work area.
89
+ * Requested bounds and reported outerHeight are not always the same quantity: on 149 the window
90
+ * reported 33px less than the bounds height it was given, and on 150 a persona's window does (33px in
91
+ * a launch, 89 in serve(); without a persona it reports the bounds exactly). Fitting bounds to the
92
+ * work area then lands short of maximized (real maximized captures have outer == avail). Rather than
93
+ * hardcode it, measure the shortfall and add it back — that self-tunes if the engine changes. Never
94
+ * asks for more than the shortfall.
95
+ *
96
+ * `room` is the space from the window's position to the work area's far edges. On an axis where the
97
+ * window is smaller than that, the correction never takes the bounds past the edge: only a window that
98
+ * fills the work area needs bounds beyond it (the persona engine then clamps the window to the work
99
+ * area), and an engine without a persona does not clamp at all.
94
100
  */
95
- export declare function fitPlan(avail: number[], outer: number[]): [number, number] | null;
101
+ export declare function fitPlan(avail: number[], outer: number[], room?: readonly number[] | null): [number, number] | null;
96
102
  /**
97
103
  * Size the headless window to the display's work area — the persona's (regime 1) or the one
98
104
  * `--screen-info` set (regime 2) — so the page reports a maximized window (outer == avail) instead
@@ -174,17 +180,19 @@ export interface ServedFit {
174
180
  /**
175
181
  * Put the served browser's first window on its display's work area (or `windowSize`, clamped into
176
182
  * it); under a persona, first make the headless display the persona's own. Over the caller's CDP
177
- * connection, on the first page target. Only Target/Emulation/Browser commands plus one
178
- * Runtime.evaluate (never Runtime.enable) on that page, and every change is browser-level, so
183
+ * connection, on the first page target. Only Target/Emulation/Browser commands plus
184
+ * Runtime.evaluate reads (never Runtime.enable) on that page, and every change is browser-level, so
179
185
  * nothing lingers on the page when the connection closes.
180
186
  *
181
187
  * Returns null when it could not act (no page, an implausible persona display, or an engine without
182
188
  * `Emulation.updateScreen` under a persona, whose window cannot outgrow 800x600 without it). Never
183
- * throws: geometry must not be able to fail a launch.
189
+ * throws: geometry must not be able to fail a launch. `settleMs` bounds the wait for each resize to
190
+ * reach the page ({@link settledOuter}).
184
191
  */
185
192
  export declare function fitWindowOverCdp(cdp: CdpSend, opts: {
186
193
  persona: boolean;
187
194
  windowSize?: Size | null;
195
+ settleMs?: number;
188
196
  }): Promise<ServedFit | null>;
189
197
  /** {@link fitWindowOverCdp} on the SDK's own short connection to a served browser. Never throws. */
190
198
  export declare function fitServedWindow(wsUrl: string | undefined, opts: {
@@ -1 +1 @@
1
- {"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../src/geometry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../src/geometry.ts"],"names":[],"mappings":"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"}
package/dist/geometry.js CHANGED
@@ -52,6 +52,7 @@
52
52
  */
53
53
  import { createHash } from "node:crypto";
54
54
  import { gunzipSync } from "node:zlib";
55
+ import { connectCdp } from "./cdpws.js";
55
56
  import { lightStealthScreen } from "./fingerprint.js";
56
57
  /**
57
58
  * The linux-x64 engine's window frame around an EMULATED viewport: outer = inner + (WIDTH, HEIGHT).
@@ -209,18 +210,75 @@ function bounds(width, height) {
209
210
  /**
210
211
  * The bounds correction, given what the window reported after the first attempt.
211
212
  *
212
- * Requested bounds and reported outerHeight are not the same quantity: on 149 the window reports
213
- * 33px less than the bounds height it was given, so fitting bounds to the work area lands 33px short
214
- * of maximized (real maximized captures have outer == avail). Rather than hardcode 33, measure the
215
- * shortfall and add it back — that self-tunes if the engine changes. Never asks for more than the
216
- * shortfall, so the window cannot be pushed past the work area.
213
+ * Requested bounds and reported outerHeight are not always the same quantity: on 149 the window
214
+ * reported 33px less than the bounds height it was given, and on 150 a persona's window does (33px in
215
+ * a launch, 89 in serve(); without a persona it reports the bounds exactly). Fitting bounds to the
216
+ * work area then lands short of maximized (real maximized captures have outer == avail). Rather than
217
+ * hardcode it, measure the shortfall and add it back — that self-tunes if the engine changes. Never
218
+ * asks for more than the shortfall.
219
+ *
220
+ * `room` is the space from the window's position to the work area's far edges. On an axis where the
221
+ * window is smaller than that, the correction never takes the bounds past the edge: only a window that
222
+ * fills the work area needs bounds beyond it (the persona engine then clamps the window to the work
223
+ * area), and an engine without a persona does not clamp at all.
217
224
  */
218
- export function fitPlan(avail, outer) {
225
+ export function fitPlan(avail, outer, room) {
219
226
  const dw = avail[0] - outer[0];
220
227
  const dh = avail[1] - outer[1];
221
228
  if (dw <= 0 && dh <= 0)
222
229
  return null;
223
- return [avail[0] + Math.max(dw, 0), avail[1] + Math.max(dh, 0)];
230
+ const capped = (want, i) => (room && avail[i] < room[i] ? Math.min(want, room[i]) : want);
231
+ return [capped(avail[0] + Math.max(dw, 0), 0), capped(avail[1] + Math.max(dh, 0), 1)];
232
+ }
233
+ /** How long a window resize may take to reach the page before a read is taken as it stands. */
234
+ const SETTLE_MS = 1000;
235
+ const SETTLE_POLL_MS = 20;
236
+ const sameSize = (a, b) => a[0] === b[0] && a[1] === b[1];
237
+ /**
238
+ * Read `[outerWidth, outerHeight]` until it reflects the last `Browser.setWindowBounds`. The call
239
+ * returns before the page has the new window size, so a read straight after it can still report the
240
+ * previous one (measured on 150/linux-x64: for tens of milliseconds). Correcting from such a
241
+ * read once asked for 1440+495 px on a 1920 px screen. Settled means the asked-for size, or a size
242
+ * other than `before` that is still there one poll later; `settled` is false when neither happened
243
+ * within `timeoutMs`.
244
+ */
245
+ async function settledOuter(read, before, want, timeoutMs = SETTLE_MS) {
246
+ const deadline = Date.now() + timeoutMs;
247
+ let outer = await read();
248
+ for (;;) {
249
+ if (sameSize(outer, want))
250
+ return { outer, settled: true };
251
+ if (Date.now() >= deadline)
252
+ return { outer, settled: false };
253
+ await new Promise((r) => setTimeout(r, SETTLE_POLL_MS));
254
+ const next = await read();
255
+ if (!sameSize(outer, before) && sameSize(next, outer))
256
+ return { outer, settled: true };
257
+ outer = next;
258
+ }
259
+ }
260
+ /**
261
+ * Give the window `target`, then correct what the engine reports (see {@link fitPlan}), on settled
262
+ * reads only: a correction is planned from a size the page has actually seen, and kept only when the
263
+ * page then sees a window no larger than `target`; otherwise the bounds go back to `target`. With no
264
+ * settled read there is nothing to correct from, and `target` lies inside the work area. Returns the
265
+ * last outer size read.
266
+ */
267
+ async function fitWindow(setBounds, read, target, room, settleMs) {
268
+ const [w, h] = target;
269
+ const before = await read();
270
+ await setBounds(w, h);
271
+ const first = await settledOuter(read, before, target, settleMs);
272
+ const plan = first.settled ? fitPlan(target, first.outer, room) : null;
273
+ if (!plan)
274
+ return first.outer;
275
+ await setBounds(plan[0], plan[1]);
276
+ const fixed = await settledOuter(read, first.outer, plan, settleMs);
277
+ // Overshooting would trade one impossible geometry for another (outer > target).
278
+ if (fixed.settled && fixed.outer[0] <= w && fixed.outer[1] <= h)
279
+ return fixed.outer;
280
+ await setBounds(w, h);
281
+ return (await settledOuter(read, fixed.outer, target, settleMs)).outer;
224
282
  }
225
283
  /**
226
284
  * Size the headless window to the display's work area — the persona's (regime 1) or the one
@@ -241,18 +299,7 @@ export async function fitWindowToWorkArea(page, args) {
241
299
  return null;
242
300
  const cdp = await page.context().newCDPSession(page);
243
301
  const { windowId } = (await cdp.send("Browser.getWindowForTarget"));
244
- await cdp.send("Browser.setWindowBounds", { windowId, bounds: bounds(avail[0], avail[1]) });
245
- let outer = (await page.evaluate(OUTER_JS));
246
- const plan = fitPlan(avail, outer);
247
- if (plan) {
248
- await cdp.send("Browser.setWindowBounds", { windowId, bounds: bounds(plan[0], plan[1]) });
249
- outer = (await page.evaluate(OUTER_JS));
250
- // Overshooting would trade one impossible geometry for another (outer > avail).
251
- if (outer[0] > avail[0] || outer[1] > avail[1]) {
252
- await cdp.send("Browser.setWindowBounds", { windowId, bounds: bounds(avail[0], avail[1]) });
253
- outer = (await page.evaluate(OUTER_JS));
254
- }
255
- }
302
+ const outer = await fitWindow((w, h) => cdp.send("Browser.setWindowBounds", { windowId, bounds: bounds(w, h) }), async () => (await page.evaluate(OUTER_JS)), [avail[0], avail[1]], [avail[0], avail[1]], SETTLE_MS);
256
303
  return [outer[0], outer[1]];
257
304
  }
258
305
  catch {
@@ -294,8 +341,7 @@ export function geometryIsCoherent(screen, avail, inner, outer) {
294
341
  // fit runs on each context's first page. A raw endpoint has neither, and a CDP emulation override
295
342
  // would not survive either, being scoped to the session that set it. What every
296
343
  // target and every client of a served browser inherits is the headless DISPLAY and the real WINDOW,
297
- // both browser-level. Untouched, measured on 153.0.8010.36/win-x64 (cc-gateway
298
- // tools/probe-geometry.mjs):
344
+ // both browser-level. Untouched, measured on 153.0.8010.36/win-x64:
299
345
  //
300
346
  // no persona: screen 800x600 avail 800x600 outer 780x580 <- the headless surface
301
347
  // no persona + --window-size=1440,900: outer 1440x900 on that 800x600 screen
@@ -379,13 +425,14 @@ const DISPLAY_JS = "[screen.width, screen.height, screen.availLeft, screen.avail
379
425
  /**
380
426
  * Put the served browser's first window on its display's work area (or `windowSize`, clamped into
381
427
  * it); under a persona, first make the headless display the persona's own. Over the caller's CDP
382
- * connection, on the first page target. Only Target/Emulation/Browser commands plus one
383
- * Runtime.evaluate (never Runtime.enable) on that page, and every change is browser-level, so
428
+ * connection, on the first page target. Only Target/Emulation/Browser commands plus
429
+ * Runtime.evaluate reads (never Runtime.enable) on that page, and every change is browser-level, so
384
430
  * nothing lingers on the page when the connection closes.
385
431
  *
386
432
  * Returns null when it could not act (no page, an implausible persona display, or an engine without
387
433
  * `Emulation.updateScreen` under a persona, whose window cannot outgrow 800x600 without it). Never
388
- * throws: geometry must not be able to fail a launch.
434
+ * throws: geometry must not be able to fail a launch. `settleMs` bounds the wait for each resize to
435
+ * reach the page ({@link settledOuter}).
389
436
  */
390
437
  export async function fitWindowOverCdp(cdp, opts) {
391
438
  let sessionId;
@@ -417,19 +464,9 @@ export async function fitWindowOverCdp(cdp, opts) {
417
464
  const top = at + Math.min(10, ah - h);
418
465
  const { windowId } = (await cdp.send("Browser.getWindowForTarget", { targetId: page.targetId }));
419
466
  const setBounds = (bw, bh) => cdp.send("Browser.setWindowBounds", { windowId, bounds: { left, top, width: Math.round(bw), height: Math.round(bh) } });
420
- await setBounds(w, h);
421
- let outer = await read(OUTER_JS);
422
- // Same self-tuning as fitWindowToWorkArea: an engine that reports less than the bounds it was
423
- // given gets the shortfall added back, and an overshoot is reverted.
424
- const plan = fitPlan([w, h], outer);
425
- if (plan) {
426
- await setBounds(plan[0], plan[1]);
427
- outer = await read(OUTER_JS);
428
- if (outer[0] > w || outer[1] > h) {
429
- await setBounds(w, h);
430
- outer = await read(OUTER_JS);
431
- }
432
- }
467
+ // Same as fitWindowToWorkArea: an engine that reports less than the bounds it was given gets the
468
+ // shortfall added back, an overshoot is reverted, and only settled reads count.
469
+ const outer = await fitWindow(setBounds, () => read(OUTER_JS), [w, h], [al + aw - left, at + ah - top], opts.settleMs ?? SETTLE_MS);
433
470
  return { display: { width: sw, height: sh, availWidth: aw, availHeight: ah }, outer: [outer[0], outer[1]] };
434
471
  }
435
472
  catch {
@@ -440,69 +477,15 @@ export async function fitWindowOverCdp(cdp, opts) {
440
477
  await cdp.send("Target.detachFromTarget", { sessionId }).catch(() => { });
441
478
  }
442
479
  }
443
- /** A minimal CDP client on Node's built-in WebSocket (Node 22+), or null where there is none. */
444
- async function openCdp(wsUrl, timeoutMs) {
445
- const WS = globalThis.WebSocket;
446
- if (!WS)
447
- return null;
448
- const ws = new WS(wsUrl);
449
- await new Promise((resolve, reject) => {
450
- const t = setTimeout(() => reject(new Error("CDP connect timed out")), timeoutMs);
451
- ws.onopen = () => { clearTimeout(t); resolve(); };
452
- ws.onerror = () => { clearTimeout(t); reject(new Error("CDP connect failed")); };
453
- });
454
- let next = 0;
455
- const pending = new Map();
456
- ws.onmessage = (ev) => {
457
- let m;
458
- try {
459
- m = JSON.parse(String(ev.data));
460
- }
461
- catch {
462
- return;
463
- }
464
- const p = m.id === undefined ? undefined : pending.get(m.id);
465
- if (!p)
466
- return;
467
- pending.delete(m.id);
468
- if (m.error)
469
- p.reject(new Error(m.error.message));
470
- else
471
- p.resolve(m.result ?? {});
472
- };
473
- ws.onclose = () => {
474
- for (const p of pending.values())
475
- p.reject(new Error("CDP connection closed"));
476
- pending.clear();
477
- };
478
- return {
479
- send(method, params = {}, sessionId) {
480
- return new Promise((resolve, reject) => {
481
- const id = ++next;
482
- const t = setTimeout(() => { pending.delete(id); reject(new Error(`${method} timed out`)); }, timeoutMs);
483
- pending.set(id, {
484
- resolve: (v) => { clearTimeout(t); resolve(v); },
485
- reject: (e) => { clearTimeout(t); reject(e); },
486
- });
487
- ws.send(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) }));
488
- });
489
- },
490
- close() {
491
- try {
492
- ws.close();
493
- }
494
- catch { /* ignore */ }
495
- },
496
- };
497
- }
498
480
  /** {@link fitWindowOverCdp} on the SDK's own short connection to a served browser. Never throws. */
499
481
  export async function fitServedWindow(wsUrl, opts) {
500
482
  if (!wsUrl)
501
483
  return null;
502
484
  let cdp = null;
503
485
  try {
504
- cdp = await openCdp(wsUrl, opts.timeoutMs ?? 5000);
505
- return cdp ? await fitWindowOverCdp(cdp, opts) : null;
486
+ // The SDK's own WebSocket client, not Node's global one: that only exists from Node 22 on.
487
+ cdp = await connectCdp(wsUrl, opts.timeoutMs ?? 5000);
488
+ return await fitWindowOverCdp(cdp, opts);
506
489
  }
507
490
  catch {
508
491
  return null;