Skip to main content

browser_control/launch/
chromium.rs

1//! Chromium-family browser launcher (Chrome, Edge, Chromium, Brave).
2
3use std::fs::File;
4use std::process::Stdio;
5
6use anyhow::{Context, Result};
7use tokio::process::Command;
8
9use crate::detect::{Engine, Installed};
10
11use super::{
12    allocate_free_port, background_after_launch, configure_session_detachment, wait_for_endpoint,
13    LaunchOpts, LaunchedHandle,
14};
15
16pub async fn launch(installed: &Installed, opts: LaunchOpts) -> Result<LaunchedHandle> {
17    let port = allocate_free_port().context("allocating CDP port")?;
18
19    if let Some(parent) = opts.profile_dir.parent() {
20        let _ = std::fs::create_dir_all(parent);
21    }
22    std::fs::create_dir_all(&opts.profile_dir)
23        .with_context(|| format!("creating profile dir {}", opts.profile_dir.display()))?;
24
25    if let Some(dir) = &opts.download_dir {
26        // Best-effort: a failed write must not block the launch; the MCP
27        // server's runtime override still applies.
28        if let Err(e) = super::download_prefs::apply_chromium(&opts.profile_dir, dir) {
29            tracing::warn!(error = %e, "writing Chromium download prefs failed");
30        }
31    }
32
33    let log_path = opts.profile_dir.join("browser.log");
34    let log_file =
35        File::create(&log_path).with_context(|| format!("creating {}", log_path.display()))?;
36    let log_clone = log_file
37        .try_clone()
38        .context("cloning log file handle for stderr")?;
39
40    let mut cmd = Command::new(&installed.executable);
41    cmd.arg(format!("--remote-debugging-port={port}"))
42        .arg(format!("--user-data-dir={}", opts.profile_dir.display()))
43        .arg("--no-first-run")
44        .arg("--no-default-browser-check")
45        .arg("--disable-component-update");
46    if opts.headless {
47        cmd.arg("--headless=new");
48    } else {
49        cmd.arg("--start-minimized");
50    }
51    cmd.arg("about:blank");
52
53    cmd.stdin(Stdio::null())
54        .stdout(Stdio::from(log_file))
55        .stderr(Stdio::from(log_clone))
56        .kill_on_drop(false);
57    configure_session_detachment(&mut cmd);
58
59    let mut child = cmd
60        .spawn()
61        .with_context(|| format!("spawning {}", installed.executable.display()))?;
62    let pid = child.id().context("child has no pid")?;
63
64    let endpoint = wait_for_endpoint(port, &mut child, &log_path).await?;
65    if !opts.headless {
66        background_after_launch(pid);
67    }
68
69    Ok(LaunchedHandle {
70        pid,
71        port,
72        endpoint,
73        engine: Engine::Cdp,
74        profile_dir: opts.profile_dir,
75        child: Some(child),
76    })
77}