Files
Graphite/desktop/src/cef/context/builder.rs
Dennis Kobert e4dd3ce806 Desktop: Use multithreaded CEF event loop on Windows and Linux (#3076)
* Prototype multi threaded event loop

* Fix input event dispatch

* Remove dead code

* Reenable do_message_loop_work for macos targets

* Cleanup

* Review cleanup

* Remove outdated comment

* Attempt to fix texture import errors

---------

Co-authored-by: Timon Schelling <me@timon.zip>
2025-08-21 19:46:13 +00:00

172 lines
5.5 KiB
Rust

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,
};
use super::CefContext;
use super::singlethreaded::SingleThreadedCefContext;
use crate::cef::CefHandler;
use crate::cef::consts::{FRONTEND_DOMAIN, GRAPHITE_SCHEME};
use crate::cef::dirs::{cef_cache_dir, cef_data_dir};
use crate::cef::input::InputState;
use crate::cef::internal::{BrowserProcessAppImpl, BrowserProcessClientImpl, RenderHandlerImpl, RenderProcessAppImpl};
pub(crate) struct CefContextBuilder {
pub(crate) args: Args,
pub(crate) is_sub_process: bool,
}
unsafe impl Send for CefContextBuilder {}
impl CefContextBuilder {
pub(crate) fn new() -> Self {
#[cfg(target_os = "macos")]
let _loader = {
let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false);
assert!(loader.load());
loader
};
let _ = api_hash(CEF_API_VERSION_LAST, 0);
let args = Args::new();
let cmd = args.as_cmd_line().unwrap();
let switch = CefString::from("type");
let is_sub_process = cmd.has_switch(Some(&switch)) == 1;
Self { args, is_sub_process }
}
pub(crate) fn is_sub_process(&self) -> bool {
self.is_sub_process
}
pub(crate) fn execute_sub_process(&self) -> SetupError {
let cmd = self.args.as_cmd_line().unwrap();
let switch = CefString::from("type");
let process_type = CefString::from(&cmd.switch_value(Some(&switch)));
let mut app = RenderProcessAppImpl::app();
let ret = execute_process(Some(self.args.as_main_args()), Some(&mut app), std::ptr::null_mut());
if ret >= 0 {
SetupError::SubprocessFailed(process_type.to_string())
} else {
SetupError::Subprocess
}
}
#[cfg(target_os = "macos")]
pub(crate) fn initialize(self, event_handler: CefHandler) -> Result<impl CefContext, InitError> {
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(),
..Default::default()
};
self.initialize_inner(&event_handler, settings)?;
create_browser(event_handler)
}
#[cfg(not(target_os = "macos"))]
pub(crate) fn initialize(self, event_handler: CefHandler) -> Result<impl CefContext, InitError> {
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(),
..Default::default()
};
self.initialize_inner(&event_handler, settings)?;
super::multithreaded::run_on_ui_thread(move || match create_browser(event_handler) {
Ok(context) => {
super::multithreaded::CONTEXT.with(|b| {
*b.borrow_mut() = Some(context);
});
}
Err(e) => {
tracing::error!("Failed to initialize CEF context: {:?}", e);
std::process::exit(1);
}
});
Ok(super::multithreaded::MultiThreadedCefContextProxy)
}
fn initialize_inner(self, event_handler: &CefHandler, 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
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 {
return Err(InitError::AlreadyRunning);
}
return Err(InitError::InitializationFailed(cef_exit_code));
}
Ok(())
}
}
fn create_browser(event_handler: CefHandler) -> Result<SingleThreadedCefContext, InitError> {
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!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str());
let window_info = WindowInfo {
windowless_rendering_enabled: 1,
#[cfg(feature = "accelerated_paint")]
shared_texture_enabled: if crate::cef::platform::should_enable_hardware_acceleration() { 1 } else { 0 },
..Default::default()
};
let settings = BrowserSettings {
windowless_frame_rate: crate::consts::CEF_WINDOWLESS_FRAME_RATE,
background_color: 0x0,
..Default::default()
};
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,
);
if let Some(browser) = browser {
Ok(SingleThreadedCefContext {
browser,
input_state: InputState::default(),
})
} else {
tracing::error!("Failed to create browser");
Err(InitError::BrowserCreationFailed)
}
}
#[derive(thiserror::Error, Debug)]
pub(crate) enum SetupError {
#[error("This is the sub process should exit immediately")]
Subprocess,
#[error("Subprocess returned non zero exit code")]
SubprocessFailed(String),
}
#[derive(thiserror::Error, Debug)]
pub(crate) enum InitError {
#[error("Initialization failed")]
InitializationFailed(u32),
#[error("Browser creation failed")]
BrowserCreationFailed,
#[error("Another instance is already running")]
AlreadyRunning,
}