Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions frontend/wasm/src/editor_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// on the dispatcher messaging system and more complex Rust data types.
//
use crate::helpers::translate_key;
use crate::{EDITOR_HANDLE, EDITOR_HAS_CRASHED, Error, MESSAGE_BUFFER};
use crate::{EDITOR_HANDLE, EDITOR_HAS_CRASHED, Error, MESSAGE_BUFFER, PANIC_DIALOG_MESSAGE_CALLBACK};
use editor::consts::FILE_EXTENSION;
use editor::messages::clipboard::utility_types::ClipboardContentRaw;
use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys;
Expand Down Expand Up @@ -78,6 +78,16 @@ impl EditorHandle {
pub fn send_frontend_message_to_js_rust_proxy(&self, message: FrontendMessage) {
self.send_frontend_message_to_js(message);
}

fn initialize_handle(frontend_message_handler_callback: js_sys::Function) -> EditorHandle {
let panic_callback = frontend_message_handler_callback.clone();
let editor_handle = EditorHandle { frontend_message_handler_callback };
if EDITOR_HANDLE.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(editor_handle.clone()))).is_none() {
log::error!("Attempted to initialize the editor handle more than once");
}
PANIC_DIALOG_MESSAGE_CALLBACK.with_borrow_mut(|callback| *callback = Some(panic_callback));
editor_handle
}
}

#[wasm_bindgen]
Expand All @@ -97,23 +107,16 @@ impl EditorHandle {
uuid_random_seed,
);

let editor_handle = EditorHandle { frontend_message_handler_callback };
if EDITOR.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(editor))).is_none() {
log::error!("Attempted to initialize the editor more than once");
}
if EDITOR_HANDLE.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(editor_handle.clone()))).is_none() {
log::error!("Attempted to initialize the editor handle more than once");
}
editor_handle

Self::initialize_handle(frontend_message_handler_callback)
}

#[cfg(feature = "native")]
pub fn create(_platform: String, _uuid_random_seed: u64, frontend_message_handler_callback: js_sys::Function) -> EditorHandle {
let editor_handle = EditorHandle { frontend_message_handler_callback };
if EDITOR_HANDLE.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(editor_handle.clone()))).is_none() {
log::error!("Attempted to initialize the editor handle more than once");
}
editor_handle
Self::initialize_handle(frontend_message_handler_callback)
}

// Sends a message to the dispatcher in the Editor Backend
Expand Down
75 changes: 72 additions & 3 deletions frontend/wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ thread_local! {
pub static EDITOR: Mutex<Option<editor::application::Editor>> = const { Mutex::new(None) };
pub static MESSAGE_BUFFER: std::cell::RefCell<Vec<Message>> = const { std::cell::RefCell::new(Vec::new()) };
pub static EDITOR_HANDLE: Mutex<Option<editor_api::EditorHandle>> = const { Mutex::new(None) };
pub static PANIC_DIALOG_MESSAGE_CALLBACK: std::cell::RefCell<Option<js_sys::Function>> = const { std::cell::RefCell::new(None) };
}

/// Initialize the backend
Expand Down Expand Up @@ -72,12 +73,80 @@ pub fn panic_hook(info: &panic::PanicHookInfo) {

log::error!("{info}");

// Prefer using the raw JS callback to avoid mutex lock contention inside the panic hook.
// Fall back to the editor handle path if needed.
if let Err(info) = send_panic_dialog_via_callback(info) {
if let Err(info) = send_panic_dialog(info) {
send_panic_dialog_deferred(info);
}
}
}

fn send_panic_dialog_via_callback(panic_info: String) -> Result<(), String> {
let message = FrontendMessage::DisplayDialogPanic { panic_info };
let message_type = message.to_discriminant().local_name();
let Ok(message_data) = serde_wasm_bindgen::to_value(&message) else {
log::error!("Failed to serialize crash dialog panic message");
let FrontendMessage::DisplayDialogPanic { panic_info } = message else {
unreachable!("Message variant changed unexpectedly")
};
return Err(panic_info);
};

PANIC_DIALOG_MESSAGE_CALLBACK.with(|callback| {
let callback_ref = callback.borrow();
let Some(callback) = callback_ref.as_ref() else {
let FrontendMessage::DisplayDialogPanic { panic_info } = message else {
unreachable!("Message variant changed unexpectedly")
};
return Err(panic_info);
};

if let Err(error) = callback.call2(&JsValue::null(), &JsValue::from(message_type), &message_data) {
log::error!("Failed to send crash dialog panic message to JS: {:?}", error);
let FrontendMessage::DisplayDialogPanic { panic_info } = message else {
unreachable!("Message variant changed unexpectedly")
};
return Err(panic_info);
}

Ok(())
})
}

fn send_panic_dialog(panic_info: String) -> Result<(), String> {
EDITOR_HANDLE.with(|editor_handle| {
let mut guard = editor_handle.lock();
if let Ok(Some(handle)) = guard.as_deref_mut() {
handle.send_frontend_message_to_js_rust_proxy(FrontendMessage::DisplayDialogPanic { panic_info: info.to_string() });
let mut guard = editor_handle.try_lock();
let Ok(Some(handle)) = guard.as_deref_mut() else {
return Err(panic_info);
};

handle.send_frontend_message_to_js_rust_proxy(FrontendMessage::DisplayDialogPanic { panic_info });
Ok(())
})
}

#[cfg(not(feature = "native"))]
fn send_panic_dialog_deferred(panic_info: String) {
let callback = Closure::once_into_js(move || {
if send_panic_dialog(panic_info).is_err() {
log::error!("Failed to send crash dialog after panic because the editor handle is unavailable");
}
});

let Some(window) = web_sys::window() else {
log::error!("Failed to schedule crash dialog after panic because no window exists");
return;
};

if window.set_timeout_with_callback_and_timeout_and_arguments_0(callback.unchecked_ref(), 0).is_err() {
log::error!("Failed to schedule crash dialog after panic with setTimeout");
}
}

#[cfg(feature = "native")]
fn send_panic_dialog_deferred(_panic_info: String) {
// Native builds do not use `setTimeout`, so just log the failure in the caller's context.
}

#[wasm_bindgen]
Expand Down
Loading