From 663e10355a6b108b9781f67ff23080185352d136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=80=E7=AE=80aw?= <2801819967@qq.com> Date: Tue, 17 Mar 2026 21:54:59 +0800 Subject: [PATCH] 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 --- cli/src/native/cdp/chrome.rs | 80 ++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/cli/src/native/cdp/chrome.rs b/cli/src/native/cdp/chrome.rs index 806b54a..6fabe79 100644 --- a/cli/src/native/cdp/chrome.rs +++ b/cli/src/native/cdp/chrome.rs @@ -100,6 +100,7 @@ impl Default for LaunchOptions { struct ChromeArgs { args: Vec, + user_data_dir: PathBuf, temp_user_data_dir: Option, } @@ -142,17 +143,18 @@ fn build_chrome_args(options: &LaunchOptions) -> Result { 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 { Ok(ChromeArgs { args, + user_data_dir, temp_user_data_dir, }) } @@ -230,9 +233,14 @@ pub fn launch_chrome(options: &LaunchOptions) -> Result { fn try_launch_chrome(chrome_path: &Path, options: &LaunchOptions) -> Result { 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| { 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 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) -> Result { - 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 { + 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, + deadline: std::time::Instant, +) -> Result { let prefix = "DevTools listening on "; let mut stderr_lines: Vec = Vec::new();