#[derive(Debug, Clone)]
pub struct BrowserConfig {
chrome_path: Option<String>,
user_data_dir: Option<String>,
headless: bool,
args: Vec<String>,
remote_debugging_port: u16,
address: Option<String>,
tmp_path: Option<String>,
existing_only: bool,
}
impl Default for BrowserConfig {
fn default() -> Self {
Self {
chrome_path: None,
user_data_dir: None,
headless: false,
args: Self::default_args(),
remote_debugging_port: 9222,
address: None,
tmp_path: None,
existing_only: false,
}
}
}
fn default_chrome_args() -> Vec<&'static str> {
vec![
"--no-default-browser-check",
"--disable-suggestions-ui",
"--no-first-run",
"--disable-infobars",
"--disable-popup-blocking",
"--hide-crash-restore-bubble",
"--disable-features=PrivacySandboxSettings4",
"--disable-blink-features=AutomationControlled",
]
}
impl BrowserConfig {
fn default_args() -> Vec<String> {
default_chrome_args().into_iter().map(String::from).collect()
}
pub fn new() -> Self {
Self::default()
}
pub fn chrome_path(mut self, path: impl Into<String>) -> Self {
self.chrome_path = Some(path.into());
self
}
pub fn user_data_dir(mut self, dir: impl Into<String>) -> Self {
self.user_data_dir = Some(dir.into());
self
}
pub fn headless(mut self, on: bool) -> Self {
self.headless = on;
self
}
pub fn set_local_port(mut self, port: u16) -> Self {
self.remote_debugging_port = port;
self.address = Some(format!("127.0.0.1:{}", port));
self
}
pub fn set_address(mut self, address: impl Into<String>) -> Self {
let a = address.into();
let a = a.replace("localhost", "127.0.0.1");
let a = a
.strip_prefix("http://")
.or_else(|| a.strip_prefix("https://"))
.unwrap_or(&a)
.trim_start_matches('/')
.to_string();
if let Some((_, port_str)) = a.split_once(':') {
if let Ok(port) = port_str.parse::<u16>() {
self.remote_debugging_port = port;
}
}
self.address = Some(a);
self
}
pub fn tmp_path(mut self, path: impl Into<String>) -> Self {
self.tmp_path = Some(path.into());
self
}
pub fn existing_only(mut self, on: bool) -> Self {
self.existing_only = on;
self
}
pub fn set_argument(mut self, arg: impl AsRef<str>, value: Option<impl Into<String>>) -> Self {
let arg = arg.as_ref().to_string();
self = self.remove_argument(&arg);
if let Some(v) = value {
self.args.push(format!("{}={}", arg, v.into()));
} else {
self.args.push(arg);
}
self
}
pub fn remove_argument(mut self, arg_prefix: impl AsRef<str>) -> Self {
let prefix = arg_prefix.as_ref();
self.args.retain(|a| a != prefix && !a.starts_with(&format!("{}=", prefix)));
self
}
pub fn args(mut self, args: Vec<String>) -> Self {
self.args = args;
self
}
pub fn remote_debugging_port(mut self, port: u16) -> Self {
self.remote_debugging_port = port;
if self.address.is_none() {
self.address = Some(format!("127.0.0.1:{}", port));
}
self
}
pub fn no_imgs(self, on: bool) -> Self {
if on {
self.set_argument("--blink-settings=imagesEnabled", Some("false"))
} else {
self.remove_argument("--blink-settings=imagesEnabled")
}
}
pub fn mute(self, on: bool) -> Self {
if on {
self.set_argument("--mute-audio", None::<&str>)
} else {
self.remove_argument("--mute-audio")
}
}
pub fn incognito(self, on: bool) -> Self {
if on {
self.set_argument("--incognito", None::<&str>)
} else {
self.remove_argument("--incognito")
}
}
pub fn ignore_certificate_errors(self, on: bool) -> Self {
if on {
self.set_argument("--ignore-certificate-errors", None::<&str>)
} else {
self.remove_argument("--ignore-certificate-errors")
}
}
pub fn set_user_agent(self, ua: impl Into<String>) -> Self {
self.set_argument("--user-agent", Some(ua))
}
pub fn set_proxy(self, proxy: impl Into<String>) -> Self {
self.set_argument("--proxy-server", Some(proxy))
}
pub fn set_cache_path(self, path: impl Into<String>) -> Self {
self.set_argument("--disk-cache-dir", Some(path))
}
pub fn set_profile_directory(self, profile: impl Into<String>) -> Self {
self.set_argument("--profile-directory", Some(profile))
}
pub(crate) fn get_chrome_path(&self) -> Option<&str> {
self.chrome_path.as_deref()
}
pub(crate) fn get_user_data_dir(&self) -> Option<&str> {
self.user_data_dir.as_deref()
}
pub(crate) fn get_headless(&self) -> bool {
self.headless
}
pub(crate) fn get_args(&self) -> &[String] {
&self.args
}
pub(crate) fn get_remote_debugging_port(&self) -> u16 {
self.remote_debugging_port
}
pub(crate) fn get_address(&self) -> Option<&str> {
self.address.as_deref()
}
pub(crate) fn get_tmp_path(&self) -> Option<&str> {
self.tmp_path.as_deref()
}
pub(crate) fn get_existing_only(&self) -> bool {
self.existing_only
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_default() {
let c = BrowserConfig::new();
assert_eq!(c.get_remote_debugging_port(), 9222);
assert!(c.get_chrome_path().is_none());
assert!(c.get_user_data_dir().is_none());
assert!(!c.get_headless());
assert!(!c.get_args().is_empty()); assert!(c.get_address().is_none());
assert!(!c.get_existing_only());
}
#[test]
fn config_builder() {
let c = BrowserConfig::new()
.chrome_path("/usr/bin/chrome")
.user_data_dir("/tmp/profile")
.headless(true)
.remote_debugging_port(9333)
.args(vec!["--foo".into(), "--bar".into()]);
assert_eq!(c.get_chrome_path(), Some("/usr/bin/chrome"));
assert_eq!(c.get_user_data_dir(), Some("/tmp/profile"));
assert!(c.get_headless());
assert_eq!(c.get_remote_debugging_port(), 9333);
assert_eq!(c.get_args(), &["--foo", "--bar"]);
assert_eq!(c.get_address(), Some("127.0.0.1:9333"));
}
#[test]
fn config_set_local_port_and_address() {
let c = BrowserConfig::new().set_local_port(9223);
assert_eq!(c.get_remote_debugging_port(), 9223);
assert_eq!(c.get_address(), Some("127.0.0.1:9223"));
let c = BrowserConfig::new().set_address("127.0.0.1:9224");
assert_eq!(c.get_remote_debugging_port(), 9224);
assert_eq!(c.get_address(), Some("127.0.0.1:9224"));
}
#[test]
fn config_default_impl() {
let c = BrowserConfig::default();
assert!(c.get_chrome_path().is_none());
assert!(!c.get_headless());
}
}