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