Skip to content

Commit 3ac0d81

Browse files
Review
1 parent f526630 commit 3ac0d81

17 files changed

Lines changed: 232 additions & 130 deletions

File tree

desktop/src/lib.rs

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -94,36 +94,47 @@ pub fn start() -> ExitCode {
9494
}
9595

9696
let acceleration = if prefs.disable_ui_acceleration { Acceleration::Disabled } else { Acceleration::Auto };
97-
let ui_context = ui_context.start(UiConfig { acceleration }).unwrap_or_else(|error| panic!("Failed to start the UI runtime: {error}"));
98-
let ui = ui_context
99-
.instance(&wgpu_context.device, &wgpu_context.queue)
100-
.unwrap_or_else(|error| panic!("Failed to start the UI: {error}"));
97+
let ui_context = match ui_context.start(UiConfig { acceleration }) {
98+
Ok(context) => context,
99+
Err(error) => {
100+
tracing::error!("Failed to start the UI runtime: {error}");
101+
return ExitCode::FAILURE;
102+
}
103+
};
104+
let ui = match ui_context.instance(&wgpu_context.device, &wgpu_context.queue) {
105+
Ok(ui) => ui,
106+
Err(error) => {
107+
tracing::error!("Failed to start the UI: {error}");
108+
return ExitCode::FAILURE;
109+
}
110+
};
101111
tracing::info!("UI runtime started successfully");
102112

