cdp_html_shot/browser/
browser_config.rs1use 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 "--no-sandbox",
15 "--no-first-run",
16 "--no-default-browser-check",
17 "--no-experiments",
18 "--no-pings",
19
20 "--js-flags=--max-old-space-size=8192", "--disk-cache-size=67108864", "--memory-pressure-off",
24 "--aggressive-cache-discard",
25 "--disable-dev-shm-usage",
26
27 "--process-per-site",
29 "--disable-hang-monitor",
30 "--disable-renderer-backgrounding",
31 "--disable-background-timer-throttling",
32 "--disable-backgrounding-occluded-windows",
33
34 "--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 "--enable-async-dns",
47 "--enable-parallel-downloading",
48 "--ignore-certificate-errors",
49 "--disable-http-cache",
50
51 "--disable-gpu",
53 "--use-gl=swiftshader", "--disable-gpu-compositing",
55 "--force-color-profile=srgb",
56 "--disable-software-rasterizer",
57
58 "--disable-features=TranslateUI,BlinkGenPropertyTrees,AudioServiceOutOfProcess",
60 "--enable-features=NetworkService,NetworkServiceInProcess,CalculateNativeWinOcclusion",
61
62 "--disable-ipc-flooding-protection",
64 "--no-zygote",
65
66 ];
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}