1use std::time::Duration;
2use std::{
3 collections::HashMap,
4 io,
5 path::{Path, PathBuf},
6};
7
8use super::argument::{Arg, ArgConst, ArgsBuilder};
9use crate::async_process::{self, Child, Stdio};
10use crate::detection::{self, DetectionOptions};
11use crate::handler::REQUEST_TIMEOUT;
12use crate::handler::viewport::Viewport;
13
14pub const LAUNCH_TIMEOUT: u64 = 20_000;
16
17#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
18pub enum HeadlessMode {
19 False,
21 #[default]
23 True,
24 New,
26}
27
28#[derive(Debug, Clone)]
29pub struct BrowserConfig {
30 pub(crate) headless: HeadlessMode,
33
34 pub(crate) sandbox: bool,
36
37 pub(crate) window_size: Option<(u32, u32)>,
39
40 pub(crate) port: u16,
42
43 pub(crate) executable: std::path::PathBuf,
48
49 pub(crate) extensions: Vec<String>,
57
58 pub process_envs: Option<HashMap<String, String>>,
61
62 pub user_data_dir: Option<PathBuf>,
64
65 pub(crate) incognito: bool,
67
68 pub(crate) launch_timeout: Duration,
70
71 pub(crate) ignore_https_errors: bool,
73
74 pub(crate) ignore_invalid_messages: bool,
76
77 pub(crate) disable_https_first: bool,
79
80 pub(crate) viewport: Option<Viewport>,
82
83 pub(crate) request_timeout: Duration,
85
86 pub(crate) args: Vec<Arg>,
88
89 pub(crate) disable_default_args: bool,
91
92 pub request_intercept: bool,
94
95 pub cache_enabled: bool,
97
98 pub(crate) hidden: bool,
100}
101
102#[derive(Debug, Clone)]
103pub struct BrowserConfigBuilder {
104 headless: HeadlessMode,
105 sandbox: bool,
106 window_size: Option<(u32, u32)>,
107 port: u16,
108 executable: Option<PathBuf>,
109 executation_detection: DetectionOptions,
110 extensions: Vec<String>,
111 process_envs: Option<HashMap<String, String>>,
112 user_data_dir: Option<PathBuf>,
113 incognito: bool,
114 launch_timeout: Duration,
115 ignore_https_errors: bool,
116 ignore_invalid_events: bool,
117 disable_https_first: bool,
118 viewport: Option<Viewport>,
119 request_timeout: Duration,
120 args: Vec<Arg>,
121 disable_default_args: bool,
122 request_intercept: bool,
123 cache_enabled: bool,
124 hidden: bool,
125}
126
127impl BrowserConfig {
128 pub fn builder() -> BrowserConfigBuilder {
129 BrowserConfigBuilder::default()
130 }
131
132 pub fn with_executable(path: impl AsRef<Path>) -> Self {
133 Self::builder().chrome_executable(path).build().unwrap()
134 }
135}
136
137impl Default for BrowserConfigBuilder {
138 fn default() -> Self {
139 Self {
140 headless: HeadlessMode::False,
141 sandbox: true,
142 window_size: None,
143 port: 0,
144 executable: None,
145 executation_detection: DetectionOptions::default(),
146 extensions: Vec::new(),
147 process_envs: None,
148 user_data_dir: None,
149 incognito: false,
150 launch_timeout: Duration::from_millis(LAUNCH_TIMEOUT),
151 ignore_https_errors: true,
152 ignore_invalid_events: true,
153 disable_https_first: false,
154 viewport: Some(Default::default()),
155 request_timeout: Duration::from_millis(REQUEST_TIMEOUT),
156 args: Vec::new(),
157 disable_default_args: false,
158 request_intercept: false,
159 cache_enabled: true,
160 hidden: true,
161 }
162 }
163}
164
165impl BrowserConfigBuilder {
166 pub fn window_size(mut self, width: u32, height: u32) -> Self {
167 self.window_size = Some((width, height));
168 self
169 }
170
171 pub fn no_sandbox(mut self) -> Self {
172 self.sandbox = false;
173 self
174 }
175
176 pub fn with_head(mut self) -> Self {
177 self.headless = HeadlessMode::False;
178 self
179 }
180
181 pub fn new_headless_mode(mut self) -> Self {
182 self.headless = HeadlessMode::New;
183 self
184 }
185
186 pub fn headless_mode(mut self, mode: HeadlessMode) -> Self {
187 self.headless = mode;
188 self
189 }
190
191 pub fn incognito(mut self) -> Self {
192 self.incognito = true;
193 self
194 }
195
196 pub fn respect_https_errors(mut self) -> Self {
197 self.ignore_https_errors = false;
198 self
199 }
200
201 pub fn surface_invalid_messages(mut self) -> Self {
204 self.ignore_invalid_events = false;
205 self
206 }
207
208 pub fn port(mut self, port: u16) -> Self {
209 self.port = port;
210 self
211 }
212
213 pub fn launch_timeout(mut self, timeout: Duration) -> Self {
214 self.launch_timeout = timeout;
215 self
216 }
217
218 pub fn request_timeout(mut self, timeout: Duration) -> Self {
219 self.request_timeout = timeout;
220 self
221 }
222
223 pub fn viewport(mut self, viewport: impl Into<Option<Viewport>>) -> Self {
229 self.viewport = viewport.into();
230 self
231 }
232
233 pub fn user_data_dir(mut self, data_dir: impl AsRef<Path>) -> Self {
234 self.user_data_dir = Some(data_dir.as_ref().to_path_buf());
235 self
236 }
237
238 pub fn chrome_executable(mut self, path: impl AsRef<Path>) -> Self {
239 self.executable = Some(path.as_ref().to_path_buf());
240 self
241 }
242
243 pub fn chrome_detection(mut self, options: DetectionOptions) -> Self {
244 self.executation_detection = options;
245 self
246 }
247
248 pub fn extension(mut self, extension: impl Into<String>) -> Self {
249 self.extensions.push(extension.into());
250 self
251 }
252
253 pub fn extensions<I, S>(mut self, extensions: I) -> Self
254 where
255 I: IntoIterator<Item = S>,
256 S: Into<String>,
257 {
258 for ext in extensions {
259 self.extensions.push(ext.into());
260 }
261 self
262 }
263
264 pub fn env(mut self, key: impl Into<String>, val: impl Into<String>) -> Self {
265 self.process_envs
266 .get_or_insert(HashMap::new())
267 .insert(key.into(), val.into());
268 self
269 }
270
271 pub fn envs<I, K, V>(mut self, envs: I) -> Self
272 where
273 I: IntoIterator<Item = (K, V)>,
274 K: Into<String>,
275 V: Into<String>,
276 {
277 self.process_envs
278 .get_or_insert(HashMap::new())
279 .extend(envs.into_iter().map(|(k, v)| (k.into(), v.into())));
280 self
281 }
282
283 pub fn arg(mut self, arg: impl Into<Arg>) -> Self {
284 self.args.push(arg.into());
285 self
286 }
287
288 pub fn args<I, S>(mut self, args: I) -> Self
289 where
290 I: IntoIterator<Item = S>,
291 S: Into<Arg>,
292 {
293 for arg in args {
294 self.args.push(arg.into());
295 }
296 self
297 }
298
299 pub fn disable_default_args(mut self) -> Self {
300 self.disable_default_args = true;
301 self
302 }
303
304 pub fn disable_https_first(mut self) -> Self {
305 self.disable_https_first = true;
306 self
307 }
308
309 pub fn enable_request_intercept(mut self) -> Self {
310 self.request_intercept = true;
311 self
312 }
313
314 pub fn disable_request_intercept(mut self) -> Self {
315 self.request_intercept = false;
316 self
317 }
318
319 pub fn enable_cache(mut self) -> Self {
320 self.cache_enabled = true;
321 self
322 }
323
324 pub fn disable_cache(mut self) -> Self {
325 self.cache_enabled = false;
326 self
327 }
328
329 pub fn hide(mut self) -> Self {
330 self.hidden = true;
331 if self.hidden && self.viewport == Some(Viewport::default()) {
332 self.viewport = None;
333 }
334 self
335 }
336
337 pub fn build(self) -> std::result::Result<BrowserConfig, String> {
338 let executable = if let Some(e) = self.executable {
339 e
340 } else {
341 detection::default_executable(self.executation_detection)?
342 };
343
344 Ok(BrowserConfig {
345 headless: self.headless,
346 sandbox: self.sandbox,
347 window_size: self.window_size,
348 port: self.port,
349 executable,
350 extensions: self.extensions,
351 process_envs: self.process_envs,
352 user_data_dir: self.user_data_dir,
353 incognito: self.incognito,
354 launch_timeout: self.launch_timeout,
355 ignore_https_errors: self.ignore_https_errors,
356 ignore_invalid_messages: self.ignore_invalid_events,
357 disable_https_first: self.disable_https_first,
358 viewport: self.viewport,
359 request_timeout: self.request_timeout,
360 args: self.args,
361 disable_default_args: self.disable_default_args,
362 request_intercept: self.request_intercept,
363 cache_enabled: self.cache_enabled,
364 hidden: self.hidden,
365 })
366 }
367}
368
369impl BrowserConfig {
370 pub fn launch(&self) -> io::Result<Child> {
371 let mut builder = ArgsBuilder::new();
372
373 if self.disable_default_args {
374 builder.args(self.args.clone());
375 } else {
376 builder.args(DEFAULT_ARGS.clone()).args(self.args.clone());
377 }
378
379 if !builder.has("remote-debugging-port") {
380 builder.arg(Arg::value("remote-debugging-port", self.port));
381 }
382
383 if self.extensions.is_empty() {
384 builder.arg(Arg::key("disable-extensions"));
385 } else {
386 builder.args(
387 self.extensions
388 .iter()
389 .map(|e| Arg::value("load-extension", e)),
390 );
391 }
392
393 if let Some(ref user_data) = self.user_data_dir {
394 builder.arg(Arg::value("user-data-dir", user_data.display()));
395 } else {
396 builder.arg(Arg::value(
400 "user-data-dir",
401 std::env::temp_dir().join("chromiumoxide-runner").display(),
402 ));
403 }
404
405 if let Some((width, height)) = self.window_size {
406 builder.arg(Arg::values("window-size", [width, height]));
407 }
408
409 if !self.sandbox {
410 builder.args([Arg::key("no-sandbox"), Arg::key("disable-setuid-sandbox")]);
411 }
412
413 match self.headless {
414 HeadlessMode::False => (),
415 HeadlessMode::True => {
416 builder.args([
417 Arg::key("headless"),
418 Arg::key("hide-scrollbars"),
419 Arg::key("mute-audio"),
420 ]);
421 }
422 HeadlessMode::New => {
423 builder.args([
424 Arg::value("headless", "new"),
425 Arg::key("hide-scrollbars"),
426 Arg::key("mute-audio"),
427 ]);
428 }
429 }
430
431 if self.incognito {
432 builder.arg(Arg::key("incognito"));
433 }
434
435 if self.hidden {
436 builder.arg(Arg::value("disable-blink-features", "AutomationControlled"));
437 }
438
439 if self.disable_https_first {
440 builder.arg(Arg::values(
441 "disable-features",
442 ["HttpsUpgrades", "HttpsFirstBalancedModeAutoEnable"],
443 ));
444 }
445
446 let mut cmd = async_process::Command::new(&self.executable);
447
448 let args = builder.into_iter().collect::<Vec<String>>();
449 cmd.args(args);
450
451 if let Some(ref envs) = self.process_envs {
452 cmd.envs(envs);
453 }
454 cmd.stdout(Stdio::null()).stderr(Stdio::piped()).spawn()
455 }
456}
457
458static DEFAULT_ARGS: [ArgConst; 23] = [
461 ArgConst::key("disable-background-networking"),
462 ArgConst::values(
463 "enable-features",
464 &["NetworkService", "NetworkServiceInProcess"],
465 ),
466 ArgConst::key("disable-background-timer-throttling"),
467 ArgConst::key("disable-backgrounding-occluded-windows"),
468 ArgConst::key("disable-breakpad"),
469 ArgConst::key("disable-client-side-phishing-detection"),
470 ArgConst::key("disable-component-extensions-with-background-pages"),
471 ArgConst::key("disable-default-apps"),
472 ArgConst::key("disable-dev-shm-usage"),
473 ArgConst::values("disable-features", &["TranslateUI"]),
474 ArgConst::key("disable-hang-monitor"),
475 ArgConst::key("disable-ipc-flooding-protection"),
476 ArgConst::key("disable-popup-blocking"),
477 ArgConst::key("disable-prompt-on-repost"),
478 ArgConst::key("disable-renderer-backgrounding"),
479 ArgConst::key("disable-sync"),
480 ArgConst::values("force-color-profile", &["srgb"]),
481 ArgConst::key("metrics-recording-only"),
482 ArgConst::key("no-first-run"),
483 ArgConst::values("password-store", &["basic"]),
484 ArgConst::key("use-mock-keychain"),
485 ArgConst::values("enable-blink-features", &["IdleDetection"]),
486 ArgConst::values("lang", &["en_US"]),
487];