use cef::{
CefString, ContentSettingTypes, ContentSettingValues, ImplDictionaryValue, ImplListValue,
ImplPreferenceManager, ImplRequestContext, ImplValue, RequestContext, Value,
};
pub(crate) fn set_preference_error_slot() -> CefString {
CefString::from("")
}
pub(crate) const DEVTOOLS_AVAILABILITY: &str = "devtools.availability";
pub(crate) const DEVTOOLS_ALLOWED: i64 = 0;
pub(crate) const REMOTE_DEBUGGING_ALLOWED: &str = "devtools.remote_debugging.allowed";
const PREFERENCES: &[(&str, bool)] = &[
("credentials_enable_service", false),
("profile.password_manager_leak_detection", false),
("autofill.profile_enabled", false),
("autofill.credit_card_enabled", false),
("translate.enabled", false),
("alternate_error_pages.enabled", false),
("search.suggest_enabled", false),
("privacy_sandbox.m1.topics_enabled", false),
("privacy_sandbox.m1.fledge_enabled", false),
("privacy_sandbox.m1.ad_measurement_enabled", false),
];
fn to_cef_value(value: &serde_json::Value) -> Option<Value> {
let cef_value = cef::value_create()?;
match value {
serde_json::Value::Null => {
cef_value.set_null();
}
serde_json::Value::Bool(value) => {
cef_value.set_bool(i32::from(*value));
}
serde_json::Value::Number(number) => {
match number.as_i64() {
Some(integer) => {
let Ok(integer) = i32::try_from(integer) else {
log::debug!("preference value {integer} does not fit in the int Chromium stores");
return None;
};
cef_value.set_int(integer);
}
None => {
cef_value.set_double(number.as_f64()?);
}
}
}
serde_json::Value::String(string) => {
cef_value.set_string(Some(&CefString::from(string.as_str())));
}
serde_json::Value::Array(items) => {
let list = cef::list_value_create()?;
for (index, item) in items.iter().enumerate() {
let mut item = to_cef_value(item)?;
list.set_value(index, Some(&mut item));
}
let mut list = list;
cef_value.set_list(Some(&mut list));
}
serde_json::Value::Object(entries) => {
let dictionary = cef::dictionary_value_create()?;
for (key, entry) in entries {
let mut entry = to_cef_value(entry)?;
dictionary.set_value(Some(&CefString::from(key.as_str())), Some(&mut entry));
}
let mut dictionary = dictionary;
cef_value.set_dictionary(Some(&mut dictionary));
}
}
Some(cef_value)
}
pub(crate) fn apply_app_webview_preferences(
request_context: &RequestContext,
overrides: &[(String, serde_json::Value)],
) {
let defaults = PREFERENCES
.iter()
.map(|(name, enabled)| ((*name).to_string(), serde_json::Value::Bool(*enabled)));
for (name, value) in defaults.chain(overrides.iter().cloned()) {
let _ = set_preference(request_context, &name, &value);
}
}
pub(crate) fn apply_default_content_settings(
request_context: &RequestContext,
settings: &[(ContentSettingTypes, ContentSettingValues)],
) {
for (content_type, value) in settings {
request_context.set_content_setting(None, None, *content_type, *value);
}
}
#[must_use]
pub(crate) fn set_preference<M: ImplPreferenceManager>(
manager: &M,
name: &str,
value: &serde_json::Value,
) -> bool {
if manager.can_set_preference(Some(&name.into())) != 1 {
log::debug!("the CEF preference store does not allow setting the {name} preference");
return false;
}
let Some(value) = to_cef_value(value) else {
log::debug!("failed to build a CEF value for the {name} preference");
return false;
};
let mut value = value;
let mut error = set_preference_error_slot();
if manager.set_preference(Some(&name.into()), Some(&mut value), Some(&mut error)) != 1 {
log::debug!("failed to apply the {name} preference: {error}");
return false;
}
true
}
pub(crate) fn apply_global_preferences(overrides: &[(String, serde_json::Value)]) {
if overrides.is_empty() {
return;
}
let Some(manager) = cef::preference_manager_get_global() else {
log::debug!("the global CEF preference manager is unavailable; skipping global preferences");
return;
};
for (name, value) in overrides {
let _ = set_preference(&manager, name, value);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_disabled_preferences_are_all_disabled() {
for (name, enabled) in PREFERENCES {
assert!(!enabled, "{name} is listed as a preference forced off");
}
}
#[test]
fn safe_browsing_is_not_disabled_by_default() {
assert!(
!PREFERENCES
.iter()
.any(|(name, _)| *name == "safebrowsing.enabled"),
"Safe Browsing stays on unless the application asks otherwise; see the module docs"
);
}
#[test]
fn the_privacy_sandbox_apis_are_pinned_off() {
for api in [
"privacy_sandbox.m1.topics_enabled",
"privacy_sandbox.m1.fledge_enabled",
"privacy_sandbox.m1.ad_measurement_enabled",
] {
assert!(PREFERENCES.iter().any(|(name, _)| *name == api));
}
}
}