Enhance Chrome launch process with user-data-dir and timeout (#852)

* fix: improve Chrome launch process by enhancing user-data-dir handling and adding timeout for DevToolsActivePort

* fix: enhance Chrome launch process by improving user data directory handling and timeout management for DevToolsActivePort

* fix: remove unused wait_for_ws_url function to streamline Chrome launch process
This commit is contained in:
简简aw
2026-03-17 08:54:59 -05:00
committed by GitHub
parent 7734bb2702
commit 663e10355a
+64 -16
View File
@@ -100,6 +100,7 @@ impl Default for LaunchOptions {
struct ChromeArgs {
args: Vec<String>,
user_data_dir: PathBuf,
temp_user_data_dir: Option<PathBuf>,
}
@@ -142,17 +143,18 @@ fn build_chrome_args(options: &LaunchOptions) -> Result<ChromeArgs, String> {
args.push(format!("--proxy-bypass-list={}", bypass));
}
let temp_user_data_dir = if let Some(ref profile) = options.profile {
let (user_data_dir, temp_user_data_dir) = if let Some(ref profile) = options.profile {
let expanded = expand_tilde(profile);
let dir = PathBuf::from(&expanded);
args.push(format!("--user-data-dir={}", expanded));
None
(dir, None)
} else {
let dir =
std::env::temp_dir().join(format!("agent-browser-chrome-{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir)
.map_err(|e| format!("Failed to create temp profile dir: {}", e))?;
args.push(format!("--user-data-dir={}", dir.display()));
Some(dir)
(dir.clone(), Some(dir))
};
if options.allow_file_access {
@@ -189,6 +191,7 @@ fn build_chrome_args(options: &LaunchOptions) -> Result<ChromeArgs, String> {
Ok(ChromeArgs {
args,
user_data_dir,
temp_user_data_dir,
})
}
@@ -230,9 +233,14 @@ pub fn launch_chrome(options: &LaunchOptions) -> Result<ChromeProcess, String> {
fn try_launch_chrome(chrome_path: &Path, options: &LaunchOptions) -> Result<ChromeProcess, String> {
let ChromeArgs {
args,
user_data_dir,
temp_user_data_dir,
} = build_chrome_args(options)?;
// Mitigate stale DevToolsActivePort risk (e.g., previous crash left it behind).
// Puppeteer does similar cleanup before spawning.
let _ = std::fs::remove_file(user_data_dir.join("DevToolsActivePort"));
let cleanup_temp_dir = |dir: &Option<PathBuf>| {
if let Some(ref d) = dir {
let _ = std::fs::remove_dir_all(d);
@@ -250,19 +258,33 @@ fn try_launch_chrome(chrome_path: &Path, options: &LaunchOptions) -> Result<Chro
format!("Failed to launch Chrome at {:?}: {}", chrome_path, e)
})?;
let stderr = child.stderr.take().ok_or_else(|| {
let _ = child.kill();
cleanup_temp_dir(&temp_user_data_dir);
"Failed to capture Chrome stderr".to_string()
})?;
let reader = BufReader::new(stderr);
// Shared overall deadline so we don't double-wait (poll + stderr fallback).
let deadline = std::time::Instant::now() + Duration::from_secs(30);
let ws_url = match wait_for_ws_url(reader) {
// Primary path: use DevToolsActivePort written into user-data-dir.
// This is more reliable on Windows than scraping stderr for "DevTools listening on ...",
// which can be missing/empty depending on how Chrome is launched.
let ws_url = match wait_for_devtools_active_port(&mut child, &user_data_dir, deadline) {
Ok(url) => url,
Err(e) => {
let _ = child.kill();
cleanup_temp_dir(&temp_user_data_dir);
return Err(e);
Err(primary_err) => {
// Fallback: scrape stderr (legacy behavior) for better diagnostics.
let stderr = child.stderr.take().ok_or_else(|| {
let _ = child.kill();
cleanup_temp_dir(&temp_user_data_dir);
"Failed to capture Chrome stderr".to_string()
})?;
let reader = BufReader::new(stderr);
match wait_for_ws_url_until(reader, deadline) {
Ok(url) => url,
Err(fallback_err) => {
let _ = child.kill();
cleanup_temp_dir(&temp_user_data_dir);
return Err(format!(
"{}\n(also tried parsing stderr) {}",
primary_err, fallback_err
));
}
}
}
};
@@ -273,8 +295,34 @@ fn try_launch_chrome(chrome_path: &Path, options: &LaunchOptions) -> Result<Chro
})
}
fn wait_for_ws_url(reader: BufReader<std::process::ChildStderr>) -> Result<String, String> {
let deadline = std::time::Instant::now() + Duration::from_secs(30);
fn wait_for_devtools_active_port(
child: &mut Child,
user_data_dir: &Path,
deadline: std::time::Instant,
) -> Result<String, String> {
let poll_interval = Duration::from_millis(50);
while std::time::Instant::now() <= deadline {
if let Ok(Some(_status)) = child.try_wait() {
// If Chrome already exited, stop waiting.
break;
}
if let Some((port, ws_path)) = read_devtools_active_port(user_data_dir) {
let ws_url = format!("ws://127.0.0.1:{}{}", port, ws_path);
return Ok(ws_url);
}
std::thread::sleep(poll_interval);
}
Err("Timeout waiting for DevToolsActivePort".to_string())
}
fn wait_for_ws_url_until(
reader: BufReader<std::process::ChildStderr>,
deadline: std::time::Instant,
) -> Result<String, String> {
let prefix = "DevTools listening on ";
let mut stderr_lines: Vec<String> = Vec::new();