#![allow(missing_docs)]
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use chromiumoxide::browser::{Browser, BrowserConfig};
use chromiumoxide::Handler as OxideHandler;
use tokio::sync::Mutex;
use super::chrome::{build_chrome_args, find_chrome, LaunchOptions};
pub struct OxideLaunch {
pub browser: Browser,
pub handler: OxideHandler,
pub executable: Option<PathBuf>,
pub ws_url: String,
pub temp_user_data_dir: Option<PathBuf>,
}
pub async fn launch_with_oxide(options: &LaunchOptions) -> Result<OxideLaunch, String> {
let chrome_args = build_chrome_args(options)?;
let mut builder = BrowserConfig::builder();
let executable = options
.executable_path
.as_ref()
.map(PathBuf::from)
.or_else(find_chrome);
if let Some(ref exe) = executable {
builder = builder.chrome_executable(exe);
}
let has_extensions = options.extensions.as_ref().is_some_and(|e| !e.is_empty());
if !options.headless || has_extensions {
builder = builder.with_head();
}
if let Some(ref exts) = options.extensions {
if !exts.is_empty() {
builder = builder.extensions(exts.clone());
}
}
builder = builder.user_data_dir(chrome_args.user_data_dir.clone());
for a in &chrome_args.args {
if a.starts_with("--user-data-dir=") {
continue;
}
if a == "--headless=new" || a == "--headless" || a.starts_with("--headless=") {
continue;
}
if a.starts_with("--load-extension=") || a.starts_with("--disable-extensions-except=") {
continue;
}
builder = builder.arg(a.as_str());
}
let config = builder
.build()
.map_err(|e| format!("chromiumoxide BrowserConfig: {e}"))?;
let (browser, handler) = Browser::launch(config)
.await
.map_err(|e| format!("chromiumoxide Browser::launch: {e}"))?;
let ws_url = browser.websocket_address().clone();
Ok(OxideLaunch {
browser,
handler,
executable,
ws_url,
temp_user_data_dir: chrome_args.temp_user_data_dir,
})
}
pub async fn finalize_browser(browser: Arc<Mutex<Browser>>) -> Result<(), String> {
let mut browser = match Arc::try_unwrap(browser) {
Ok(m) => m.into_inner(),
Err(shared) => {
let mut guard = shared.lock().await;
if let Err(e) = guard.close().await {
let _ = guard.kill().await;
return Err(format!("chromiumoxide close: {e}"));
}
match tokio::time::timeout(Duration::from_secs(5), guard.wait()).await {
Ok(Ok(_)) => {}
Ok(Err(e)) => {
let _ = guard.kill().await;
return Err(format!("chromiumoxide wait: {e}"));
}
Err(_) => {
let _ = guard.kill().await;
}
}
return Ok(());
}
};
if let Err(e) = browser.close().await {
let _ = browser.kill().await;
return Err(format!("chromiumoxide close: {e}"));
}
match tokio::time::timeout(Duration::from_secs(5), browser.wait()).await {
Ok(Ok(_)) => {}
Ok(Err(e)) => {
let _ = browser.kill().await;
return Err(format!("chromiumoxide wait: {e}"));
}
Err(_) => {
let _ = browser.kill().await;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn launch_options_default_is_headless_path() {
let o = LaunchOptions::default();
assert!(o.headless);
}
#[test]
fn build_args_feed_proxy_into_oxide_flag_list() {
let o = LaunchOptions {
proxy: Some("http://127.0.0.1:8080".to_string()),
..Default::default()
};
let args = build_chrome_args(&o).unwrap();
assert!(args
.args
.iter()
.any(|a| a == "--proxy-server=http://127.0.0.1:8080"));
if let Some(ref dir) = args.temp_user_data_dir {
let _ = std::fs::remove_dir_all(dir);
}
}
}