103113
{
104114
let ui = ui.clone();
105115
let scheduler = app_event_scheduler.clone();
106-
std::thread::Builder::new()
107-
.name("ui-events".to_string())
108-
.spawn(move || {
109-
while let Some(event) = ui.recv() {
110-
match event {
111-
UiEvent::Ready => scheduler.schedule(AppEvent::WebCommunicationInitialized),
112-
UiEvent::Frame(texture) => scheduler.schedule(AppEvent::UiUpdate(texture)),
113-
UiEvent::Cursor(cursor) => scheduler.schedule(AppEvent::CursorChange(cursor)),
114-
UiEvent::Message(message) => match wrapper::deserialize_editor_message(&message) {
115-
Some(message) => scheduler.schedule(AppEvent::DesktopWrapperMessage(message)),
116-
None => tracing::error!("Failed to deserialize web message"),
117-
},
118-
UiEvent::InitFailed(error) => {
119-
tracing::error!("UI initialization failed: {error}");
120-
scheduler.schedule(AppEvent::UiCrashed);
121-
}
122-
UiEvent::Crashed => scheduler.schedule(AppEvent::UiCrashed),
116+
let spawned = std::thread::Builder::new().name("ui-events".to_string()).spawn(move || {
117+
while let Some(event) = ui.recv() {
118+
match event {
119+
UiEvent::Ready => scheduler.schedule(AppEvent::WebCommunicationInitialized),
120+
UiEvent::Frame(texture) => scheduler.schedule(AppEvent::UiUpdate(texture)),
121+
UiEvent::Cursor(cursor) => scheduler.schedule(AppEvent::CursorChange(cursor)),
122+
UiEvent::Message(message) => match wrapper::deserialize_editor_message(&message) {
123+
Some(message) => scheduler.schedule(AppEvent::DesktopWrapperMessage(message)),
124+
None => tracing::error!("Failed to deserialize web message"),
125+
},
126+
UiEvent::InitFailed(error) => {
127+
tracing::error!("UI initialization failed: {error}");
128+
scheduler.schedule(AppEvent::UiCrashed);
123129
}
130+
UiEvent::Crashed => scheduler.schedule(AppEvent::UiCrashed),
124131
}
125-
})
126-
.expect("Failed to spawn the UI event bridge thread");
132+
}
133+
});
134+
if let Err(error) = spawned {
135+
tracing::error!("Failed to spawn the UI event bridge thread: {error}");
136+
return ExitCode::FAILURE;
137+
}
127138
}
128139

129140
let app = App::new(ui.clone(), wgpu_context, app_event_receiver, app_event_scheduler, prefs, cli.files);
@@ -158,14 +169,6 @@ pub fn start() -> ExitCode {
158169
_ => {}
159170
}
160171

161-
// Workaround for a Windows-specific exception that occurs when `app` is dropped.
162-
// The issue causes the window to hang for a few seconds before closing.
163-
// Appears to be related to CEF object destruction order.
164-
// Calling `exit` bypasses rust teardown and lets Windows perform process cleanup.
165-
// TODO: Identify and fix the underlying CEF shutdown issue so this workaround can be removed.
166-
#[cfg(target_os = "windows")]
167-
std::process::exit(0);
168-
169172
#[cfg(not(target_os = "windows"))]
170173
ExitCode::SUCCESS
171174
}

desktop/ui/src/context.rs

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,27 @@ impl CefContext {
6969
let control_thread = std::thread::Builder::new()
7070
.name("cef-host-control".to_string())
7171
.spawn(move || {
72-
let result = control(CefContextHandle);
72+
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| control(CefContextHandle)));
7373
with_context(|context| {
74-
context.browser.host().unwrap().close_browser(1);
74+
if let Some(host) = context.browser.host() {
75+
host.close_browser(1);
76+
}
7577
});
7678
run_on_ui_thread(cef::quit_message_loop);
77-
let _ = result_sender.send(result);
79+
match result {
80+
Ok(result) => {
81+
let _ = result_sender.send(result);
82+
}
83+
Err(panic) => std::panic::resume_unwind(panic),
84+
}
7885
})
7986
.expect("Failed to spawn the CEF control thread");
8087
cef::run_message_loop();
8188
drop(CONTEXT.take());
8289
cef::shutdown();
83-
let _ = control_thread.join();
90+
if let Err(panic) = control_thread.join() {
91+
std::panic::resume_unwind(panic);
92+
}
8493
result_receiver.recv().expect("The CEF control thread ended without a result")
8594
}
8695
}
@@ -95,11 +104,12 @@ pub(crate) fn execute_helper_process() -> std::process::ExitCode {
95104

96105
fn bootstrap(helper: bool) -> Args {
97106
#[cfg(target_os = "macos")]
98-
let _loader = {
107+
{
99108
let loader = cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), helper);
100109
assert!(loader.load());
101-
loader
102-
};
110+
// LibraryLoader unloads the framework on drop
111+
std::mem::forget(loader);
112+
}
103113
#[cfg(not(target_os = "macos"))]
104114
let _ = helper;
105115

