use either::Either;
use std::vec;
use crate::{
database::browser_profile::Platform,
server::dtos::{
browser_profile_dto::{self, BrowserProfileFullData, Mode},
settings_dto::{DISABLE_GPU, DISABLE_IMAGES},
start_dto::{LoggingLevel, Os, StartRequest},
},
};
use darkwing_derive::DarkwingArgs;
use super::config::BrowserProfileConfig;
struct List(Vec<String>);
impl std::fmt::Display for List {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.iter().enumerate().try_for_each(|(i, s)| {
if i > 0 {
write!(f, ",")?;
}
write!(f, "{}", s)
})
}
}
impl From<Vec<String>> for List {
fn from(value: Vec<String>) -> Self {
Self(value)
}
}
impl List {
pub fn new(value: Vec<String>) -> Self {
Self(value)
}
#[cfg(test)]
pub fn empty() -> Self {
Self(Vec::new())
}
pub fn has(&self, value: &str) -> bool {
self.0.contains(&value.to_string())
}
}
trait IsEmpty {
fn is_empty(&self) -> bool;
}
impl IsEmpty for List {
fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl IsEmpty for String {
fn is_empty(&self) -> bool {
self.is_empty()
}
}
impl IsEmpty for Option<String> {
fn is_empty(&self) -> bool {
match self {
Some(value) => value.is_empty(),
None => true,
}
}
}
impl IsEmpty for Option<usize> {
fn is_empty(&self) -> bool {
self.is_none()
}
}
impl IsEmpty for usize {
fn is_empty(&self) -> bool {
false
}
}
trait DisplayAnything {
fn to_string(&self) -> String;
}
impl DisplayAnything for Option<usize> {
fn to_string(&self) -> String {
match self {
Some(v) => v.to_string(),
None => "".to_string(),
}
}
}
impl DisplayAnything for Option<String> {
fn to_string(&self) -> String {
match self {
Some(v) => v.to_string(),
None => "".to_string(),
}
}
}
#[derive(PartialEq, Eq)]
enum Switch {
On,
Off,
}
impl Default for Switch {
fn default() -> Self {
Self::Off
}
}
impl IsEmpty for Switch {
fn is_empty(&self) -> bool {
self == &Switch::Off
}
}
impl DisplayAnything for Switch {
fn to_string(&self) -> String {
match self {
Switch::On => "".to_string(),
Switch::Off => "".to_string(),
}
}
}
impl Switch {
fn from_bool(value: bool) -> Self {
if value {
Switch::On
} else {
Switch::Off
}
}
}
#[derive(DarkwingArgs)]
pub struct Args {
#[darkwing_args(internal)]
user_os: Os,
#[darkwing_args(internal)]
user_args: browser_profile_dto::Args,
#[darkwing_args(name = "user-data-dir")]
browser_profile_datadir: String,
#[darkwing_args(name = "dolphin-shared-dir")]
dolphin_components_dir: String,
#[darkwing_args(name = "enable-features")]
enable_features: List,
#[darkwing_args(name = "disable-features")]
disable_features: List,
#[darkwing_args(name = "enable-logging")]
enable_logging: Option<String>,
#[darkwing_args(name = "v")]
logging_verbosity: Option<usize>,
#[darkwing_args(name = "disable-backgrounding-occluded-windows")]
disable_backgrounding_occluded_windows: Switch,
#[darkwing_args(name = "disable-web-security")]
disable_web_security: Switch,
#[darkwing_args(name = "flag-switches-begin")]
flag_switches_begin: Switch,
#[darkwing_args(name = "disable-site-isolation-trials")]
disable_site_isolation_trials: Switch,
#[darkwing_args(name = "flag-switches-end")]
flag_switches_end: Switch,
#[darkwing_args(name = "down-port")]
down_port: usize,
#[darkwing_args(name = "remote-debugging-port")]
remote_debugging_port: Option<usize>,
#[darkwing_args(name = "remote-allow-origins")]
remote_allow_origins: Option<String>,
#[darkwing_args(name = "enable-blink-features")]
enable_blink_features: List,
#[darkwing_args(name = "disable-field-trial-config")]
disable_field_trial_config: Switch,
#[darkwing_args(name = "locale")]
locale: String,
#[darkwing_args(name = "user-agent")]
user_agent: Option<String>,
#[darkwing_args(name = "headless")]
headless: Option<String>,
#[darkwing_args(name = "enable-unsafe-webgpu")]
enable_unsafe_webgpu: Switch,
#[darkwing_args(name = "new-extensions")]
new_extensions: Switch,
#[darkwing_args(name = "off-updater")]
off_updater: Switch,
#[darkwing_args(name = "blink-settings")]
blink_settings: Option<String>,
#[darkwing_args(name = "disable-webgl")]
disable_webgl: Switch,
#[darkwing_args(name = "disable-gpu")]
disable_gpu: Switch,
#[darkwing_args(name = "proxy-server")]
proxy_server: Option<String>,
#[darkwing_args(name = "proxy-bypass-list")]
proxy_bypass_list: Option<String>,
#[darkwing_args(name = "component-updater")]
component_updater: String,
}
impl Args {
fn make_enable_features(bp: &BrowserProfileFullData) -> List {
let mut enable_features_vec = vec!["enable-tls13-early-data".into()];
if bp.platform == Platform::Windows {
enable_features_vec.push("SharedStorageAPI".into());
}
List::new(enable_features_vec)
}
fn make_disable_features(
bp: &BrowserProfileFullData,
request: &StartRequest,
config: &BrowserProfileConfig,
) -> List {
let mut disable_features =
vec!["UseOsCryptAsyncForCookieEncryption".into()];
if request.for_scenario {
disable_features.push("IsolateOrigins".into());
disable_features.push("site-per-process".into());
disable_features.push("SitePerProcess".into());
}
if bp.platform == Platform::Windows {
disable_features.push("kJavaScriptIteratorHelpers".into());
disable_features.push("PrintCompositorLPAC".into());
}
if bp.webgl.mode == Mode::Off
|| bp.webgpu.mode == Mode::Off
|| (bp.webgpu.mode != Mode::Real && config.webgpu.is_none())
|| (bp.webgpu.mode == Mode::Empty && bp.webgl_info.mode != Mode::Real)
{
disable_features.push("WebGPU".into());
disable_features.push("WebGPUService".into());
}
List::new(disable_features)
}
fn make_proxy_server(
request: &StartRequest,
bp: &BrowserProfileFullData,
) -> Option<String> {
if let Some(override_proxy_url) =
&request.connection_info.override_proxy_url
{
Some(override_proxy_url.clone())
} else {
bp.proxy.as_ref().map(|proxy| proxy.as_base64_url())
}
}
fn make_user_proxy_bypass_list(
bp: &BrowserProfileFullData,
) -> Option<Vec<String>> {
let user_proxy_bypass_list = bp.args.0.clone().unwrap_or_default();
let user_proxy_bypass_list = user_proxy_bypass_list
.iter()
.filter(|arg| arg.starts_with("proxy-bypass-list"));
let proxy_bypass_list = user_proxy_bypass_list
.filter_map(|arg| {
arg
.split('=')
.nth(1)
.map(|value| value.trim_matches('\"').to_string())
})
.collect::<Vec<String>>();
let domains_list = proxy_bypass_list
.iter()
.flat_map(|arg| arg.split(';').map(|arg| arg.to_string()))
.collect();
Some(domains_list)
}
fn make_proxy_bypass_list(
bp: &BrowserProfileFullData,
request: &StartRequest,
proxy_bypass_domains_list: Option<Vec<String>>,
) -> Option<String> {
let mut full_list = vec![
"*anty-api.com".to_string(),
request.remote_api_base_url.clone(),
];
full_list.extend(
proxy_bypass_domains_list
.unwrap_or_default()
.iter()
.cloned(),
);
if bp.proxy.is_none() {
None
} else {
Some(full_list.join("; "))
}
}
pub fn new(
config: BrowserProfileConfig,
bp: BrowserProfileFullData,
request: StartRequest,
) -> Self {
let enable_features = Self::make_enable_features(&bp);
let disable_features = Self::make_disable_features(&bp, &request, &config);
let (enable_logging, logging_verbosity) =
if request.logging_level.clone() == LoggingLevel::Trace {
(Some("stderr".into()), Some(1))
} else {
(None, None)
};
let disable_backgrounding_occluded_windows = Switch::from_bool(
request.automation || request.for_scenario || request.scenum_custom_mode,
);
let (
disable_web_security,
flag_switches_begin,
disable_site_isolation_trials,
flag_switches_end,
) = if request.for_scenario {
(Switch::On, Switch::On, Switch::On, Switch::On)
} else {
(Switch::Off, Switch::Off, Switch::Off, Switch::Off)
};
let (remote_debugging_port, remote_allow_origins) = if request.automation {
(Some(0), Some("*".into()))
} else {
(None, None)
};
let enable_blink_features = List::new(vec![
"SharedStorageAPI".into(),
"FencedFrames".into(),
"EnforceAnonymityExposure".into(),
"Fledge".into(),
]);
let headless = if request.headless {
Some("new".into())
} else {
None
};
let enable_unsafe_webgpu = Switch::from_bool(
request.os == Os::Windows
&& bp.webgl_info.mode != Mode::Real
&& !disable_features.has("WebGPU"),
);
let off_updater = Switch::from_bool(bp.webrtc.mode == Mode::Manual);
let blink_settings = if request.imageless || bp.settings.any(DISABLE_IMAGES)
{
Some("imagesEnabled=false".into())
} else {
None
};
let disable_webgl = Switch::from_bool(
bp.webgl_info.mode == Mode::Off || bp.webgl.mode == Mode::Off,
);
let disable_gpu = Switch::from_bool(
bp.webgl_info.mode == Mode::Software || bp.settings.any(DISABLE_GPU),
);
let proxy_server = Self::make_proxy_server(&request, &bp);
let proxy_bypass_domains_list = Self::make_user_proxy_bypass_list(&bp);
let proxy_bypass_list =
Self::make_proxy_bypass_list(&bp, &request, proxy_bypass_domains_list);
Self {
user_os: request.os,
user_args: bp.args,
browser_profile_datadir: request.paths.user_data_dir,
dolphin_components_dir: request.paths.user_chromium_components_dir,
enable_features,
disable_features,
enable_logging,
logging_verbosity,
disable_backgrounding_occluded_windows,
disable_web_security,
flag_switches_begin,
disable_site_isolation_trials,
flag_switches_end,
down_port: request.browser_down_port as usize,
remote_debugging_port,
remote_allow_origins,
enable_blink_features,
disable_field_trial_config: Switch::On,
locale: config.navigator.app_locale,
user_agent: bp.useragent.value,
headless,
enable_unsafe_webgpu,
new_extensions: Switch::On,
off_updater,
blink_settings,
disable_webgl,
disable_gpu,
proxy_server,
proxy_bypass_list,
component_updater: "fast-update".into(),
}
}
pub fn output(&self) -> Either<String, Vec<String>> {
let args = self.stringify();
if self.user_os == Os::Windows {
let mut result = args
.into_iter()
.map(|(arg, value)| match value.is_empty() {
true => arg,
false => format!("{arg}={value}"),
})
.collect::<Vec<String>>();
for arg in self.user_args.splitted() {
result.push(arg);
}
Either::Right(result)
} else {
let our_args = args
.into_iter()
.map(|(arg, value)| match value.is_empty() {
true => arg,
false => format!(r#"{arg}="{value}""#),
})
.collect::<Vec<String>>()
.join(" ");
let user_args = self.user_args.join();
Either::Left(format!("{} {}", our_args, user_args).trim().to_string())
}
}
}
#[cfg(test)]
mod tests {
use crate::server::{
dtos::proxy_dto::ProxyFullData,
services::browser_profile_services::{
BrowserProfileService, BrowserProfileServiceTrait,
},
};
use super::*;
#[test]
fn test_list_display() {
let list = List::new(vec!["a".into(), "b".into(), "c".into()]);
assert_eq!(format!("{}", list), "a,b,c");
}
#[test]
fn test_args_serialize() {
let mut args = Args {
user_os: Os::MacOS,
user_args: browser_profile_dto::Args(None),
browser_profile_datadir: "~/Library/Application Support/Google/Chrome"
.into(),
dolphin_components_dir: "~/Library/Application Support/Google/Chrome"
.into(),
enable_features: List::empty(),
disable_features: List::empty(),
enable_logging: Some("stderr".into()),
logging_verbosity: Some(1),
disable_backgrounding_occluded_windows: Switch::Off,
disable_web_security: Switch::Off,
flag_switches_begin: Switch::Off,
disable_site_isolation_trials: Switch::Off,
flag_switches_end: Switch::Off,
down_port: 0,
remote_debugging_port: None,
remote_allow_origins: None,
enable_blink_features: List::empty(),
disable_field_trial_config: Switch::Off,
locale: "en-US".into(),
user_agent: None,
headless: None,
enable_unsafe_webgpu: Switch::Off,
new_extensions: Switch::Off,
off_updater: Switch::Off,
blink_settings: None,
disable_webgl: Switch::Off,
disable_gpu: Switch::Off,
proxy_server: None,
proxy_bypass_list: None,
component_updater: "fast-update".into(),
};
let expected = vec![
(
"--user-data-dir".into(),
"~/Library/Application Support/Google/Chrome".into(),
),
(
"--dolphin-shared-dir".into(),
"~/Library/Application Support/Google/Chrome".into(),
),
("--enable-logging".into(), "stderr".into()),
("--v".into(), "1".into()),
("--down-port".into(), "0".into()),
("--locale".into(), "en-US".into()),
("--component-updater".into(), "fast-update".into()),
];
let serialized = args.stringify();
assert_eq!(serialized, expected);
args.enable_logging = None;
args.logging_verbosity = None;
let expected = vec![
(
"--user-data-dir".into(),
"~/Library/Application Support/Google/Chrome".into(),
),
(
"--dolphin-shared-dir".into(),
"~/Library/Application Support/Google/Chrome".into(),
),
("--down-port".into(), "0".into()),
("--locale".into(), "en-US".into()),
("--component-updater".into(), "fast-update".into()),
];
let serialized = args.stringify();
assert_eq!(serialized, expected);
args.disable_backgrounding_occluded_windows = Switch::On;
let expected = vec![
(
"--user-data-dir".into(),
"~/Library/Application Support/Google/Chrome".into(),
),
(
"--dolphin-shared-dir".into(),
"~/Library/Application Support/Google/Chrome".into(),
),
("--disable-backgrounding-occluded-windows".into(), "".into()),
("--down-port".into(), "0".into()),
("--locale".into(), "en-US".into()),
("--component-updater".into(), "fast-update".into()),
];
let serialized = args.stringify();
assert_eq!(serialized, expected);
}
#[test]
fn test_args_output() {
let mut args = Args {
user_os: Os::MacOS,
user_args: browser_profile_dto::Args(None),
browser_profile_datadir: "~/Library/Application Support/Google/Chrome"
.into(),
dolphin_components_dir: "~/Library/Application Support/Google/Chrome"
.into(),
enable_features: List::empty(),
disable_features: List::empty(),
enable_logging: Some("stderr".into()),
logging_verbosity: Some(1),
disable_backgrounding_occluded_windows: Switch::Off,
disable_web_security: Switch::Off,
flag_switches_begin: Switch::Off,
disable_site_isolation_trials: Switch::Off,
flag_switches_end: Switch::Off,
down_port: 0,
remote_debugging_port: None,
remote_allow_origins: None,
enable_blink_features: List::empty(),
disable_field_trial_config: Switch::Off,
locale: "en-US".into(),
user_agent: None,
headless: None,
enable_unsafe_webgpu: Switch::Off,
new_extensions: Switch::Off,
off_updater: Switch::Off,
blink_settings: None,
disable_webgl: Switch::Off,
disable_gpu: Switch::Off,
proxy_server: None,
proxy_bypass_list: None,
component_updater: "fast-update".into(),
};
let expected =
r#"--user-data-dir="~/Library/Application Support/Google/Chrome"
--dolphin-shared-dir="~/Library/Application Support/Google/Chrome"
--enable-logging="stderr"
--v="1"
--down-port="0"
--locale="en-US"
--component-updater="fast-update""#
.replace('\n', " ");
assert_eq!(args.output(), Either::Left(expected));
args.disable_backgrounding_occluded_windows = Switch::On;
let expected =
r#"--user-data-dir="~/Library/Application Support/Google/Chrome"
--dolphin-shared-dir="~/Library/Application Support/Google/Chrome"
--enable-logging="stderr"
--v="1"
--disable-backgrounding-occluded-windows
--down-port="0"
--locale="en-US"
--component-updater="fast-update""#
.replace('\n', " ");
assert_eq!(args.output(), Either::Left(expected));
}
#[test]
fn test_make_user_proxy_bypass_list() {
let mut bp = BrowserProfileService::get_mock_profile(Os::MacOS);
bp.args = browser_profile_dto::Args(Some(vec![
"some-arg=value".to_string(),
"another-arg".to_string(),
]));
assert_eq!(Args::make_user_proxy_bypass_list(&bp), Some(vec![]));
let mut bp = BrowserProfileService::get_mock_profile(Os::MacOS);
bp.args = browser_profile_dto::Args(Some(vec![
"proxy-bypass-list=example.com".to_string(),
]));
assert_eq!(
Args::make_user_proxy_bypass_list(&bp),
Some(vec!["example.com".to_string()])
);
let mut bp = BrowserProfileService::get_mock_profile(Os::MacOS);
bp.args = browser_profile_dto::Args(Some(vec![
"proxy-bypass-list=example.com".to_string(),
"proxy-bypass-list=test.com;another.com".to_string(),
]));
assert_eq!(
Args::make_user_proxy_bypass_list(&bp),
Some(vec![
"example.com".to_string(),
"test.com".to_string(),
"another.com".to_string()
])
);
let mut bp = BrowserProfileService::get_mock_profile(Os::MacOS);
bp.args = browser_profile_dto::Args(Some(vec![
r#"proxy-bypass-list="example.com;test.com""#.to_string(),
]));
assert_eq!(
Args::make_user_proxy_bypass_list(&bp),
Some(vec!["example.com".to_string(), "test.com".to_string()])
);
let mut bp = BrowserProfileService::get_mock_profile(Os::MacOS);
bp.args = browser_profile_dto::Args(Some(vec![
"some-arg=value".to_string(),
"proxy-bypass-list=example.com".to_string(),
"another-arg".to_string(),
r#"proxy-bypass-list="test.com;another.com""#.to_string(),
]));
assert_eq!(
Args::make_user_proxy_bypass_list(&bp),
Some(vec![
"example.com".to_string(),
"test.com".to_string(),
"another.com".to_string()
])
);
}
#[test]
fn test_make_proxy_bypass_list() {
let mut request = StartRequest::get_mock();
request.remote_api_base_url = "https://api.example.com".to_string();
let mut bp = BrowserProfileService::get_mock_profile(Os::MacOS);
bp.proxy = None;
let result = Args::make_proxy_bypass_list(&bp, &request, None);
assert_eq!(result, None);
bp.proxy = Some(ProxyFullData::get_mock());
let result = Args::make_proxy_bypass_list(&bp, &request, None);
assert_eq!(
result,
Some("*anty-api.com; https://api.example.com".to_string())
);
let additional_domains =
Some(vec!["test.com".to_string(), "example.org".to_string()]);
let result =
Args::make_proxy_bypass_list(&bp, &request, additional_domains);
assert_eq!(
result,
Some(
"*anty-api.com; https://api.example.com; test.com; example.org"
.to_string()
)
);
let result = Args::make_proxy_bypass_list(&bp, &request, Some(vec![]));
assert_eq!(
result,
Some("*anty-api.com; https://api.example.com".to_string())
);
let additional_domains =
Some(vec!["*anty-api.com".to_string(), "test.com".to_string()]);
let result =
Args::make_proxy_bypass_list(&bp, &request, additional_domains);
assert_eq!(
result,
Some(
"*anty-api.com; https://api.example.com; *anty-api.com; test.com"
.to_string()
)
);
}
#[test]
fn test_user_custom_args() {
let request = StartRequest::get_mock();
let mut bp = BrowserProfileService::get_mock_profile(Os::MacOS);
let config =
BrowserProfileService::create_config(&bp, request.clone(), "".into())
.unwrap();
bp.args = browser_profile_dto::Args(Some(vec!["--test-arg".to_string()]));
let args = Args::new(config, bp, request);
assert_eq!(args.user_args.splitted(), ["--test-arg"]);
}
}