diff --git a/src/browser/chrome.launch-args.test.ts b/src/browser/chrome.launch-args.test.ts new file mode 100644 index 00000000000..c76d64cc4d4 --- /dev/null +++ b/src/browser/chrome.launch-args.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from "vitest"; +import { buildOpenClawChromeLaunchArgs } from "./chrome.js"; + +describe("browser chrome launch args", () => { + it("does not force an about:blank tab at startup", () => { + const args = buildOpenClawChromeLaunchArgs({ + resolved: { + enabled: true, + controlPort: 18791, + cdpProtocol: "http", + cdpHost: "127.0.0.1", + cdpIsLoopback: true, + cdpPortRangeStart: 18800, + cdpPortRangeEnd: 18810, + evaluateEnabled: false, + remoteCdpTimeoutMs: 1500, + remoteCdpHandshakeTimeoutMs: 3000, + extraArgs: [], + color: "#FF4500", + headless: false, + noSandbox: false, + attachOnly: false, + ssrfPolicy: { allowPrivateNetwork: true }, + defaultProfile: "openclaw", + profiles: { + openclaw: { cdpPort: 18800, color: "#FF4500" }, + }, + }, + profile: { + name: "openclaw", + cdpUrl: "http://127.0.0.1:18800", + cdpPort: 18800, + cdpHost: "127.0.0.1", + cdpProtocol: "http", + cdpPath: "", + cdpIsLoopback: true, + color: "#FF4500", + auth: undefined, + }, + userDataDir: "/tmp/openclaw-test-user-data", + }); + + expect(args).not.toContain("about:blank"); + expect(args).toContain("--remote-debugging-port=18800"); + expect(args).toContain("--user-data-dir=/tmp/openclaw-test-user-data"); + }); +}); diff --git a/src/browser/chrome.ts b/src/browser/chrome.ts index 1cb94cf39fb..c2ea1872859 100644 --- a/src/browser/chrome.ts +++ b/src/browser/chrome.ts @@ -85,6 +85,44 @@ function cdpUrlForPort(cdpPort: number) { return `http://127.0.0.1:${cdpPort}`; } +export function buildOpenClawChromeLaunchArgs(params: { + resolved: ResolvedBrowserConfig; + profile: ResolvedBrowserProfile; + userDataDir: string; +}): string[] { + const { resolved, profile, userDataDir } = params; + const args: string[] = [ + `--remote-debugging-port=${profile.cdpPort}`, + `--user-data-dir=${userDataDir}`, + "--no-first-run", + "--no-default-browser-check", + "--disable-sync", + "--disable-background-networking", + "--disable-component-update", + "--disable-features=Translate,MediaRouter", + "--disable-session-crashed-bubble", + "--hide-crash-restore-bubble", + "--password-store=basic", + ]; + + if (resolved.headless) { + args.push("--headless=new"); + args.push("--disable-gpu"); + } + if (resolved.noSandbox) { + args.push("--no-sandbox"); + args.push("--disable-setuid-sandbox"); + } + if (process.platform === "linux") { + args.push("--disable-dev-shm-usage"); + } + if (resolved.extraArgs.length > 0) { + args.push(...resolved.extraArgs); + } + + return args; +} + async function canOpenWebSocket(url: string, timeoutMs: number): Promise { return new Promise((resolve) => { const ws = openCdpWebSocket(url, { handshakeTimeoutMs: timeoutMs }); @@ -280,41 +318,11 @@ export async function launchOpenClawChrome( // First launch to create preference files if missing, then decorate and relaunch. const spawnOnce = () => { - const args: string[] = [ - `--remote-debugging-port=${profile.cdpPort}`, - `--user-data-dir=${userDataDir}`, - "--no-first-run", - "--no-default-browser-check", - "--disable-sync", - "--disable-background-networking", - "--disable-component-update", - "--disable-features=Translate,MediaRouter", - "--disable-session-crashed-bubble", - "--hide-crash-restore-bubble", - "--password-store=basic", - ]; - - if (resolved.headless) { - // Best-effort; older Chromes may ignore. - args.push("--headless=new"); - args.push("--disable-gpu"); - } - if (resolved.noSandbox) { - args.push("--no-sandbox"); - args.push("--disable-setuid-sandbox"); - } - if (process.platform === "linux") { - args.push("--disable-dev-shm-usage"); - } - - // Append user-configured extra arguments (e.g., stealth flags, window size) - if (resolved.extraArgs.length > 0) { - args.push(...resolved.extraArgs); - } - - // Always open a blank tab to ensure a target exists. - args.push("about:blank"); - + const args = buildOpenClawChromeLaunchArgs({ + resolved, + profile, + userDataDir, + }); return spawn(exe.path, args, { stdio: "pipe", env: {