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:
@@ -100,6 +100,7 @@ impl Default for LaunchOptions {
|
|||||||
|
|
||||||
struct ChromeArgs {
|
struct ChromeArgs {
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
|
user_data_dir: PathBuf,
|
||||||
temp_user_data_dir: Option<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));
|
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 expanded = expand_tilde(profile);
|
||||||
|
let dir = PathBuf::from(&expanded);
|
||||||
args.push(format!("--user-data-dir={}", expanded));
|
args.push(format!("--user-data-dir={}", expanded));
|
||||||
None
|
(dir, None)
|
||||||
} else {
|
} else {
|
||||||
let dir =
|
let dir =
|
||||||
std::env::temp_dir().join(format!("agent-browser-chrome-{}", uuid::Uuid::new_v4()));
|
std::env::temp_dir().join(format!("agent-browser-chrome-{}", uuid::Uuid::new_v4()));
|
||||||
std::fs::create_dir_all(&dir)
|
std::fs::create_dir_all(&dir)
|
||||||
.map_err(|e| format!("Failed to create temp profile dir: {}", e))?;
|
.map_err(|e| format!("Failed to create temp profile dir: {}", e))?;
|
||||||
args.push(format!("--user-data-dir={}", dir.display()));
|
args.push(format!("--user-data-dir={}", dir.display()));
|
||||||
Some(dir)
|
(dir.clone(), Some(dir))
|
||||||
};
|
};
|
||||||
|
|
||||||
if options.allow_file_access {
|
if options.allow_file_access {
|
||||||
@@ -189,6 +191,7 @@ fn build_chrome_args(options: &LaunchOptions) -> Result<ChromeArgs, String> {
|
|||||||
|
|
||||||
Ok(ChromeArgs {
|
Ok(ChromeArgs {
|
||||||
args,
|
args,
|
||||||
|
user_data_dir,
|
||||||
temp_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> {
|
fn try_launch_chrome(chrome_path: &Path, options: &LaunchOptions) -> Result<ChromeProcess, String> {
|
||||||
let ChromeArgs {
|
let ChromeArgs {
|
||||||
args,
|
args,
|
||||||
|
user_data_dir,
|
||||||
temp_user_data_dir,
|
temp_user_data_dir,
|
||||||
} = build_chrome_args(options)?;
|
} = 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>| {
|
let cleanup_temp_dir = |dir: &Option<PathBuf>| {
|
||||||
if let Some(ref d) = dir {
|
if let Some(ref d) = dir {
|
||||||
let _ = std::fs::remove_dir_all(d);
|
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)
|
format!("Failed to launch Chrome at {:?}: {}", chrome_path, e)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let stderr = child.stderr.take().ok_or_else(|| {
|
// Shared overall deadline so we don't double-wait (poll + stderr fallback).
|
||||||
let _ = child.kill();
|
let deadline = std::time::Instant::now() + Duration::from_secs(30);
|
||||||
cleanup_temp_dir(&temp_user_data_dir);
|
|
||||||
"Failed to capture Chrome stderr".to_string()
|
|
||||||
})?;
|
|
||||||
let reader = BufReader::new(stderr);
|
|
||||||
|
|
||||||
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,
|
Ok(url) => url,
|
||||||
Err(e) => {
|
Err(primary_err) => {
|
||||||
let _ = child.kill();
|
// Fallback: scrape stderr (legacy behavior) for better diagnostics.
|
||||||
cleanup_temp_dir(&temp_user_data_dir);
|
let stderr = child.stderr.take().ok_or_else(|| {
|
||||||
return Err(e);
|
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> {
|
fn wait_for_devtools_active_port(
|
||||||
let deadline = std::time::Instant::now() + Duration::from_secs(30);
|
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 prefix = "DevTools listening on ";
|
||||||
let mut stderr_lines: Vec<String> = Vec::new();
|
let mut stderr_lines: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user