use std::time::Duration;
use std::{
collections::HashMap,
io,
path::{Path, PathBuf},
};
use super::argument::{Arg, ArgConst, ArgsBuilder};
use crate::async_process::{self, Child, Stdio};
use crate::detection::{self, DetectionOptions};
use crate::handler::REQUEST_TIMEOUT;
use crate::handler::viewport::Viewport;
pub const LAUNCH_TIMEOUT: u64 = 20_000;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum HeadlessMode {
False,
#[default]
True,
New,
}
#[derive(Debug, Clone)]
pub struct BrowserConfig {
pub(crate) headless: HeadlessMode,
pub(crate) sandbox: bool,
pub(crate) window_size: Option<(u32, u32)>,
pub(crate) port: u16,
pub(crate) executable: std::path::PathBuf,
pub(crate) extensions: Vec<String>,
pub process_envs: Option<HashMap<String, String>>,
pub user_data_dir: Option<PathBuf>,
pub(crate) incognito: bool,
pub(crate) launch_timeout: Duration,
pub(crate) ignore_https_errors: bool,
pub(crate) ignore_invalid_messages: bool,
pub(crate) disable_https_first: bool,
pub(crate) viewport: Option<Viewport>,
pub(crate) request_timeout: Duration,
pub(crate) args: Vec<Arg>,
pub(crate) disable_default_args: bool,
pub request_intercept: bool,
pub cache_enabled: bool,
pub(crate) hidden: bool,
}
#[derive(Debug, Clone)]
pub struct BrowserConfigBuilder {
headless: HeadlessMode,
sandbox: bool,
window_size: Option<(u32, u32)>,
port: u16,
executable: Option<PathBuf>,
executation_detection: DetectionOptions,
extensions: Vec<String>,
process_envs: Option<HashMap<String, String>>,
user_data_dir: Option<PathBuf>,
incognito: bool,
launch_timeout: Duration,
ignore_https_errors: bool,
ignore_invalid_events: bool,
disable_https_first: bool,
viewport: Option<Viewport>,
request_timeout: Duration,
args: Vec<Arg>,
disable_default_args: bool,
request_intercept: bool,
cache_enabled: bool,
hidden: bool,
}
impl BrowserConfig {
pub fn builder() -> BrowserConfigBuilder {
BrowserConfigBuilder::default()
}
pub fn with_executable(path: impl AsRef<Path>) -> Self {
Self::builder().chrome_executable(path).build().unwrap()
}
}
impl Default for BrowserConfigBuilder {
fn default() -> Self {
Self {
headless: HeadlessMode::False,
sandbox: true,
window_size: None,
port: 0,
executable: None,
executation_detection: DetectionOptions::default(),
extensions: Vec::new(),
process_envs: None,
user_data_dir: None,
incognito: false,
launch_timeout: Duration::from_millis(LAUNCH_TIMEOUT),
ignore_https_errors: true,
ignore_invalid_events: true,
disable_https_first: false,
viewport: Some(Default::default()),
request_timeout: Duration::from_millis(REQUEST_TIMEOUT),
args: Vec::new(),
disable_default_args: false,
request_intercept: false,
cache_enabled: true,
hidden: true,
}
}
}
impl BrowserConfigBuilder {
pub fn window_size(mut self, width: u32, height: u32) -> Self {
self.window_size = Some((width, height));
self
}
pub fn no_sandbox(mut self) -> Self {
self.sandbox = false;
self
}
pub fn with_head(mut self) -> Self {
self.headless = HeadlessMode::False;
self
}
pub fn new_headless_mode(mut self) -> Self {
self.headless = HeadlessMode::New;
self
}
pub fn headless_mode(mut self, mode: HeadlessMode) -> Self {
self.headless = mode;
self
}
pub fn incognito(mut self) -> Self {
self.incognito = true;
self
}
pub fn respect_https_errors(mut self) -> Self {
self.ignore_https_errors = false;
self
}
pub fn surface_invalid_messages(mut self) -> Self {
self.ignore_invalid_events = false;
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn launch_timeout(mut self, timeout: Duration) -> Self {
self.launch_timeout = timeout;
self
}
pub fn request_timeout(mut self, timeout: Duration) -> Self {
self.request_timeout = timeout;
self
}
pub fn viewport(mut self, viewport: impl Into<Option<Viewport>>) -> Self {
self.viewport = viewport.into();
self
}
pub fn user_data_dir(mut self, data_dir: impl AsRef<Path>) -> Self {
self.user_data_dir = Some(data_dir.as_ref().to_path_buf());
self
}
pub fn chrome_executable(mut self, path: impl AsRef<Path>) -> Self {
self.executable = Some(path.as_ref().to_path_buf());
self
}
pub fn chrome_detection(mut self, options: DetectionOptions) -> Self {
self.executation_detection = options;
self
}
pub fn extension(mut self, extension: impl Into<String>) -> Self {
self.extensions.push(extension.into());
self
}
pub fn extensions<I, S>(mut self, extensions: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
for ext in extensions {
self.extensions.push(ext.into());
}
self
}
pub fn env(mut self, key: impl Into<String>, val: impl Into<String>) -> Self {
self.process_envs
.get_or_insert(HashMap::new())
.insert(key.into(), val.into());
self
}
pub fn envs<I, K, V>(mut self, envs: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
self.process_envs
.get_or_insert(HashMap::new())
.extend(envs.into_iter().map(|(k, v)| (k.into(), v.into())));
self
}
pub fn arg(mut self, arg: impl Into<Arg>) -> Self {
self.args.push(arg.into());
self
}
pub fn args<I, S>(mut self, args: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<Arg>,
{
for arg in args {
self.args.push(arg.into());
}
self
}
pub fn disable_default_args(mut self) -> Self {
self.disable_default_args = true;
self
}
pub fn disable_https_first(mut self) -> Self {
self.disable_https_first = true;
self
}
pub fn enable_request_intercept(mut self) -> Self {
self.request_intercept = true;
self
}
pub fn disable_request_intercept(mut self) -> Self {
self.request_intercept = false;
self
}
pub fn enable_cache(mut self) -> Self {
self.cache_enabled = true;
self
}
pub fn disable_cache(mut self) -> Self {
self.cache_enabled = false;
self
}
pub fn hide(mut self) -> Self {
self.hidden = true;
if self.hidden && self.viewport == Some(Viewport::default()) {
self.viewport = None;
}
self
}
pub fn build(self) -> std::result::Result<BrowserConfig, String> {
let executable = if let Some(e) = self.executable {
e
} else {
detection::default_executable(self.executation_detection)?
};
Ok(BrowserConfig {
headless: self.headless,
sandbox: self.sandbox,
window_size: self.window_size,
port: self.port,
executable,
extensions: self.extensions,
process_envs: self.process_envs,
user_data_dir: self.user_data_dir,
incognito: self.incognito,
launch_timeout: self.launch_timeout,
ignore_https_errors: self.ignore_https_errors,
ignore_invalid_messages: self.ignore_invalid_events,
disable_https_first: self.disable_https_first,
viewport: self.viewport,
request_timeout: self.request_timeout,
args: self.args,
disable_default_args: self.disable_default_args,
request_intercept: self.request_intercept,
cache_enabled: self.cache_enabled,
hidden: self.hidden,
})
}
}
impl BrowserConfig {
pub fn launch(&self) -> io::Result<Child> {
let mut builder = ArgsBuilder::new();
if self.disable_default_args {
builder.args(self.args.clone());
} else {
builder.args(DEFAULT_ARGS.clone()).args(self.args.clone());
}
if !builder.has("remote-debugging-port") {
builder.arg(Arg::value("remote-debugging-port", self.port));
}
if self.extensions.is_empty() {
builder.arg(Arg::key("disable-extensions"));
} else {
builder.args(
self.extensions
.iter()
.map(|e| Arg::value("load-extension", e)),
);
}
if let Some(ref user_data) = self.user_data_dir {
builder.arg(Arg::value("user-data-dir", user_data.display()));
} else {
builder.arg(Arg::value(
"user-data-dir",
std::env::temp_dir().join("chromiumoxide-runner").display(),
));
}
if let Some((width, height)) = self.window_size {
builder.arg(Arg::values("window-size", [width, height]));
}
if !self.sandbox {
builder.args([Arg::key("no-sandbox"), Arg::key("disable-setuid-sandbox")]);
}
match self.headless {
HeadlessMode::False => (),
HeadlessMode::True => {
builder.args([
Arg::key("headless"),
Arg::key("hide-scrollbars"),
Arg::key("mute-audio"),
]);
}
HeadlessMode::New => {
builder.args([
Arg::value("headless", "new"),
Arg::key("hide-scrollbars"),
Arg::key("mute-audio"),
]);
}
}
if self.incognito {
builder.arg(Arg::key("incognito"));
}
if self.hidden {
builder.arg(Arg::value("disable-blink-features", "AutomationControlled"));
}
if self.disable_https_first {
builder.arg(Arg::values(
"disable-features",
["HttpsUpgrades", "HttpsFirstBalancedModeAutoEnable"],
));
}
let mut cmd = async_process::Command::new(&self.executable);
let args = builder.into_iter().collect::<Vec<String>>();
cmd.args(args);
if let Some(ref envs) = self.process_envs {
cmd.envs(envs);
}
cmd.stdout(Stdio::null()).stderr(Stdio::piped()).spawn()
}
}
static DEFAULT_ARGS: [ArgConst; 19] = [
ArgConst::key("disable-background-networking"),
ArgConst::key("disable-background-timer-throttling"),
ArgConst::key("disable-backgrounding-occluded-windows"),
ArgConst::key("disable-breakpad"),
ArgConst::key("disable-client-side-phishing-detection"),
ArgConst::key("disable-component-extensions-with-background-pages"),
ArgConst::key("disable-default-apps"),
ArgConst::key("disable-dev-shm-usage"),
ArgConst::values("disable-features", &["TranslateUI"]),
ArgConst::key("disable-hang-monitor"),
ArgConst::key("disable-ipc-flooding-protection"),
ArgConst::key("disable-popup-blocking"),
ArgConst::key("disable-prompt-on-repost"),
ArgConst::key("disable-renderer-backgrounding"),
ArgConst::key("disable-sync"),
ArgConst::values("force-color-profile", &["srgb"]),
ArgConst::key("no-first-run"),
ArgConst::values("password-store", &["basic"]),
ArgConst::key("use-mock-keychain"),
];