pub(crate) const REPLACING_SWITCHES: &[(&str, &str)] = &[
("disable-features", "Cef::disable_features"),
("enable-features", "Cef::enable_features"),
("js-flags", "Cef::javascript_flags"),
];
const DANGEROUS_SWITCHES: &[&str] = &[
"disable-web-security",
"allow-running-insecure-content",
"ignore-certificate-errors",
"ignore-certificate-errors-spki-list",
"unsafely-treat-insecure-origin-as-secure",
"allow-insecure-localhost",
"disable-site-isolation-trials",
"enable-blink-features",
"disable-blink-features",
"enable-unsafe-webgpu",
"disable-hid-blocklist",
"unsafely-allow-protected-media-identifier-for-domain",
"no-sandbox",
"disable-gpu-sandbox",
"disable-setuid-sandbox",
"disable-seccomp-filter-sandbox",
"disable-namespace-sandbox",
"disable-landlock-sandbox",
"disable-webnn-compiler-sandbox",
"allow-sandbox-debugging",
"allow-third-party-modules",
"single-process",
"host-resolver-rules",
"host-rules",
"ssl-key-log-file",
"log-net-log",
"net-log-capture-mode",
"disable-webrtc-encryption",
"use-fake-ui-for-media-stream",
"enable-speech-dispatcher",
"enable-gpu-benchmarking",
];
fn split_list(value: &str) -> impl Iterator<Item = &str> {
value
.split(',')
.map(str::trim)
.filter(|part| !part.is_empty())
}
fn merged_switch_value(existing: &str, values: &[String]) -> Option<String> {
let mut merged: Vec<&str> = split_list(existing).collect();
for value in values.iter().flat_map(|value| split_list(value)) {
if !merged.contains(&value) {
merged.push(value);
}
}
(!merged.is_empty()).then(|| merged.join(","))
}
pub(crate) fn append_merged_switch(
command_line: &mut cef::CommandLine,
name: &str,
values: &[String],
) {
use cef::{CefString, ImplCommandLine};
if values.is_empty() {
return;
}
let switch = CefString::from(name);
let existing = if command_line.has_switch(Some(&switch)) == 1 {
CefString::from(&command_line.switch_value(Some(&switch))).to_string()
} else {
String::new()
};
let Some(merged) = merged_switch_value(&existing, values) else {
return;
};
command_line.remove_switch(Some(&switch));
command_line.append_switch_with_value(Some(&switch), Some(&CefString::from(merged.as_str())));
}
fn switch_key(argument: &str) -> &str {
argument.trim_start_matches('-')
}
pub(crate) fn warn_about_replacing_switches(args: &[(String, Option<String>)]) {
for (argument, _) in args {
let key = switch_key(argument);
if let Some((_, replacement)) = REPLACING_SWITCHES.iter().find(|(name, _)| *name == key) {
log::warn!(
"the --{key} switch replaces the value Chromium and CEF already set rather than \
adding to it, which drops entries the runtime depends on. Use {replacement} instead."
);
}
}
}
pub(crate) fn warn_about_dangerous_switches(args: &[(String, Option<String>)]) {
for (argument, _) in args {
let key = switch_key(argument);
if DANGEROUS_SWITCHES.contains(&key) {
log::warn!(
"the --{key} switch turns off a Chromium security boundary. Chrome itself warns \
its users when it is set; do not ship it."
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_list_is_split_the_way_chromium_splits_it() {
let parts: Vec<_> = split_list("A,,B , C,").collect();
assert_eq!(parts, ["A", "B", "C"]);
}
fn merge(existing: &str, values: &[&str]) -> Option<String> {
let values: Vec<String> = values.iter().map(ToString::to_string).collect();
merged_switch_value(existing, &values)
}
#[test]
fn merging_keeps_what_cef_already_disabled() {
assert_eq!(
merge("GlicActorUi,LensOverlay", &["MediaRouter"]).as_deref(),
Some("GlicActorUi,LensOverlay,MediaRouter")
);
}
#[test]
fn merging_onto_an_unset_switch_writes_only_the_new_values() {
assert_eq!(merge("", &["A", "B"]).as_deref(), Some("A,B"));
}
#[test]
fn a_value_already_present_is_not_repeated() {
assert_eq!(
merge("A,B", &["B", "C", "B"]).as_deref(),
Some("A,B,C"),
"a duplicate would be harmless to Chromium but makes the switch unreadable"
);
}
#[test]
fn entries_may_themselves_be_comma_delimited() {
assert_eq!(merge("A", &["B,C", "D"]).as_deref(), Some("A,B,C,D"));
}
#[test]
fn nothing_to_write_leaves_the_command_line_alone() {
assert_eq!(merge("", &[""]), None);
assert_eq!(merge("", &[]), None);
}
#[test]
fn switch_names_are_recognised_with_or_without_a_prefix() {
assert_eq!(switch_key("--disable-features"), "disable-features");
assert_eq!(switch_key("-disable-features"), "disable-features");
assert_eq!(switch_key("disable-features"), "disable-features");
}
#[test]
fn every_replacing_switch_names_a_replacement() {
for (name, replacement) in REPLACING_SWITCHES {
assert!(!name.is_empty());
assert!(
replacement.starts_with("Cef::"),
"the warning tells the user what to call instead"
);
}
}
#[test]
fn the_two_lists_answer_different_questions() {
assert!(
REPLACING_SWITCHES
.iter()
.any(|(name, _)| *name == "disable-features")
);
assert!(!DANGEROUS_SWITCHES.contains(&"disable-features"));
assert!(DANGEROUS_SWITCHES.contains(&"enable-blink-features"));
assert!(
!REPLACING_SWITCHES
.iter()
.any(|(name, _)| *name == "enable-blink-features")
);
}
}