cdp_html_shot/browser/
browser_config.rs

1use std::net;
2use which::which;
3use std::path::{Path, PathBuf};
4use rand::prelude::SliceRandom;
5use anyhow::{anyhow, Context, Result};
6
7#[cfg(windows)]
8use winreg::{RegKey, enums::HKEY_LOCAL_MACHINE};
9
10use crate::browser::temp_dir::CustomTempDir;
11
12static DEFAULT_ARGS: [&str; 37] = [
13    // System Settings
14    "--no-sandbox",
15    "--no-first-run",
16    "--no-default-browser-check",
17    "--no-experiments",
18    "--no-pings",
19
20    // Memory Optimization
21    "--js-flags=--max-old-space-size=8192",  // Set JS heap to 8GB
22    "--disk-cache-size=67108864",            // 64MB cache
23    "--memory-pressure-off",
24    "--aggressive-cache-discard",
25    "--disable-dev-shm-usage",
26
27    // Process Management
28    "--process-per-site",
29    "--disable-hang-monitor",
30    "--disable-renderer-backgrounding",
31    "--disable-background-timer-throttling",
32    "--disable-backgrounding-occluded-windows",
33
34    // Disable Optional Features
35    "--disable-sync",
36    "--disable-breakpad",
37    "--disable-infobars",
38    "--disable-extensions",
39    "--disable-default-apps",
40    "--disable-notifications",
41    "--disable-popup-blocking",
42    "--disable-prompt-on-repost",
43    "--disable-client-side-phishing-detection",
44
45    // Network Settings
46    "--enable-async-dns",
47    "--enable-parallel-downloading",
48    "--ignore-certificate-errors",
49    "--disable-http-cache",
50
51    // Graphics Settings
52    "--disable-gpu",
53    "--use-gl=swiftshader",            // Use software rendering
54    "--disable-gpu-compositing",
55    "--force-color-profile=srgb",
56    "--disable-software-rasterizer",
57
58    // Feature Flags
59    "--disable-features=TranslateUI,BlinkGenPropertyTrees,AudioServiceOutOfProcess",
60    "--enable-features=NetworkService,NetworkServiceInProcess,CalculateNativeWinOcclusion",
61
62    // Performance
63    "--disable-ipc-flooding-protection",
64    "--no-zygote",
65
66    // Logging
67    // "--enable-logging=stderr"
68];
69
70pub(crate) struct BrowserConfig {
71    debug_port: u16,
72    pub(crate) headless: bool,
73    pub(crate) temp_dir: CustomTempDir,
74    pub(crate) executable_path: PathBuf,
75}
76
77impl BrowserConfig {
78    pub(crate) fn new() -> Result<Self> {
79        let temp_dir = std::env::current_dir()?.join("temp");
80
81        Ok(Self {
82            headless: true,
83            executable_path: default_executable()?,
84            debug_port: get_available_port().context("Failed to get available port")?,
85            temp_dir: CustomTempDir::new(temp_dir, "cdp-html-shot")
86                .context("Failed to create custom temporary directory")?,
87        })
88    }
89
90    pub(crate) fn get_browser_args(&self) -> Vec<String> {
91        let mut args = vec![
92            format!("--remote-debugging-port={}", self.debug_port),
93            format!("--user-data-dir={}", self.temp_dir.path().display()),
94        ];
95
96        args.extend(DEFAULT_ARGS.iter().map(|s| s.to_string()));
97        if self.headless {
98            args.push("--headless".to_string());
99        }
100
101        args
102    }
103}
104
105fn default_executable() -> Result<PathBuf> {
106    if let Ok(path) = std::env::var("CHROME") {
107        if Path::new(&path).exists() {
108            return Ok(path.into());
109        }
110    }
111
112    let apps = [
113        "google-chrome-stable",
114        "google-chrome-beta",
115        "google-chrome-dev",
116        "google-chrome-unstable",
117        "chromium",
118        "chromium-browser",
119        "microsoft-edge-stable",
120        "microsoft-edge-beta",
121        "microsoft-edge-dev",
122        "chrome",
123        "chrome-browser",
124        "msedge",
125        "microsoft-edge",
126    ];
127    for app in apps {
128        if let Ok(path) = which(app) {
129            return Ok(path);
130        }
131    }
132
133    #[cfg(target_os = "macos")]
134    {
135        let macos_apps = [
136            "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
137            "/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
138            "/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev",
139            "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
140            "/Applications/Chromium.app/Contents/MacOS/Chromium",
141            "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
142            "/Applications/Microsoft Edge Beta.app/Contents/MacOS/Microsoft Edge Beta",
143            "/Applications/Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Dev",
144            "/Applications/Microsoft Edge Canary.app/Contents/MacOS/Microsoft Edge Canary",
145        ];
146        for path in macos_apps.iter() {
147            let path = Path::new(path);
148            if path.exists() {
149                return Ok(path.into());
150            }
151        }
152    }
153
154    #[cfg(windows)]
155    {
156        if let Some(path) = get_chrome_path_from_registry().filter(|p| p.exists()) {
157            return Ok(path);
158        }
159
160        let windows_apps = [
161            r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
162        ];
163        for path in windows_apps.iter() {
164            let path = Path::new(path);
165            if path.exists() {
166                return Ok(path.into());
167            }
168        }
169    }
170
171    Err(anyhow!("Could not auto detect a chrome executable"))
172}
173
174#[cfg(windows)]
175fn get_chrome_path_from_registry() -> Option<PathBuf> {
176    RegKey::predef(HKEY_LOCAL_MACHINE)
177        .open_subkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe")
178        .and_then(|key| key.get_value::<String, _>(""))
179        .map(PathBuf::from)
180        .ok()
181}
182
183fn get_available_port() -> Option<u16> {
184    let mut ports: Vec<u16> = (8000..9000).collect();
185    ports.shuffle(&mut rand::thread_rng());
186    ports.iter().find(|port| port_is_available(**port)).copied()
187}
188
189fn port_is_available(port: u16) -> bool {
190    net::TcpListener::bind(("127.0.0.1", port)).is_ok()
191}