MCPcopy
hub / github.com/garrytan/gstack / launch

Method launch

browse/src/browser-manager.ts:340–420  ·  view source on GitHub ↗
()

Source from the content-addressed store, hash-verified

338 }
339
340 async launch() {
341 // ─── Extension Support ────────────────────────────────────
342 // BROWSE_EXTENSIONS_DIR points to an unpacked Chrome extension directory.
343 // Extensions only work in headed mode, so we use an off-screen window.
344 const extensionsDir = process.env.BROWSE_EXTENSIONS_DIR;
345 const { STEALTH_LAUNCH_ARGS, buildGStackLaunchArgs } = await import('./stealth');
346 const launchArgs: string[] = [...STEALTH_LAUNCH_ARGS, ...buildGStackLaunchArgs()];
347 let useHeadless = true;
348
349 // Docker/CI/root: Chromium sandbox requires unprivileged user namespaces which
350 // are typically disabled in containers and are never available for the root
351 // user on Linux. Detect all three cases and add --no-sandbox automatically.
352 const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
353 if (process.env.CI || process.env.CONTAINER || isRoot) {
354 launchArgs.push('--no-sandbox');
355 }
356
357 if (extensionsDir) {
358 // Skip --load-extension when running against a custom Chromium build that
359 // already bakes the extension in (e.g., GBrowser / GStack Browser.app).
360 // Loading it twice causes a ServiceWorkerState::SetWorkerId DCHECK crash.
361 if (!isCustomChromium()) {
362 launchArgs.push(
363 `--disable-extensions-except=${extensionsDir}`,
364 `--load-extension=${extensionsDir}`,
365 );
366 }
367 launchArgs.push('--window-position=-9999,-9999', '--window-size=1,1');
368 useHeadless = false; // extensions require headed mode; off-screen window simulates headless
369 console.log(`[browse] Extensions loaded from: ${extensionsDir}`);
370 }
371
372 this.browser = await chromium.launch({
373 headless: useHeadless,
374 // On Windows, Chromium's sandbox fails when the server is spawned through
375 // the Bun→Node process chain (GitHub #276). Disable it — local daemon
376 // browsing user-specified URLs has marginal sandbox benefit. Also disabled
377 // on Linux root/CI/container, where the sandbox requires unprivileged user
378 // namespaces that aren't available.
379 chromiumSandbox: shouldEnableChromiumSandbox(),
380 ...(launchArgs.length > 0 ? { args: launchArgs } : {}),
381 ...(this.proxyConfig ? { proxy: this.proxyConfig } : {}),
382 });
383
384 // Chromium disconnect → distinguish clean user-quit from crash. Both
385 // events look identical to Playwright (one 'disconnected' fires), but
386 // the underlying ChildProcess exit code separates them:
387 // exitCode === 0 → clean quit (user Cmd+Q on macOS, normal shutdown)
388 // exitCode !== 0 → crash, signal-kill, or OOM
389 // Process supervisors (gbrowser's gbd) consume our exit code: code 0
390 // means "user wanted this, don't restart"; non-zero means "crash, please
391 // bring me back." Without this distinction every Cmd+Q gets treated as
392 // a crash and the user-visible window keeps respawning.
393 this.browser.on('disconnected', () => {
394 void handleChromiumDisconnect(this.browser);
395 });
396
397 const contextOptions: BrowserContextOptions = {

Calls 7

newTabMethod · 0.95
buildGStackLaunchArgsFunction · 0.85
isCustomChromiumFunction · 0.85
handleChromiumDisconnectFunction · 0.85
applyStealthFunction · 0.85
pushMethod · 0.45

Tested by

no test coverage detected