From da330b6dd0f11278a35b793045cf155421253410 Mon Sep 17 00:00:00 2001 From: Timon Date: Sat, 13 Sep 2025 15:19:45 -0700 Subject: [PATCH] Desktop: Run CEF in incognito mode and delete root cache path on exit (#3137) * Try * Run cef in non persistence mode --- Cargo.lock | 1 + desktop/Cargo.toml | 1 + desktop/src/cef/context/builder.rs | 55 ++++++++++++++----- desktop/src/cef/context/singlethreaded.rs | 2 + desktop/src/cef/dirs.rs | 11 +--- desktop/src/cef/internal.rs | 1 + .../cef/internal/browser_process_handler.rs | 12 +--- .../cef/internal/scheme_handler_factory.rs | 28 +++------- desktop/src/main.rs | 4 ++ 9 files changed, 61 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aec9abb4..98a7c904 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2277,6 +2277,7 @@ dependencies = [ "objc2-io-surface", "objc2-metal 0.3.1", "open", + "rand 0.9.2", "rfd", "ron", "serde", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 039fe132..699f02b7 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -41,6 +41,7 @@ vello = { workspace = true } derivative = { workspace = true } rfd = { workspace = true } open = { workspace = true } +rand = { workspace = true } serde = { workspace = true } # Hardware acceleration dependencies diff --git a/desktop/src/cef/context/builder.rs b/desktop/src/cef/context/builder.rs index c771c4ae..a5bbc93c 100644 --- a/desktop/src/cef/context/builder.rs +++ b/desktop/src/cef/context/builder.rs @@ -1,16 +1,19 @@ +use std::path::PathBuf; + use cef::args::Args; use cef::sys::{CEF_API_VERSION_LAST, cef_resultcode_t}; use cef::{ - App, BrowserSettings, CefString, Client, DictionaryValue, ImplCommandLine, RenderHandler, RequestContext, Settings, WindowInfo, api_hash, browser_host_create_browser_sync, execute_process, + App, BrowserSettings, CefString, Client, DictionaryValue, ImplCommandLine, ImplRequestContext, RenderHandler, RequestContextSettings, SchemeHandlerFactory, Settings, WindowInfo, api_hash, + browser_host_create_browser_sync, execute_process, }; use super::CefContext; use super::singlethreaded::SingleThreadedCefContext; use crate::cef::CefEventHandler; use crate::cef::consts::{RESOURCE_DOMAIN, RESOURCE_SCHEME}; -use crate::cef::dirs::{cef_cache_dir, cef_data_dir}; +use crate::cef::dirs::create_instance_dir; use crate::cef::input::InputState; -use crate::cef::internal::{BrowserProcessAppImpl, BrowserProcessClientImpl, RenderHandlerImpl, RenderProcessAppImpl}; +use crate::cef::internal::{BrowserProcessAppImpl, BrowserProcessClientImpl, RenderHandlerImpl, RenderProcessAppImpl, SchemeHandlerFactoryImpl}; pub(crate) struct CefContextBuilder { pub(crate) args: Args, @@ -61,33 +64,37 @@ impl CefContextBuilder { #[cfg(target_os = "macos")] pub(crate) fn initialize(self, event_handler: H) -> Result { + let instance_dir = create_instance_dir(); + let settings = Settings { windowless_rendering_enabled: 1, multi_threaded_message_loop: 0, external_message_pump: 1, - root_cache_path: cef_data_dir().to_str().map(CefString::from).unwrap(), - cache_path: cef_cache_dir().to_str().map(CefString::from).unwrap(), + root_cache_path: instance_dir.to_str().map(CefString::from).unwrap(), + cache_path: CefString::from(""), ..Default::default() }; self.initialize_inner(&event_handler, settings)?; - create_browser(event_handler) + create_browser(event_handler, instance_dir) } #[cfg(not(target_os = "macos"))] pub(crate) fn initialize(self, event_handler: H) -> Result { + let instance_dir = create_instance_dir(); + let settings = Settings { windowless_rendering_enabled: 1, multi_threaded_message_loop: 1, - root_cache_path: cef_data_dir().to_str().map(CefString::from).unwrap(), - cache_path: cef_cache_dir().to_str().map(CefString::from).unwrap(), + root_cache_path: instance_dir.to_str().map(CefString::from).unwrap(), + cache_path: CefString::from(""), ..Default::default() }; self.initialize_inner(&event_handler, settings)?; - super::multithreaded::run_on_ui_thread(move || match create_browser(event_handler) { + super::multithreaded::run_on_ui_thread(move || match create_browser(event_handler, instance_dir) { Ok(context) => { super::multithreaded::CONTEXT.with(|b| { *b.borrow_mut() = Some(context); @@ -103,10 +110,10 @@ impl CefContextBuilder { } fn initialize_inner(self, event_handler: &H, settings: Settings) -> Result<(), InitError> { - let mut cef_app = App::new(BrowserProcessAppImpl::new(event_handler.clone())); - let result = cef::initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut()); // Attention! Wrapping this in an extra App is necessary, otherwise the program still compiles but segfaults + let mut cef_app = App::new(BrowserProcessAppImpl::new(event_handler.clone())); + let result = cef::initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut()); if result != 1 { let cef_exit_code = cef::get_exit_code() as u32; if cef_exit_code == cef_resultcode_t::CEF_RESULT_CODE_NORMAL_EXIT_PROCESS_NOTIFIED as u32 { @@ -118,12 +125,10 @@ impl CefContextBuilder { } } -fn create_browser(event_handler: H) -> Result { +fn create_browser(event_handler: H, instance_dir: PathBuf) -> Result { let render_handler = RenderHandler::new(RenderHandlerImpl::new(event_handler.clone())); let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, event_handler.clone())); - let url = CefString::from(format!("{RESOURCE_SCHEME}://{RESOURCE_DOMAIN}/").as_str()); - let window_info = WindowInfo { windowless_rendering_enabled: 1, #[cfg(feature = "accelerated_paint")] @@ -137,19 +142,37 @@ fn create_browser(event_handler: H) -> Result::None, + ) else { + return Err(InitError::RequestContextCreationFailed); + }; + + let mut scheme_handler_factory = SchemeHandlerFactory::new(SchemeHandlerFactoryImpl::new(event_handler.clone())); + incognito_request_context.clear_scheme_handler_factories(); + incognito_request_context.register_scheme_handler_factory(Some(&CefString::from(RESOURCE_SCHEME)), Some(&CefString::from(RESOURCE_DOMAIN)), Some(&mut scheme_handler_factory)); + + let url = CefString::from(format!("{RESOURCE_SCHEME}://{RESOURCE_DOMAIN}/").as_str()); + let browser = browser_host_create_browser_sync( Some(&window_info), Some(&mut client), Some(&url), Some(&settings), Option::<&mut DictionaryValue>::None, - Option::<&mut RequestContext>::None, + Some(&mut incognito_request_context), ); if let Some(browser) = browser { Ok(SingleThreadedCefContext { browser, input_state: InputState::default(), + instance_dir, }) } else { tracing::error!("Failed to create browser"); @@ -171,6 +194,8 @@ pub(crate) enum InitError { InitializationFailed(u32), #[error("Browser creation failed")] BrowserCreationFailed, + #[error("Request context creation failed")] + RequestContextCreationFailed, #[error("Another instance is already running")] AlreadyRunning, } diff --git a/desktop/src/cef/context/singlethreaded.rs b/desktop/src/cef/context/singlethreaded.rs index 2222c852..2825b784 100644 --- a/desktop/src/cef/context/singlethreaded.rs +++ b/desktop/src/cef/context/singlethreaded.rs @@ -10,6 +10,7 @@ use super::CefContext; pub(super) struct SingleThreadedCefContext { pub(super) browser: Browser, pub(super) input_state: InputState, + pub(super) instance_dir: std::path::PathBuf, } impl CefContext for SingleThreadedCefContext { @@ -33,6 +34,7 @@ impl CefContext for SingleThreadedCefContext { impl Drop for SingleThreadedCefContext { fn drop(&mut self) { cef::shutdown(); + std::fs::remove_dir_all(&self.instance_dir).expect("Failed to remove CEF cache directory"); } } diff --git a/desktop/src/cef/dirs.rs b/desktop/src/cef/dirs.rs index 0d4ce6fb..8511011e 100644 --- a/desktop/src/cef/dirs.rs +++ b/desktop/src/cef/dirs.rs @@ -4,14 +4,9 @@ use crate::dirs::{ensure_dir_exists, graphite_data_dir}; static CEF_DIR_NAME: &str = "browser"; -pub(crate) fn cef_data_dir() -> PathBuf { - let path = graphite_data_dir().join(CEF_DIR_NAME); - ensure_dir_exists(&path); - path -} - -pub(crate) fn cef_cache_dir() -> PathBuf { - let path = cef_data_dir().join("cache"); +pub(crate) fn create_instance_dir() -> PathBuf { + let instance_id: String = (0..32).map(|_| format!("{:x}", rand::random::() % 16)).collect(); + let path = graphite_data_dir().join(CEF_DIR_NAME).join(instance_id); ensure_dir_exists(&path); path } diff --git a/desktop/src/cef/internal.rs b/desktop/src/cef/internal.rs index 2e48c6df..47f0ff20 100644 --- a/desktop/src/cef/internal.rs +++ b/desktop/src/cef/internal.rs @@ -17,3 +17,4 @@ pub(super) use browser_process_app::BrowserProcessAppImpl; pub(super) use browser_process_client::BrowserProcessClientImpl; pub(super) use render_handler::RenderHandlerImpl; pub(super) use render_process_app::RenderProcessAppImpl; +pub(super) use scheme_handler_factory::SchemeHandlerFactoryImpl; diff --git a/desktop/src/cef/internal/browser_process_handler.rs b/desktop/src/cef/internal/browser_process_handler.rs index 780218a6..74604b16 100644 --- a/desktop/src/cef/internal/browser_process_handler.rs +++ b/desktop/src/cef/internal/browser_process_handler.rs @@ -2,11 +2,9 @@ use std::time::{Duration, Instant}; use cef::rc::{Rc, RcImpl}; use cef::sys::{_cef_browser_process_handler_t, cef_base_ref_counted_t, cef_browser_process_handler_t}; -use cef::{CefString, ImplBrowserProcessHandler, SchemeHandlerFactory, WrapBrowserProcessHandler}; +use cef::{CefString, ImplBrowserProcessHandler, WrapBrowserProcessHandler}; -use super::scheme_handler_factory::SchemeHandlerFactoryImpl; use crate::cef::CefEventHandler; -use crate::cef::consts::RESOURCE_SCHEME; pub(crate) struct BrowserProcessHandlerImpl { object: *mut RcImpl, @@ -22,14 +20,6 @@ impl BrowserProcessHandlerImpl { } impl ImplBrowserProcessHandler for BrowserProcessHandlerImpl { - fn on_context_initialized(&self) { - cef::register_scheme_handler_factory( - Some(&CefString::from(RESOURCE_SCHEME)), - None, - Some(&mut SchemeHandlerFactory::new(SchemeHandlerFactoryImpl::new(self.event_handler.clone()))), - ); - } - fn on_schedule_message_pump_work(&self, delay_ms: i64) { self.event_handler.schedule_cef_message_loop_work(Instant::now() + Duration::from_millis(delay_ms as u64)); } diff --git a/desktop/src/cef/internal/scheme_handler_factory.rs b/desktop/src/cef/internal/scheme_handler_factory.rs index 83c006c7..44f51fe1 100644 --- a/desktop/src/cef/internal/scheme_handler_factory.rs +++ b/desktop/src/cef/internal/scheme_handler_factory.rs @@ -31,26 +31,14 @@ impl SchemeHandlerFactoryImpl { } impl ImplSchemeHandlerFactory for SchemeHandlerFactoryImpl { - fn create(&self, _browser: Option<&mut Browser>, _frame: Option<&mut Frame>, scheme_name: Option<&CefString>, request: Option<&mut Request>) -> Option { - if let Some(scheme_name) = scheme_name { - if scheme_name.to_string() != RESOURCE_SCHEME { - return None; - } - if let Some(request) = request { - let url = CefString::from(&request.url()).to_string(); - let path = url.strip_prefix(&format!("{RESOURCE_SCHEME}://")).unwrap(); - let domain = path.split('/').next().unwrap_or(""); - let path = path.strip_prefix(domain).unwrap_or(""); - let path = path.trim_start_matches('/'); - return match domain { - RESOURCE_DOMAIN => { - let resource = self.event_handler.load_resource(path.to_string().into()); - Some(ResourceHandler::new(ResourceHandlerImpl::new(resource))) - } - _ => None, - }; - } - return None; + fn create(&self, _browser: Option<&mut Browser>, _frame: Option<&mut Frame>, _scheme_name: Option<&CefString>, request: Option<&mut Request>) -> Option { + if let Some(request) = request { + let url = CefString::from(&request.url()).to_string(); + let path = url + .strip_prefix(&format!("{RESOURCE_SCHEME}://{RESOURCE_DOMAIN}/")) + .expect("CEF should only call this for our custom scheme and domain that we registered this factory for"); + let resource = self.event_handler.load_resource(path.to_string().into()); + return Some(ResourceHandler::new(ResourceHandlerImpl::new(resource))); } None } diff --git a/desktop/src/main.rs b/desktop/src/main.rs index f4ed4901..bdc392e9 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -63,6 +63,10 @@ fn main() { tracing::error!("Failed to create CEF browser"); exit(1); } + Err(cef::InitError::RequestContextCreationFailed) => { + tracing::error!("Failed to create CEF request context"); + exit(1); + } }; tracing::info!("CEF initialized successfully");