use std::{
fs::create_dir_all,
path::{Component, Path, PathBuf},
sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
},
time::Duration,
};
use base64::Engine;
use cef::*;
use sha2::{Digest, Sha256};
use tauri_runtime::webview::WebviewAttributes;
use tauri_utils::Theme;
use crate::cef_impl::{preferences, request_handler};
#[inline]
fn theme_to_color_variant(theme: Option<Theme>) -> ColorVariant {
match theme {
Some(Theme::Dark) => ColorVariant::DARK,
Some(Theme::Light) => ColorVariant::LIGHT,
_ => ColorVariant::SYSTEM,
}
}
pub(crate) fn apply_theme_scheme(request_context: Option<&RequestContext>, theme: Option<Theme>) {
if let Some(request_context) = request_context {
request_context.set_chrome_color_scheme(theme_to_color_variant(theme), 0);
}
}
fn resolve_request_context_cache_path(global_cache_path: &Path, data_directory: &Path) -> PathBuf {
if data_directory.is_absolute() {
if data_directory.starts_with(global_cache_path) {
return data_directory.to_path_buf();
} else {
log::warn!(
"data directory is not a child of the global cache path, we will derive a profile hash from it"
);
}
} else if !data_directory
.components()
.any(|component| matches!(component, Component::ParentDir))
{
return global_cache_path.join(data_directory);
} else {
log::warn!(
"data directory is a relative path with parent components, we will derive a profile hash from it"
);
}
let mut hasher = Sha256::new();
hasher.update(data_directory.as_os_str().as_encoded_bytes());
let hash = hasher.finalize();
let suffix = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&hash[..16]);
let path = global_cache_path.join(format!("Profile-{suffix}"));
log::info!(
"derived profile hash from data directory: {suffix}, cache path: {}",
path.display()
);
path
}
pub(crate) type RequestContextInitContinuation = Box<dyn FnOnce(Option<RequestContext>) + 'static>;
pub(crate) fn deferred_init_continuation<F>(
work: F,
) -> (Arc<AtomicBool>, RequestContextInitContinuation)
where
F: FnOnce(Option<RequestContext>) + 'static,
{
struct Guard(Arc<AtomicBool>);
impl Drop for Guard {
fn drop(&mut self) {
self.0.store(true, Ordering::SeqCst);
}
}
let flag = Arc::new(AtomicBool::new(false));
let guard = Guard(flag.clone());
let wrapped: RequestContextInitContinuation = Box::new(move |request_context| {
let _guard = guard;
work(request_context);
});
(flag, wrapped)
}
pub(crate) fn wait_for_deferred_init(flag: &Arc<AtomicBool>) {
let on_ui_thread = cef::currently_on(cef::sys::cef_thread_id_t::TID_UI.into()) != 0;
if on_ui_thread {
let _allow = AllowNestableTasks::enter();
while !flag.load(Ordering::SeqCst) {
cef::do_message_loop_work();
}
} else {
while !flag.load(Ordering::SeqCst) {
std::thread::sleep(Duration::from_millis(1));
}
}
}
struct AllowNestableTasks;
impl AllowNestableTasks {
fn enter() -> Self {
NESTABLE_TASKS_DEPTH.with(|depth| {
let current = depth.get();
if current == 0 {
cef::set_nestable_tasks_allowed(1);
}
depth.set(current + 1);
});
Self
}
}
impl Drop for AllowNestableTasks {
fn drop(&mut self) {
NESTABLE_TASKS_DEPTH.with(|depth| {
let current = depth.get();
depth.set(current - 1);
if current == 1 {
cef::set_nestable_tasks_allowed(0);
}
});
}
}
thread_local! {
static NESTABLE_TASKS_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
wrap_request_context_handler! {
struct WebviewRequestContextHandler {
on_initialized: Arc<Mutex<Option<RequestContextInitContinuation>>>,
}
impl RequestContextHandler {
fn on_request_context_initialized(&self, request_context: Option<&mut RequestContext>) {
let Some(callback) = self.on_initialized.lock().unwrap().take() else {
return;
};
let request_context = request_context.map(|rc| rc.clone());
callback(request_context);
}
}
}
fn apply_proxy(request_context: &RequestContext, proxy_url: &url::Url) {
let scheme = match proxy_url.scheme() {
"socks5" | "socks5h" => "socks5",
"socks4" | "socks4a" => "socks4",
"https" => "https",
_ => "http",
};
let Some(host) = proxy_url.host_str() else {
log::warn!("ignoring proxy URL without a host: {proxy_url}");
return;
};
let server = match proxy_url.port_or_known_default() {
Some(port) => format!("{scheme}://{host}:{port}"),
None => format!("{scheme}://{host}"),
};
if !preferences::set_preference(
request_context,
"proxy",
&serde_json::json!({ "mode": "fixed_servers", "server": server }),
) {
log::warn!("failed to apply the proxy preference to the CEF request context");
}
}
pub(crate) fn request_context_from_webview_attributes<'a>(
global_cache_path: &Path,
webview_attributes: &WebviewAttributes,
profile_preferences: Arc<Vec<(String, serde_json::Value)>>,
content_settings: Arc<Vec<(ContentSettingTypes, ContentSettingValues)>>,
custom_schemes: impl IntoIterator<Item = &'a String>,
custom_protocol_scheme: &str,
scheme_registry: request_handler::SchemeRegistry,
on_initialized: RequestContextInitContinuation,
) -> Option<RequestContext> {
let cache_path = if webview_attributes.incognito {
CefStringUtf16::from("")
} else if let Some(data_directory) = &webview_attributes.data_directory {
let cache_path = resolve_request_context_cache_path(global_cache_path, data_directory);
if let Err(error) = create_dir_all(&cache_path) {
log::error!(
"failed to create request context cache directory {}: {error}",
cache_path.display()
);
}
CefStringUtf16::from(cache_path.to_string_lossy().as_ref())
} else {
let global_context =
request_context_get_global_context().expect("Failed to get global request context");
(&global_context.cache_path()).into()
};
let settings = RequestContextSettings {
cache_path,
..Default::default()
};
let rc_holder: Arc<Mutex<Option<RequestContext>>> = Arc::new(Mutex::new(None));
let proxy_url = webview_attributes.proxy_url.clone();
let wrapped_callback: RequestContextInitContinuation = Box::new({
let rc_holder = rc_holder.clone();
move |rc| {
if let Some(rc) = rc.as_ref() {
preferences::apply_app_webview_preferences(rc, &profile_preferences);
preferences::apply_default_content_settings(rc, &content_settings);
if let Some(proxy_url) = proxy_url.as_ref() {
apply_proxy(rc, proxy_url);
}
}
on_initialized(rc);
let _released = rc_holder.lock().unwrap().take();
}
});
let mut handler = WebviewRequestContextHandler::new(Arc::new(Mutex::new(Some(wrapped_callback))));
let request_context = request_context_create_context(Some(&settings), Some(&mut handler));
*rc_holder.lock().unwrap() = request_context.clone();
if let Some(request_context) = request_context.as_ref() {
for scheme in custom_schemes {
request_context.register_scheme_handler_factory(
Some(&custom_protocol_scheme.into()),
Some(&format!("{scheme}.localhost").as_str().into()),
Some(&mut request_handler::UriSchemeHandlerFactory::new(
scheme_registry.clone(),
scheme.clone(),
)),
);
}
}
request_context
}