@@ -109,13 +119,13 @@ fn bootstrap(helper: bool) -> Args {
109119

110120
fn initialize(args: &Args, instance_dir: &Path, accelerated_paint: bool) -> Result<(), InitError> {
111121
let mut app = App::new(BrowserProcessAppImpl::new(accelerated_paint));
112-
if cef::initialize(Some(args.as_main_args()), Some(&platform_settings(instance_dir)), Some(&mut app), std::ptr::null_mut()) != 1 {
122+
if cef::initialize(Some(args.as_main_args()), Some(&platform_settings(instance_dir)?), Some(&mut app), std::ptr::null_mut()) != 1 {
113123
return Err(InitError::InitializationFailureCode(cef::get_exit_code() as u32));
114124
}
115125
Ok(())
116126
}
117127

118-
fn platform_settings(instance_dir: &Path) -> Settings {
128+
fn platform_settings(instance_dir: &Path) -> Result<Settings, InitError> {
119129
let log_severity = match std::env::var("GRAPHITE_BROWSER_LOG").unwrap_or_default().to_lowercase().as_str() {
120130
"debug" => cef_log_severity_t::LOGSEVERITY_VERBOSE,
121131
"info" => cef_log_severity_t::LOGSEVERITY_INFO,
@@ -125,9 +135,12 @@ fn platform_settings(instance_dir: &Path) -> Settings {
125135
_ => cef_log_severity_t::LOGSEVERITY_FATAL,
126136
};
127137

138+
let Some(root_cache_path) = instance_dir.to_str().map(CefString::from) else {
139+
return Err(InitError::PathResolutionFailed(format!("non-UTF-8 instance directory path: {}", instance_dir.display())));
140+
};
128141
let base = Settings {
129142
windowless_rendering_enabled: 1,
130-
root_cache_path: instance_dir.to_str().map(CefString::from).unwrap(),
143+
root_cache_path,
131144
cache_path: "".into(),
132145
disable_signal_handlers: 1,
133146
log_severity: LogSeverity::from(log_severity),
@@ -136,24 +149,31 @@ fn platform_settings(instance_dir: &Path) -> Settings {
136149

137150
#[cfg(target_os = "macos")]
138151
{
139-
let exe = std::env::current_exe().expect("cannot get current exe path");
140-
let app_root = exe.parent().and_then(|p| p.parent()).expect("bad path structure").parent().expect("bad path structure");
141-
Settings {
142-
main_bundle_path: app_root.to_str().map(CefString::from).unwrap(),
152+
let exe = std::env::current_exe().map_err(|e| InitError::PathResolutionFailed(format!("cannot get current exe path: {e}")))?;
153+
let app_root = exe
154+
.parent()
155+
.and_then(|p| p.parent())
156+
.and_then(|p| p.parent())
157+
.ok_or_else(|| InitError::PathResolutionFailed(format!("executable is not inside an app bundle: {}", exe.display())))?;
158+
let Some(main_bundle_path) = app_root.to_str().map(CefString::from) else {
159+
return Err(InitError::PathResolutionFailed(format!("invalid app bundle path: {}", app_root.display())));
160+
};
161+
Ok(Settings {
162+
main_bundle_path,
143163
multi_threaded_message_loop: 0,
144164
external_message_pump: 0,
145165
no_sandbox: 1, // GPU helper crashes when running with sandbox
146166
..base
147-
}
167+
})
148168
}
149169

150170
#[cfg(not(target_os = "macos"))]
151-
Settings {
171+
Ok(Settings {
152172
multi_threaded_message_loop: 1,
153173
#[cfg(target_os = "linux")]
154174
no_sandbox: 1,
155175
..base
156-
}
176+
})
157177
}
158178

159179
fn create_browser(delegate: BrowserDelegate, frames: FrameStreamer, view_info_sender: Sender<ViewInfoUpdate>, instance_dir: TempDir, accelerated_paint: bool) -> Result<BrowserContext, InitError> {
@@ -187,7 +207,9 @@ fn create_browser(delegate: BrowserDelegate, frames: FrameStreamer, view_info_se
187207

188208
let mut scheme_handler_factory = SchemeHandlerFactory::new(SchemeHandlerFactoryImpl::new(delegate.clone()));
189209
incognito_request_context.clear_scheme_handler_factories();
190-
incognito_request_context.register_scheme_handler_factory(Some(&RESOURCE_SCHEME.into()), Some(&RESOURCE_DOMAIN.into()), Some(&mut scheme_handler_factory));
210+
if incognito_request_context.register_scheme_handler_factory(Some(&RESOURCE_SCHEME.into()), Some(&RESOURCE_DOMAIN.into()), Some(&mut scheme_handler_factory)) != 1 {
211+
return Err(InitError::SchemeHandlerRegistrationFailed);
212+
}
191213

192214
let url = format!("{RESOURCE_SCHEME}://{RESOURCE_DOMAIN}/");
193215
browser_host_create_browser_sync(
@@ -220,6 +242,10 @@ pub(crate) enum InitError {
220242
BrowserCreationFailed,
221243
#[error("Request context creation failed")]
222244
RequestContextCreationFailed,
245+
#[error("Failed to resolve a required path: {0}")]
246+
PathResolutionFailed(String),
247+
#[error("Scheme handler registration failed")]
248+
SchemeHandlerRegistrationFailed,
223249
}
224250

225251
#[derive(Clone)]
@@ -261,7 +287,10 @@ impl BrowserContext {
261287

262288
fn refresh_view_info(&self) {
263289
let view_info = self.delegate.view_info();
264-
let host = self.browser.host().unwrap();
290+
let Some(host) = self.browser.host() else {
291+
tracing::error!("Browser host is not available, cannot refresh view info");
292+
return;
293+
};
265294
host.set_zoom_level(view_info.zoom());
266295
host.was_resized();
267296

@@ -278,7 +307,11 @@ impl BrowserContext {
278307
impl Drop for BrowserContext {
279308
fn drop(&mut self) {
280309
tracing::debug!("Shutting down CEF");
281-
self.browser.host().unwrap().close_browser(1);
310+
if let Some(host) = self.browser.host() {
311+
host.close_browser(1);
312+
} else {
313+
tracing::error!("Browser host is not available, cannot close browser");
314+
}
282315
}
283316
}
284317

@@ -298,7 +331,9 @@ where
298331
{
299332
let closure_task = ClosureTask::new(closure);
300333
let mut task = Task::new(closure_task);
301-
post_task(ThreadId::from(cef_thread_id_t::TID_UI), Some(&mut task));
334+
if post_task(ThreadId::from(cef_thread_id_t::TID_UI), Some(&mut task)) != 1 {
335+
tracing::error!("Failed to post a task to the CEF UI thread");
336+
}
302337
}
303338

304339
fn with_context<F>(closure: F)

desktop/ui/src/dirs.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ const APP_DIRECTORY_NAME: &str = "Graphite";
99

1010
pub(crate) fn app_tmp_dir() -> PathBuf {
1111
let path = std::env::temp_dir().join(APP_DIRECTORY_NAME);
12-
if !path.exists() {
13-
fs::create_dir_all(&path).unwrap_or_else(|_| panic!("Failed to create directory at {path:?}"));
12+
if let Err(e) = fs::create_dir_all(&path) {
13+
tracing::error!("Failed to create temp directory at {path:?}: {e}");
1414
}
1515
path
1616
}

desktop/ui/src/frames/import/d3d11.rs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::{TextureImportError, TextureImportResult, TextureImporter, texture_descriptor, wgpu_format};
22
use cef::sys::cef_color_type_t;
33
use std::os::raw::c_void;
4+
use wgpu::hal::api;
45

56
pub struct D3D11Importer {
67
pub handle: *mut c_void,
@@ -15,16 +16,11 @@ impl TextureImporter for D3D11Importer {
1516
return Err(TextureImportError::InvalidHandle("Null D3D11 shared texture handle".to_string()));
1617
}
1718

18-
if is_d3d12_backend(device) {
19-
match self.import_via_d3d12(device) {
20-
Ok(texture) => {
21-
tracing::trace!("Successfully imported D3D11 shared texture via D3D12");
22-
return Ok(texture);
23-
}
24-
Err(e) => {
25-
tracing::warn!("Failed to import D3D11 via D3D12: {}, trying Vulkan fallback", e);
26-
}
27-
}
19+
let is_d3d12_backend = unsafe { device.as_hal::<api::Dx12>().is_some() };
20+
21+
if is_d3d12_backend {
22+
let texture = self.import_via_d3d12(device)?;
23+
return Ok(texture);
2824
}
2925

3026
let texture = self.import_via_vulkan(device)?;
@@ -138,8 +134,3 @@ impl D3D11Importer {
138134
}
139135
}
140136
}
141-
142-
fn is_d3d12_backend(device: &wgpu::Device) -> bool {
143-
use wgpu::hal::api;
144-
unsafe { device.as_hal::<api::Dx12>().is_some() }
145-
}

0 commit comments

Comments
 (0)