use headless_chrome::{Browser, LaunchOptions};
use super::BrowserFactory;
use crate::error::{BrowserPoolError, Result};
pub struct ChromeBrowserFactory {
launch_options_fn: Box<dyn Fn() -> Result<LaunchOptions<'static>> + Send + Sync>,
}
impl ChromeBrowserFactory {
pub fn new<F>(launch_options_fn: F) -> Self
where
F: Fn() -> Result<LaunchOptions<'static>> + Send + Sync + 'static,
{
Self {
launch_options_fn: Box::new(launch_options_fn),
}
}
pub fn with_defaults() -> Self {
log::debug!("🛠️ Creating ChromeBrowserFactory with auto-detect");
Self::new(|| {
create_chrome_options(None).map_err(|e| BrowserPoolError::Configuration(e.to_string()))
})
}
pub fn with_path(chrome_path: String) -> Self {
log::debug!(
" Creating ChromeBrowserFactory with custom path: {}",
chrome_path
);
Self::new(move || {
create_chrome_options(Some(&chrome_path))
.map_err(|e| BrowserPoolError::Configuration(e.to_string()))
})
}
}
impl BrowserFactory for ChromeBrowserFactory {
fn create(&self) -> Result<Browser> {
log::trace!("🛠️ ChromeBrowserFactory::create() called");
let options = (self.launch_options_fn)()?;
log::debug!("🚀 Launching Chrome browser...");
Browser::new(options).map_err(|e| {
log::error!("❌ Chrome launch failed: {}", e);
BrowserPoolError::BrowserCreation(e.to_string())
})
}
}
pub fn create_chrome_options(
chrome_path: Option<&str>,
) -> std::result::Result<LaunchOptions<'static>, Box<dyn std::error::Error + Send + Sync>> {
match chrome_path {
Some(path) => log::debug!("🛠️ Creating Chrome options with custom path: {}", path),
None => log::debug!("🛠️ Creating Chrome options (auto-detect browser)"),
}
let mut builder = LaunchOptions::default_builder();
if let Some(path) = chrome_path {
builder.path(Some(path.to_string().into()));
log::trace!("📍 Chrome path set to: {}", path);
} else {
log::trace!("🔍 Chrome path: auto-detect");
}
builder
.headless(true) .sandbox(false) .disable_default_args(true) .args(vec![
"--disable-dev-shm-usage".as_ref(), "--disable-crash-reporter".as_ref(), "--max_old_space_size=1024".as_ref(), "--disable-gpu-compositing".as_ref(),
"--disable-software-rasterizer".as_ref(),
"--disable-accelerated-2d-canvas".as_ref(),
"--disable-gl-drawing-for-tests".as_ref(),
"--disable-webgl".as_ref(),
"--disable-webgl2".as_ref(),
"--disable-extensions".as_ref(), "--disable-plugins".as_ref(), "--disable-sync".as_ref(), "--disable-default-apps".as_ref(), "--disable-web-security".as_ref(), "--enable-automation".as_ref(), "--disable-background-timer-throttling".as_ref(), "--disable-backgrounding-occluded-windows".as_ref(), "--disable-hang-monitor".as_ref(), "--disable-popup-blocking".as_ref(), "--disable-renderer-backgrounding".as_ref(), "--disable-ipc-flooding-protection".as_ref(), ])
.build()
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync> {
let path_msg = chrome_path.unwrap_or("auto-detect");
log::error!(
"❌ Failed to build Chrome launch options (path: {}): {}",
path_msg,
e
);
e.into()
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chrome_factory_creation() {
let _factory = ChromeBrowserFactory::with_defaults();
let _factory_with_path = ChromeBrowserFactory::with_path("/custom/chrome/path".to_string());
}
#[test]
fn test_create_chrome_options() {
let result = create_chrome_options(None);
assert!(
result.is_ok(),
"Auto-detect Chrome options should build successfully: {:?}",
result.err()
);
let result = create_chrome_options(Some("/custom/chrome/path"));
assert!(
result.is_ok(),
"Custom path Chrome options should build successfully: {:?}",
result.err()
);
}
}