@@ -24,6 +24,7 @@ thread_local! {
2424 pub static EDITOR : Mutex <Option <editor:: application:: Editor >> = const { Mutex :: new( None ) } ;
2525 pub static MESSAGE_BUFFER : std:: cell:: RefCell <Vec <Message >> = const { std:: cell:: RefCell :: new( Vec :: new( ) ) } ;
2626 pub static EDITOR_HANDLE : Mutex <Option <editor_api:: EditorHandle >> = const { Mutex :: new( None ) } ;
27+ pub static PANIC_DIALOG_MESSAGE_CALLBACK : std:: cell:: RefCell <Option <js_sys:: Function >> = const { std:: cell:: RefCell :: new( None ) } ;
2728}
2829
2930/// Initialize the backend
@@ -72,12 +73,65 @@ pub fn panic_hook(info: &panic::PanicHookInfo) {
7273
7374 log:: error!( "{info}" ) ;
7475
75- EDITOR_HANDLE . with ( |editor_handle| {
76- let mut guard = editor_handle. lock ( ) ;
77- if let Ok ( Some ( handle) ) = guard. as_deref_mut ( ) {
78- handle. send_frontend_message_to_js_rust_proxy ( FrontendMessage :: DisplayDialogPanic { panic_info : info. to_string ( ) } ) ;
76+ // Prefer using the raw JS callback to avoid mutex lock contention inside the panic hook.
77+ if let Err ( info) = send_panic_dialog_via_callback ( info) {
78+ send_panic_dialog_deferred ( info) ;
79+ }
80+ }
81+
82+ fn send_panic_dialog_via_callback ( panic_info : String ) -> Result < ( ) , String > {
83+ let message = FrontendMessage :: DisplayDialogPanic { panic_info } ;
84+ let message_type = message. to_discriminant ( ) . local_name ( ) ;
85+ let Ok ( message_data) = serde_wasm_bindgen:: to_value ( & message) else {
86+ log:: error!( "Failed to serialize crash dialog panic message" ) ;
87+ let FrontendMessage :: DisplayDialogPanic { panic_info } = message else {
88+ unreachable ! ( "Message variant changed unexpectedly" )
89+ } ;
90+ return Err ( panic_info) ;
91+ } ;
92+
93+ PANIC_DIALOG_MESSAGE_CALLBACK . with ( |callback| {
94+ let callback_ref = callback. borrow ( ) ;
95+ let Some ( callback) = callback_ref. as_ref ( ) else {
96+ let FrontendMessage :: DisplayDialogPanic { panic_info } = message else {
97+ unreachable ! ( "Message variant changed unexpectedly" )
98+ } ;
99+ return Err ( panic_info) ;
100+ } ;
101+
102+ if let Err ( error) = callback. call2 ( & JsValue :: null ( ) , & JsValue :: from ( message_type) , & message_data) {
103+ log:: error!( "Failed to send crash dialog panic message to JS: {:?}" , error) ;
104+ let FrontendMessage :: DisplayDialogPanic { panic_info } = message else {
105+ unreachable ! ( "Message variant changed unexpectedly" )
106+ } ;
107+ return Err ( panic_info) ;
108+ }
109+
110+ Ok ( ( ) )
111+ } )
112+ }
113+
114+ #[ cfg( not( feature = "native" ) ) ]
115+ fn send_panic_dialog_deferred ( panic_info : String ) {
116+ let callback = Closure :: once_into_js ( move || {
117+ if send_panic_dialog_via_callback ( panic_info) . is_err ( ) {
118+ log:: error!( "Failed to send crash dialog after panic because the editor handle is unavailable" ) ;
79119 }
80120 } ) ;
121+
122+ let Some ( window) = web_sys:: window ( ) else {
123+ log:: error!( "Failed to schedule crash dialog after panic because no window exists" ) ;
124+ return ;
125+ } ;
126+
127+ if window. set_timeout_with_callback_and_timeout_and_arguments_0 ( callback. unchecked_ref ( ) , 0 ) . is_err ( ) {
128+ log:: error!( "Failed to schedule crash dialog after panic with setTimeout" ) ;
129+ }
130+ }
131+
132+ #[ cfg( feature = "native" ) ]
133+ fn send_panic_dialog_deferred ( _panic_info : String ) {
134+ // Native builds do not use `setTimeout`, so just log the failure in the caller's context.
81135}
82136
83137#[ wasm_bindgen]
0 commit comments