use std::path::PathBuf;
use anyhow::{Context, Result};
use chromiumoxide::browser::BrowserConfig;
use chromiumoxide::handler::Handler;
use chromiumoxide::{Browser, Page};
use futures::StreamExt;
use crate::error::BrowserError;
pub fn chrome_binary_candidates() -> Vec<PathBuf> {
if let Ok(p) = std::env::var("EARL_BROWSER_PATH") {
return vec![PathBuf::from(p)];
}
let mut candidates = vec![];
#[cfg(target_os = "macos")]
{
candidates.push(PathBuf::from(
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
));
candidates.push(PathBuf::from(
"/Applications/Chromium.app/Contents/MacOS/Chromium",
));
}
#[cfg(target_os = "linux")]
{
candidates.push(PathBuf::from("/usr/bin/google-chrome"));
candidates.push(PathBuf::from("/usr/bin/google-chrome-stable"));
candidates.push(PathBuf::from("/usr/bin/chromium-browser"));
candidates.push(PathBuf::from("/usr/bin/chromium"));
}
#[cfg(target_os = "windows")]
{
candidates.push(PathBuf::from(
r"C:\Program Files\Google\Chrome\Application\chrome.exe",
));
candidates.push(PathBuf::from(
r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
));
}
for name in &["chrome", "google-chrome", "chromium", "chromium-browser"] {
if let Ok(p) = which::which(name)
&& !candidates.contains(&p)
{
candidates.push(p);
}
}
candidates
}
pub fn find_chrome() -> Result<PathBuf> {
let candidates = chrome_binary_candidates();
for path in &candidates {
if path.exists() {
return Ok(path.clone());
}
}
let paths = candidates
.iter()
.map(|p| format!(" - {}", p.display()))
.collect::<Vec<_>>()
.join("\n");
Err(BrowserError::ChromeNotFound { paths }.into())
}
fn spawn_handler(mut handler: Handler) {
tokio::spawn(async move { while handler.next().await.is_some() {} });
}
pub async fn launch_chrome(headless: bool) -> Result<(Browser, String)> {
let chrome = find_chrome()?;
let mut config_builder = BrowserConfig::builder()
.chrome_executable(chrome)
.request_timeout(std::time::Duration::from_secs(3600));
if !headless {
config_builder = config_builder.with_head();
}
let config = config_builder
.build()
.map_err(|e| anyhow::anyhow!("browser config error: {e}"))?;
let (browser, handler) = Browser::launch(config).await.context("launching Chrome")?;
spawn_handler(handler);
let ws_url = browser.websocket_address().clone();
Ok((browser, ws_url))
}
pub async fn connect_chrome(ws_url: &str) -> Result<Browser> {
let (browser, handler) = Browser::connect(ws_url)
.await
.context("connecting to Chrome CDP")?;
spawn_handler(handler);
Ok(browser)
}
pub async fn configure_page(page: &Page) -> Result<()> {
use chromiumoxide::cdp::browser_protocol::browser::{
SetDownloadBehaviorBehavior, SetDownloadBehaviorParams,
};
let params = SetDownloadBehaviorParams::new(SetDownloadBehaviorBehavior::Deny);
page.execute(params)
.await
.context("setting download behavior to deny")?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chrome_binary_candidates_non_empty() {
let candidates = chrome_binary_candidates();
assert!(!candidates.is_empty());
}
#[test]
fn find_chrome_returns_result() {
let _ = find_chrome();
}
}