1use hashbrown::HashMap;
2use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
3use std::future::Future;
4use std::time::Duration;
5use std::{
6 io,
7 path::{Path, PathBuf},
8};
9
10use futures::channel::mpsc::{channel, unbounded, Sender};
11use futures::channel::oneshot::channel as oneshot_channel;
12use futures::select;
13use futures::SinkExt;
14
15use crate::async_process::{self, Child, ExitStatus, Stdio};
16use crate::cmd::{to_command_response, CommandMessage};
17use crate::conn::Connection;
18use crate::detection::{self, DetectionOptions};
19use crate::error::{BrowserStderr, CdpError, Result};
20use crate::handler::blockers::intercept_manager::NetworkInterceptManager;
21use crate::handler::browser::BrowserContext;
22use crate::handler::viewport::Viewport;
23use crate::handler::{Handler, HandlerConfig, HandlerMessage, REQUEST_TIMEOUT};
24use crate::listeners::{EventListenerRequest, EventStream};
25use crate::page::Page;
26use crate::utils;
27use chromiumoxide_cdp::cdp::browser_protocol::browser::{
28 BrowserContextId, CloseReturns, GetVersionParams, GetVersionReturns,
29};
30use chromiumoxide_cdp::cdp::browser_protocol::network::{Cookie, CookieParam};
31use chromiumoxide_cdp::cdp::browser_protocol::storage::{
32 ClearCookiesParams, GetCookiesParams, SetCookiesParams,
33};
34use chromiumoxide_cdp::cdp::browser_protocol::target::{
35 CreateBrowserContextParams, CreateTargetParams, DisposeBrowserContextParams, TargetId,
36 TargetInfo,
37};
38use chromiumoxide_cdp::cdp::{CdpEventMessage, IntoEventKind};
39use chromiumoxide_types::*;
40
41pub const LAUNCH_TIMEOUT: u64 = 20_000;
43
44lazy_static::lazy_static! {
45 static ref REQUEST_CLIENT: reqwest::Client = reqwest::Client::builder()
47 .tcp_keepalive(Duration::from_secs(30))
48 .http2_keep_alive_while_idle(true)
49 .timeout(Duration::from_secs(120))
50 .default_headers({
51 let mut m = HeaderMap::new();
52
53 m.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
54
55 m
56 })
57 .build()
58 .unwrap();
59}
60
61#[derive(Debug)]
63pub struct Browser {
64 sender: Sender<HandlerMessage>,
67 config: Option<BrowserConfig>,
69 child: Option<Child>,
71 debug_ws_url: String,
73 pub browser_context: BrowserContext,
75}
76
77#[derive(serde::Deserialize, Debug, Default)]
79pub struct BrowserConnection {
80 #[serde(rename = "Browser")]
81 pub browser: String,
83 #[serde(rename = "Protocol-Version")]
84 pub protocol_version: String,
86 #[serde(rename = "User-Agent")]
87 pub user_agent: String,
89 #[serde(rename = "V8-Version")]
90 pub v8_version: String,
92 #[serde(rename = "WebKit-Version")]
93 pub webkit_version: String,
95 #[serde(rename = "webSocketDebuggerUrl")]
96 pub web_socket_debugger_url: String,
98}
99
100impl Browser {
101 pub async fn connect(url: impl Into<String>) -> Result<(Self, Handler)> {
105 Self::connect_with_config(url, HandlerConfig::default()).await
106 }
107
108 pub async fn connect_with_config(
112 url: impl Into<String>,
113 config: HandlerConfig,
114 ) -> Result<(Self, Handler)> {
115 let mut debug_ws_url = url.into();
116
117 if debug_ws_url.starts_with("http") {
118 match REQUEST_CLIENT
119 .get(
120 if debug_ws_url.ends_with("/json/version")
121 || debug_ws_url.ends_with("/json/version/")
122 {
123 debug_ws_url.to_owned()
124 } else {
125 format!(
126 "{}{}json/version",
127 &debug_ws_url,
128 if debug_ws_url.ends_with('/') { "" } else { "/" }
129 )
130 },
131 )
132 .send()
133 .await
134 {
135 Ok(req) => {
136 let connection: BrowserConnection =
137 crate::serde_json::from_slice(&req.bytes().await.unwrap_or_default())
138 .unwrap_or_default();
139 if !connection.web_socket_debugger_url.is_empty() {
140 debug_ws_url = connection.web_socket_debugger_url;
141 }
142 }
143 Err(_) => return Err(CdpError::NoResponse),
144 }
145 }
146
147 let conn = Connection::<CdpEventMessage>::connect(&debug_ws_url).await?;
148
149 let (tx, rx) = channel(1000);
150
151 let fut = Handler::new(conn, rx, config);
152 let browser_context = fut.default_browser_context().clone();
153
154 let browser = Self {
155 sender: tx,
156 config: None,
157 child: None,
158 debug_ws_url,
159 browser_context,
160 };
161
162 Ok((browser, fut))
163 }
164
165 pub async fn launch(mut config: BrowserConfig) -> Result<(Self, Handler)> {
174 config.executable = utils::canonicalize_except_snap(config.executable).await?;
176
177 let mut child = config.launch()?;
179
180 async fn with_child(
185 config: &BrowserConfig,
186 child: &mut Child,
187 ) -> Result<(String, Connection<CdpEventMessage>)> {
188 let dur = config.launch_timeout;
189 let timeout_fut = Box::pin(tokio::time::sleep(dur));
190
191 let debug_ws_url = ws_url_from_output(child, timeout_fut).await?;
193 let conn = Connection::<CdpEventMessage>::connect(&debug_ws_url).await?;
194 Ok((debug_ws_url, conn))
195 }
196
197 let (debug_ws_url, conn) = match with_child(&config, &mut child).await {
198 Ok(conn) => conn,
199 Err(e) => {
200 if let Ok(Some(_)) = child.try_wait() {
202 } else {
204 child.kill().await.expect("`Browser::launch` failed but could not clean-up the child process (`kill`)");
206 child.wait().await.expect("`Browser::launch` failed but could not clean-up the child process (`wait`)");
207 }
208 return Err(e);
209 }
210 };
211
212 let (tx, rx) = channel(1000);
216
217 let handler_config = HandlerConfig {
218 ignore_https_errors: config.ignore_https_errors,
219 viewport: config.viewport.clone(),
220 context_ids: Vec::new(),
221 request_timeout: config.request_timeout,
222 request_intercept: config.request_intercept,
223 cache_enabled: config.cache_enabled,
224 ignore_visuals: config.ignore_visuals,
225 ignore_stylesheets: config.ignore_stylesheets,
226 ignore_javascript: config.ignore_javascript,
227 ignore_analytics: config.ignore_analytics,
228 ignore_ads: config.ignore_ads,
229 extra_headers: config.extra_headers.clone(),
230 only_html: config.only_html,
231 created_first_target: false,
232 intercept_manager: config.intercept_manager,
233 };
234
235 let fut = Handler::new(conn, rx, handler_config);
236 let browser_context = fut.default_browser_context().clone();
237
238 let browser = Self {
239 sender: tx,
240 config: Some(config),
241 child: Some(child),
242 debug_ws_url,
243 browser_context,
244 };
245
246 Ok((browser, fut))
247 }
248
249 pub async fn fetch_targets(&mut self) -> Result<Vec<TargetInfo>> {
259 let (tx, rx) = oneshot_channel();
260
261 self.sender
262 .clone()
263 .send(HandlerMessage::FetchTargets(tx))
264 .await?;
265
266 rx.await?
267 }
268
269 pub async fn close(&mut self) -> Result<CloseReturns> {
276 let (tx, rx) = oneshot_channel();
277
278 self.sender
279 .clone()
280 .send(HandlerMessage::CloseBrowser(tx))
281 .await?;
282
283 rx.await?
284 }
285
286 pub async fn wait(&mut self) -> io::Result<Option<ExitStatus>> {
295 if let Some(child) = self.child.as_mut() {
296 Ok(Some(child.wait().await?))
297 } else {
298 Ok(None)
299 }
300 }
301
302 pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
311 if let Some(child) = self.child.as_mut() {
312 child.try_wait()
313 } else {
314 Ok(None)
315 }
316 }
317
318 pub fn get_mut_child(&mut self) -> Option<&mut Child> {
329 self.child.as_mut()
330 }
331
332 pub fn has_child(&self) -> bool {
334 self.child.is_some()
335 }
336
337 pub async fn kill(&mut self) -> Option<io::Result<()>> {
348 match self.child.as_mut() {
349 Some(child) => Some(child.kill().await),
350 None => None,
351 }
352 }
353
354 pub async fn start_incognito_context(&mut self) -> Result<&mut Self> {
360 if !self.is_incognito_configured() {
361 let browser_context_id = self
362 .create_browser_context(CreateBrowserContextParams::default())
363 .await?;
364 self.browser_context = BrowserContext::from(browser_context_id);
365 self.sender
366 .clone()
367 .send(HandlerMessage::InsertContext(self.browser_context.clone()))
368 .await?;
369 }
370
371 Ok(self)
372 }
373
374 pub async fn quit_incognito_context(&mut self) -> Result<&mut Self> {
380 if let Some(id) = self.browser_context.take() {
381 self.dispose_browser_context(id.clone()).await?;
382 self.sender
383 .clone()
384 .send(HandlerMessage::DisposeContext(BrowserContext::from(id)))
385 .await?;
386 }
387 Ok(self)
388 }
389
390 fn is_incognito_configured(&self) -> bool {
392 self.config
393 .as_ref()
394 .map(|c| c.incognito)
395 .unwrap_or_default()
396 }
397
398 pub fn websocket_address(&self) -> &String {
400 &self.debug_ws_url
401 }
402
403 pub fn is_incognito(&self) -> bool {
405 self.is_incognito_configured() || self.browser_context.is_incognito()
406 }
407
408 pub fn config(&self) -> Option<&BrowserConfig> {
410 self.config.as_ref()
411 }
412
413 pub async fn new_page(&self, params: impl Into<CreateTargetParams>) -> Result<Page> {
415 let (tx, rx) = oneshot_channel();
416 let mut params = params.into();
417
418 if let Some(id) = self.browser_context.id() {
419 if params.browser_context_id.is_none() {
420 params.browser_context_id = Some(id.clone());
421 }
422 }
423
424 let _ = self
425 .sender
426 .clone()
427 .send(HandlerMessage::CreatePage(params, tx))
428 .await;
429
430 rx.await?
431 }
432
433 pub async fn version(&self) -> Result<GetVersionReturns> {
435 Ok(self.execute(GetVersionParams::default()).await?.result)
436 }
437
438 pub async fn user_agent(&self) -> Result<String> {
440 Ok(self.version().await?.user_agent)
441 }
442
443 pub async fn execute<T: Command>(&self, cmd: T) -> Result<CommandResponse<T::Response>> {
445 let (tx, rx) = oneshot_channel();
446 let method = cmd.identifier();
447 let msg = CommandMessage::new(cmd, tx)?;
448
449 self.sender
450 .clone()
451 .send(HandlerMessage::Command(msg))
452 .await?;
453 let resp = rx.await??;
454 to_command_response::<T>(resp, method)
455 }
456
457 pub async fn pages(&self) -> Result<Vec<Page>> {
459 let (tx, rx) = oneshot_channel();
460 self.sender
461 .clone()
462 .send(HandlerMessage::GetPages(tx))
463 .await?;
464 Ok(rx.await?)
465 }
466
467 pub async fn get_page(&self, target_id: TargetId) -> Result<Page> {
469 let (tx, rx) = oneshot_channel();
470 self.sender
471 .clone()
472 .send(HandlerMessage::GetPage(target_id, tx))
473 .await?;
474 rx.await?.ok_or(CdpError::NotFound)
475 }
476
477 pub async fn event_listener<T: IntoEventKind>(&self) -> Result<EventStream<T>> {
479 let (tx, rx) = unbounded();
480 self.sender
481 .clone()
482 .send(HandlerMessage::AddEventListener(
483 EventListenerRequest::new::<T>(tx),
484 ))
485 .await?;
486
487 Ok(EventStream::new(rx))
488 }
489
490 pub async fn create_browser_context(
492 &mut self,
493 params: CreateBrowserContextParams,
494 ) -> Result<BrowserContextId> {
495 let response = self.execute(params).await?;
496 Ok(response.result.browser_context_id)
497 }
498
499 pub async fn send_new_context(&mut self, browser_context_id: BrowserContextId) -> Result<()> {
501 self.browser_context = BrowserContext::from(browser_context_id);
502 self.sender
503 .clone()
504 .send(HandlerMessage::InsertContext(self.browser_context.clone()))
505 .await?;
506 Ok(())
507 }
508
509 pub async fn dispose_browser_context(
511 &self,
512 browser_context_id: impl Into<BrowserContextId>,
513 ) -> Result<()> {
514 self.execute(DisposeBrowserContextParams::new(browser_context_id))
515 .await?;
516
517 Ok(())
518 }
519
520 pub async fn clear_cookies(&self) -> Result<()> {
522 self.execute(ClearCookiesParams::default()).await?;
523 Ok(())
524 }
525
526 pub async fn get_cookies(&self) -> Result<Vec<Cookie>> {
528 let mut cmd = GetCookiesParams::default();
529
530 cmd.browser_context_id = self.browser_context.id.clone();
531
532 Ok(self.execute(cmd).await?.result.cookies)
533 }
534
535 pub async fn set_cookies(&self, mut cookies: Vec<CookieParam>) -> Result<&Self> {
537 for cookie in &mut cookies {
538 if let Some(url) = cookie.url.as_ref() {
539 crate::page::validate_cookie_url(url)?;
540 }
541 }
542
543 let mut cookies_param = SetCookiesParams::new(cookies);
544
545 cookies_param.browser_context_id = self.browser_context.id.clone();
546
547 self.execute(cookies_param).await?;
548 Ok(self)
549 }
550}
551
552impl Drop for Browser {
553 fn drop(&mut self) {
554 if let Some(child) = self.child.as_mut() {
555 if let Ok(Some(_)) = child.try_wait() {
556 } else {
558 tracing::warn!("Browser was not closed manually, it will be killed automatically in the background");
566 }
567 }
568 }
569}
570
571async fn ws_url_from_output(
581 child_process: &mut Child,
582 timeout_fut: impl Future<Output = ()> + Unpin,
583) -> Result<String> {
584 use futures::{AsyncBufReadExt, FutureExt};
585 let mut timeout_fut = timeout_fut.fuse();
586 let stderr = child_process.stderr.take().expect("no stderror");
587 let mut stderr_bytes = Vec::<u8>::new();
588 let mut exit_status_fut = Box::pin(child_process.wait()).fuse();
589 let mut buf = futures::io::BufReader::new(stderr);
590 loop {
591 select! {
592 _ = timeout_fut => return Err(CdpError::LaunchTimeout(BrowserStderr::new(stderr_bytes))),
593 exit_status = exit_status_fut => {
594 return Err(match exit_status {
595 Err(e) => CdpError::LaunchIo(e, BrowserStderr::new(stderr_bytes)),
596 Ok(exit_status) => CdpError::LaunchExit(exit_status, BrowserStderr::new(stderr_bytes)),
597 })
598 },
599 read_res = buf.read_until(b'\n', &mut stderr_bytes).fuse() => {
600 match read_res {
601 Err(e) => return Err(CdpError::LaunchIo(e, BrowserStderr::new(stderr_bytes))),
602 Ok(byte_count) => {
603 if byte_count == 0 {
604 let e = io::Error::new(io::ErrorKind::UnexpectedEof, "unexpected end of stream");
605 return Err(CdpError::LaunchIo(e, BrowserStderr::new(stderr_bytes)));
606 }
607 let start_offset = stderr_bytes.len() - byte_count;
608 let new_bytes = &stderr_bytes[start_offset..];
609 match std::str::from_utf8(new_bytes) {
610 Err(_) => {
611 let e = io::Error::new(io::ErrorKind::InvalidData, "stream did not contain valid UTF-8");
612 return Err(CdpError::LaunchIo(e, BrowserStderr::new(stderr_bytes)));
613 }
614 Ok(line) => {
615 if let Some((_, ws)) = line.rsplit_once("listening on ") {
616 if ws.starts_with("ws") && ws.contains("devtools/browser") {
617 return Ok(ws.trim().to_string());
618 }
619 }
620 }
621 }
622 }
623 }
624 }
625 }
626 }
627}
628
629#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
630pub enum HeadlessMode {
631 False,
633 #[default]
635 True,
636 New,
638}
639
640#[derive(Debug, Clone)]
641pub struct BrowserConfig {
642 headless: HeadlessMode,
645 sandbox: bool,
647 window_size: Option<(u32, u32)>,
649 port: u16,
651 executable: std::path::PathBuf,
656
657 extensions: Vec<String>,
665
666 pub process_envs: Option<HashMap<String, String>>,
669
670 pub user_data_dir: Option<PathBuf>,
672
673 incognito: bool,
675
676 launch_timeout: Duration,
678
679 ignore_https_errors: bool,
681 viewport: Option<Viewport>,
682 request_timeout: Duration,
684
685 args: Vec<String>,
687
688 disable_default_args: bool,
690
691 pub request_intercept: bool,
693
694 pub cache_enabled: bool,
696
697 pub ignore_visuals: bool,
699 pub ignore_stylesheets: bool,
701 pub ignore_javascript: bool,
703 pub ignore_analytics: bool,
705 pub ignore_ads: bool,
707 pub extra_headers: Option<std::collections::HashMap<String, String>>,
709 pub only_html: bool,
711 pub intercept_manager: NetworkInterceptManager,
713}
714
715#[derive(Debug, Clone)]
716pub struct BrowserConfigBuilder {
717 headless: HeadlessMode,
718 sandbox: bool,
719 window_size: Option<(u32, u32)>,
720 port: u16,
721 executable: Option<PathBuf>,
722 executation_detection: DetectionOptions,
723 extensions: Vec<String>,
724 process_envs: Option<HashMap<String, String>>,
725 user_data_dir: Option<PathBuf>,
726 incognito: bool,
727 launch_timeout: Duration,
728 ignore_https_errors: bool,
729 viewport: Option<Viewport>,
730 request_timeout: Duration,
731 args: Vec<String>,
732 disable_default_args: bool,
733 request_intercept: bool,
734 cache_enabled: bool,
735 ignore_visuals: bool,
736 ignore_ads: bool,
737 ignore_javascript: bool,
738 ignore_stylesheets: bool,
739 ignore_analytics: bool,
740 only_html: bool,
741 extra_headers: Option<std::collections::HashMap<String, String>>,
742 intercept_manager: NetworkInterceptManager,
743}
744
745impl BrowserConfig {
746 pub fn builder() -> BrowserConfigBuilder {
747 BrowserConfigBuilder::default()
748 }
749
750 pub fn with_executable(path: impl AsRef<Path>) -> Self {
751 Self::builder().chrome_executable(path).build().unwrap()
752 }
753}
754
755impl Default for BrowserConfigBuilder {
756 fn default() -> Self {
757 Self {
758 headless: HeadlessMode::True,
759 sandbox: true,
760 window_size: None,
761 port: 0,
762 executable: None,
763 executation_detection: DetectionOptions::default(),
764 extensions: Vec::new(),
765 process_envs: None,
766 user_data_dir: None,
767 incognito: false,
768 launch_timeout: Duration::from_millis(LAUNCH_TIMEOUT),
769 ignore_https_errors: true,
770 viewport: Some(Default::default()),
771 request_timeout: Duration::from_millis(REQUEST_TIMEOUT),
772 args: Vec::new(),
773 disable_default_args: false,
774 request_intercept: false,
775 cache_enabled: true,
776 ignore_visuals: false,
777 ignore_ads: false,
778 ignore_javascript: false,
779 ignore_analytics: false,
780 ignore_stylesheets: false,
781 only_html: false,
782 extra_headers: Default::default(),
783 intercept_manager: NetworkInterceptManager::UNKNOWN,
784 }
785 }
786}
787
788impl BrowserConfigBuilder {
789 pub fn window_size(mut self, width: u32, height: u32) -> Self {
790 self.window_size = Some((width, height));
791 self
792 }
793
794 pub fn no_sandbox(mut self) -> Self {
795 self.sandbox = false;
796 self
797 }
798
799 pub fn with_head(mut self) -> Self {
800 self.headless = HeadlessMode::False;
801 self
802 }
803
804 pub fn new_headless_mode(mut self) -> Self {
805 self.headless = HeadlessMode::New;
806 self
807 }
808
809 pub fn headless_mode(mut self, mode: HeadlessMode) -> Self {
810 self.headless = mode;
811 self
812 }
813
814 pub fn incognito(mut self) -> Self {
815 self.incognito = true;
816 self
817 }
818
819 pub fn respect_https_errors(mut self) -> Self {
820 self.ignore_https_errors = false;
821 self
822 }
823
824 pub fn port(mut self, port: u16) -> Self {
825 self.port = port;
826 self
827 }
828
829 pub fn launch_timeout(mut self, timeout: Duration) -> Self {
830 self.launch_timeout = timeout;
831 self
832 }
833
834 pub fn request_timeout(mut self, timeout: Duration) -> Self {
835 self.request_timeout = timeout;
836 self
837 }
838
839 pub fn viewport(mut self, viewport: impl Into<Option<Viewport>>) -> Self {
845 self.viewport = viewport.into();
846 self
847 }
848
849 pub fn user_data_dir(mut self, data_dir: impl AsRef<Path>) -> Self {
850 self.user_data_dir = Some(data_dir.as_ref().to_path_buf());
851 self
852 }
853
854 pub fn chrome_executable(mut self, path: impl AsRef<Path>) -> Self {
855 self.executable = Some(path.as_ref().to_path_buf());
856 self
857 }
858
859 pub fn chrome_detection(mut self, options: DetectionOptions) -> Self {
860 self.executation_detection = options;
861 self
862 }
863
864 pub fn extension(mut self, extension: impl Into<String>) -> Self {
865 self.extensions.push(extension.into());
866 self
867 }
868
869 pub fn extensions<I, S>(mut self, extensions: I) -> Self
870 where
871 I: IntoIterator<Item = S>,
872 S: Into<String>,
873 {
874 for ext in extensions {
875 self.extensions.push(ext.into());
876 }
877 self
878 }
879
880 pub fn env(mut self, key: impl Into<String>, val: impl Into<String>) -> Self {
881 self.process_envs
882 .get_or_insert(HashMap::new())
883 .insert(key.into(), val.into());
884 self
885 }
886
887 pub fn envs<I, K, V>(mut self, envs: I) -> Self
888 where
889 I: IntoIterator<Item = (K, V)>,
890 K: Into<String>,
891 V: Into<String>,
892 {
893 self.process_envs
894 .get_or_insert(HashMap::new())
895 .extend(envs.into_iter().map(|(k, v)| (k.into(), v.into())));
896 self
897 }
898
899 pub fn arg(mut self, arg: impl Into<String>) -> Self {
900 self.args.push(arg.into());
901 self
902 }
903
904 pub fn args<I, S>(mut self, args: I) -> Self
905 where
906 I: IntoIterator<Item = S>,
907 S: Into<String>,
908 {
909 for arg in args {
910 self.args.push(arg.into());
911 }
912 self
913 }
914
915 pub fn disable_default_args(mut self) -> Self {
916 self.disable_default_args = true;
917 self
918 }
919
920 pub fn enable_request_intercept(mut self) -> Self {
921 self.request_intercept = true;
922 self
923 }
924
925 pub fn disable_request_intercept(mut self) -> Self {
926 self.request_intercept = false;
927 self
928 }
929
930 pub fn enable_cache(mut self) -> Self {
931 self.cache_enabled = true;
932 self
933 }
934
935 pub fn disable_cache(mut self) -> Self {
936 self.cache_enabled = false;
937 self
938 }
939 pub fn set_extra_headers(
940 mut self,
941 headers: Option<std::collections::HashMap<String, String>>,
942 ) -> Self {
943 self.extra_headers = headers;
944 self
945 }
946 pub fn build(self) -> std::result::Result<BrowserConfig, String> {
947 let executable = if let Some(e) = self.executable {
948 e
949 } else {
950 detection::default_executable(self.executation_detection)?
951 };
952
953 Ok(BrowserConfig {
954 headless: self.headless,
955 sandbox: self.sandbox,
956 window_size: self.window_size,
957 port: self.port,
958 executable,
959 extensions: self.extensions,
960 process_envs: self.process_envs,
961 user_data_dir: self.user_data_dir,
962 incognito: self.incognito,
963 launch_timeout: self.launch_timeout,
964 ignore_https_errors: self.ignore_https_errors,
965 viewport: self.viewport,
966 request_timeout: self.request_timeout,
967 args: self.args,
968 disable_default_args: self.disable_default_args,
969 request_intercept: self.request_intercept,
970 cache_enabled: self.cache_enabled,
971 ignore_visuals: self.ignore_visuals,
972 ignore_ads: self.ignore_ads,
973 ignore_javascript: self.ignore_javascript,
974 ignore_analytics: self.ignore_analytics,
975 ignore_stylesheets: self.ignore_stylesheets,
976 extra_headers: self.extra_headers,
977 only_html: self.only_html,
978 intercept_manager: self.intercept_manager,
979 })
980 }
981}
982
983impl BrowserConfig {
984 pub fn launch(&self) -> io::Result<Child> {
985 let mut cmd = async_process::Command::new(&self.executable);
986
987 if self.disable_default_args {
988 cmd.args(&self.args);
989 } else {
990 cmd.args(DEFAULT_ARGS).args(&self.args);
991 }
992
993 if !self
994 .args
995 .iter()
996 .any(|arg| arg.contains("--remote-debugging-port="))
997 {
998 cmd.arg(format!("--remote-debugging-port={}", self.port));
999 }
1000
1001 cmd.args(
1002 self.extensions
1003 .iter()
1004 .map(|e| format!("--load-extension={e}")),
1005 );
1006
1007 if let Some(ref user_data) = self.user_data_dir {
1008 cmd.arg(format!("--user-data-dir={}", user_data.display()));
1009 } else {
1010 cmd.arg(format!(
1014 "--user-data-dir={}",
1015 std::env::temp_dir().join("chromiumoxide-runner").display()
1016 ));
1017 }
1018
1019 if let Some((width, height)) = self.window_size {
1020 cmd.arg(format!("--window-size={width},{height}"));
1021 }
1022
1023 if !self.sandbox {
1024 cmd.args(["--no-sandbox", "--disable-setuid-sandbox"]);
1025 }
1026
1027 match self.headless {
1028 HeadlessMode::False => (),
1029 HeadlessMode::True => {
1030 cmd.args(["--headless", "--hide-scrollbars", "--mute-audio"]);
1031 }
1032 HeadlessMode::New => {
1033 cmd.args(["--headless=new", "--hide-scrollbars", "--mute-audio"]);
1034 }
1035 }
1036
1037 if self.incognito {
1038 cmd.arg("--incognito");
1039 }
1040
1041 if let Some(ref envs) = self.process_envs {
1042 cmd.envs(envs);
1043 }
1044 cmd.stderr(Stdio::piped()).spawn()
1045 }
1046}
1047
1048#[deprecated(note = "Use detection::default_executable instead")]
1057pub fn default_executable() -> Result<std::path::PathBuf, String> {
1058 let options = DetectionOptions {
1059 msedge: false,
1060 unstable: false,
1061 };
1062 detection::default_executable(options)
1063}
1064
1065static DEFAULT_ARGS: [&str; 25] = [
1068 "--disable-background-networking",
1069 "--enable-features=NetworkService,NetworkServiceInProcess",
1070 "--disable-background-timer-throttling",
1071 "--disable-backgrounding-occluded-windows",
1072 "--disable-breakpad",
1073 "--disable-client-side-phishing-detection",
1074 "--disable-component-extensions-with-background-pages",
1075 "--disable-default-apps",
1076 "--disable-dev-shm-usage",
1077 "--disable-extensions",
1078 "--disable-features=TranslateUI",
1079 "--disable-hang-monitor",
1080 "--disable-ipc-flooding-protection",
1081 "--disable-popup-blocking",
1082 "--disable-prompt-on-repost",
1083 "--disable-renderer-backgrounding",
1084 "--disable-sync",
1085 "--force-color-profile=srgb",
1086 "--metrics-recording-only",
1087 "--no-first-run",
1088 "--enable-automation",
1089 "--password-store=basic",
1090 "--use-mock-keychain",
1091 "--enable-blink-features=IdleDetection",
1092 "--lang=en_US",
1093];