console Chrome never outlives its run: exit kills it, SIGINT / SIGTERM / SIGHUP close it gracefully, a detached watchdog ends it when the parent dies (SIGKILL included; a reused pid is never killed); every start ends headless orphans on storebound profiles and names orphaned windows; headless runs on macOS prefer Chromium / Chrome for Testing over Google Chrome.app (an orphan kept the owner's Chrome from opening from the Dock); console fill closes the window on errors; pitfall 68; tests
This commit is contained in:
parent
b350781ab5
commit
89d94d6993
10 changed files with 372 additions and 17 deletions
|
|
@ -32,6 +32,9 @@
|
|||
// kills it, so Chromium flushes its cookie store. Apple keeps a long sign-in only with "Keep me signed in" / "Trust
|
||||
// this browser" ticked (storebound says so when it sees the sign-in page).
|
||||
// Cookies are never printed, logged or sent anywhere: only file names and counts are reported.
|
||||
// The Chrome storebound starts never outlives its run (cli/console/chrome-guard.ts, an October 2026 incident: an
|
||||
// orphaned headless Chrome kept the owner's own Chrome from opening from the Dock): exit, Ctrl+C, SIGTERM / SIGHUP and
|
||||
// a watchdog for the parent's death end it, and every start first ends the headless ones earlier runs left behind.
|
||||
// Sources (checked October 2026): DBSC https://blog.google/security/protecting-cookies-with-device-bound-session-credentials/
|
||||
// and https://developer.chrome.com/docs/web-platform/device-bound-session-credentials ; Google's sign-out on suspicious
|
||||
// session cookies https://knowledge.workspace.google.com/admin/security/investigate-and-take-action-on-suspicious-session-cookies ;
|
||||
|
|
@ -43,6 +46,7 @@ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync
|
|||
import { homedir, tmpdir } from 'node:os';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { chromiumFor, installedBrowsers, type LaunchDeps, persistentExecutable, profileHolder, sandboxArgs } from '../shots/browser';
|
||||
import { guardChrome, reapStaleChromes } from './chrome-guard';
|
||||
import * as rt from '../lib/rt';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
|
@ -280,6 +284,7 @@ export function browserPlan(o: Pick<OpenOpts, 'store' | 'cdp' | 'fresh' | 'useCh
|
|||
* is closed (macOS keeps Chrome running without windows: then storebound closes it gracefully).
|
||||
*/
|
||||
async function startOwnChrome(root: string, mode: 'copy' | 'profile', exe: { label: string; path: string }, userDataDir: string, o: OpenOpts, cleanup: () => void): Promise<ConsoleBrowser> {
|
||||
await reapStaleChromes({ say: o.say });
|
||||
const holder = profileHolder(userDataDir);
|
||||
if (holder) throw new Error(`the browser profile ${userDataDir} is still open in another browser (process ${holder}), probably an earlier storebound run that waits for its window to close. Close that window (or end that run) and start again`);
|
||||
const portFile = join(userDataDir, 'DevToolsActivePort');
|
||||
|
|
@ -287,7 +292,12 @@ async function startOwnChrome(root: string, mode: 'copy' | 'profile', exe: { lab
|
|||
const args = consoleChromeArgs(userDataDir, { headless: o.headless ?? false });
|
||||
const proc = rt.spawn([exe.path, ...args], { stdin: 'ignore', stdout: 'ignore', stderr: 'ignore' });
|
||||
let exited = false;
|
||||
const gone = proc.exited.then(() => { exited = true; });
|
||||
// eslint-disable-next-line prefer-const
|
||||
let close: () => Promise<void>;
|
||||
// until it exits: process exit, a signal (graceful close, then the copy is removed) or this process dying ends it
|
||||
const release = proc.pid ? guardChrome(proc.pid, `--user-data-dir=${userDataDir}`, async () => { await close(); cleanup(); }) : () => {};
|
||||
const gone = proc.exited.then(() => { exited = true; release(); });
|
||||
close = async () => { if (!exited) { proc.kill('SIGTERM'); await Promise.race([gone, rt.sleep(5000)]); } };
|
||||
const end = Date.now() + (o.launchTimeoutMs ?? 30_000);
|
||||
let endpoint: string | null = null;
|
||||
while (!endpoint && !exited && Date.now() < end) { endpoint = devToolsEndpoint(userDataDir); if (!endpoint) await rt.sleep(100); }
|
||||
|
|
@ -304,15 +314,17 @@ async function startOwnChrome(root: string, mode: 'copy' | 'profile', exe: { lab
|
|||
throw new Error(`${exe.label}: could not attach (${(e as Error).message.split('\n')[0]})`, { cause: e });
|
||||
}
|
||||
const context = browser.contexts()[0] ?? await browser.newContext();
|
||||
const close = async () => {
|
||||
let closing: Promise<void> | null = null;
|
||||
close = () => (closing ??= (async () => {
|
||||
if (exited) return;
|
||||
await gracefully(async () => { const s = await browser.newBrowserCDPSession(); await s.send('Browser.close'); });
|
||||
await Promise.race([gone, rt.sleep(10_000)]);
|
||||
if (!exited) { proc.kill('SIGTERM'); await Promise.race([gone, rt.sleep(5000)]); }
|
||||
};
|
||||
})());
|
||||
let closed = false;
|
||||
const done = gone.then(async () => { closed = true; cleanup(); await rt.sleep(300); cleanup(); });
|
||||
const page = await ownTab(context);
|
||||
let page: any;
|
||||
try { page = await ownTab(context); } catch (e) { await close(); throw e; } // never a running Chrome without its run
|
||||
const watch = (p: any) => p.on('close', () => { if (!exited && context.pages().length === 0) void close(); });
|
||||
for (const p of context.pages()) watch(p);
|
||||
context.on('page', watch);
|
||||
|
|
@ -323,7 +335,7 @@ async function startOwnChrome(root: string, mode: 'copy' | 'profile', exe: { lab
|
|||
function executableFor(root: string, kind: 'visible' | 'chrome', o: OpenOpts): Promise<{ label: string; path: string }> {
|
||||
return (async () => {
|
||||
const installed = o.launchDeps?.installed ?? installedBrowsers(o.launchDeps?.pw ?? await chromiumFor(root).catch(() => null));
|
||||
return persistentExecutable(kind, installed);
|
||||
return persistentExecutable(kind, installed, process.env, { headless: o.headless ?? false, platform: o.platform ?? process.platform });
|
||||
})();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue