diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 2c77b68c..a5573a5c 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -35,6 +35,8 @@ pub(crate) struct WinitApp { last_ui_update: Instant, avg_frame_time: f32, start_render_sender: SyncSender<()>, + web_communication_initialized: bool, + web_communication_startup_buffer: Vec>, } impl WinitApp { @@ -61,6 +63,8 @@ impl WinitApp { last_ui_update: Instant::now(), avg_frame_time: 0., start_render_sender, + web_communication_initialized: false, + web_communication_startup_buffer: Vec::new(), } } @@ -71,7 +75,7 @@ impl WinitApp { tracing::error!("Failed to serialize frontend messages"); return; }; - self.cef_context.send_web_message(bytes); + self.send_or_queue_web_message(bytes); } DesktopFrontendMessage::OpenFileDialog { title, filters, context } => { let event_loop_proxy = self.event_loop_proxy.clone(); @@ -161,6 +165,14 @@ impl WinitApp { let responses = self.desktop_wrapper.dispatch(message); self.handle_desktop_frontend_messages(responses); } + + fn send_or_queue_web_message(&mut self, message: Vec) { + if self.web_communication_initialized { + self.cef_context.send_web_message(message); + } else { + self.web_communication_startup_buffer.push(message); + } + } } impl ApplicationHandler for WinitApp { @@ -215,6 +227,12 @@ impl ApplicationHandler for WinitApp { fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) { match event { + CustomEvent::WebCommunicationInitialized => { + self.web_communication_initialized = true; + for message in self.web_communication_startup_buffer.drain(..) { + self.cef_context.send_web_message(message); + } + } CustomEvent::DesktopWrapperMessage(message) => self.dispatch_desktop_wrapper_message(message), CustomEvent::NodeGraphExecutionResult(result) => match result { NodeGraphExecutionResult::HasRun(texture) => { diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index 6424fa59..11c83e06 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -46,6 +46,7 @@ pub(crate) trait CefEventHandler: Clone { /// Scheudule the main event loop to run the cef event loop after the timeout /// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation. fn schedule_cef_message_loop_work(&self, scheduled_time: Instant); + fn initialized_web_communication(&self); fn receive_web_message(&self, message: &[u8]); } @@ -145,6 +146,10 @@ impl CefEventHandler for CefHandler { let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time)); } + fn initialized_web_communication(&self) { + let _ = self.event_loop_proxy.send_event(CustomEvent::WebCommunicationInitialized); + } + fn receive_web_message(&self, message: &[u8]) { let Some(desktop_wrapper_message) = deserialize_editor_message(message) else { tracing::error!("Failed to deserialize web message"); diff --git a/desktop/src/cef/internal/browser_process_client.rs b/desktop/src/cef/internal/browser_process_client.rs index d5bd70ed..1ed5eeb1 100644 --- a/desktop/src/cef/internal/browser_process_client.rs +++ b/desktop/src/cef/internal/browser_process_client.rs @@ -32,6 +32,10 @@ impl ImplClient for BrowserProcessClientImpl { ) -> ::std::os::raw::c_int { let unpacked_message = unsafe { message.and_then(|m| m.unpack()) }; match unpacked_message { + Some(UnpackedMessage { + message_type: MessageType::Initialized, + data: _, + }) => self.event_handler.initialized_web_communication(), Some(UnpackedMessage { message_type: MessageType::SendToNative, data, diff --git a/desktop/src/cef/internal/render_process_handler.rs b/desktop/src/cef/internal/render_process_handler.rs index 62917a8b..78feadc1 100644 --- a/desktop/src/cef/internal/render_process_handler.rs +++ b/desktop/src/cef/internal/render_process_handler.rs @@ -76,24 +76,30 @@ impl ImplRenderProcessHandler for RenderProcessHandlerImpl { } fn on_context_created(&self, _browser: Option<&mut cef::Browser>, _frame: Option<&mut cef::Frame>, context: Option<&mut cef::V8Context>) { - let function_name = "sendNativeMessage"; + let register_js_function = |context: &mut cef::V8Context, name: &'static str| { + let mut v8_handler = V8Handler::new(BrowserProcessV8HandlerImpl::new()); + let Some(mut function) = v8_value_create_function(Some(&CefString::from(name)), Some(&mut v8_handler)) else { + tracing::error!("Failed to create V8 function {name}"); + return; + }; + + let Some(global) = context.global() else { + tracing::error!("Global object is not available in V8 context"); + return; + }; + global.set_value_bykey(Some(&CefString::from(name)), Some(&mut function), V8Propertyattribute::default()); + }; let Some(context) = context else { tracing::error!("V8 context is not available"); return; }; - let mut v8_handler = V8Handler::new(BrowserProcessV8HandlerImpl::new()); - let Some(mut function) = v8_value_create_function(Some(&CefString::from(function_name)), Some(&mut v8_handler)) else { - tracing::error!("Failed to create V8 function {function_name}"); - return; - }; + let initialized_function_name = "initializeNativeCommunication"; + let send_function_name = "sendNativeMessage"; - let Some(global) = context.global() else { - tracing::error!("Global object is not available in V8 context"); - return; - }; - global.set_value_bykey(Some(&CefString::from(function_name)), Some(&mut function), V8Propertyattribute::default()); + register_js_function(context, initialized_function_name); + register_js_function(context, send_function_name); } fn get_raw(&self) -> *mut _cef_render_process_handler_t { diff --git a/desktop/src/cef/internal/render_process_v8_handler.rs b/desktop/src/cef/internal/render_process_v8_handler.rs index e742ac9d..76cb136b 100644 --- a/desktop/src/cef/internal/render_process_v8_handler.rs +++ b/desktop/src/cef/internal/render_process_v8_handler.rs @@ -21,8 +21,11 @@ impl ImplV8Handler for BrowserProcessV8HandlerImpl { _retval: Option<&mut Option>, _exception: Option<&mut cef::CefString>, ) -> ::std::os::raw::c_int { - if let Some(name) = name { - if name.to_string() == "sendNativeMessage" { + match name.map(|s| s.to_string()).unwrap_or_default().as_str() { + "initializeNativeCommunication" => { + v8_context_get_current_context().send_message(MessageType::Initialized, vec![0u8].as_slice()); + } + "sendNativeMessage" => { let Some(args) = arguments else { tracing::error!("No arguments provided to sendNativeMessage"); return 0; @@ -48,6 +51,9 @@ impl ImplV8Handler for BrowserProcessV8HandlerImpl { return 1; } + name => { + tracing::error!("Unknown V8 function called: {}", name); + } } 1 } diff --git a/desktop/src/cef/ipc.rs b/desktop/src/cef/ipc.rs index 8d2080fd..b1a27741 100644 --- a/desktop/src/cef/ipc.rs +++ b/desktop/src/cef/ipc.rs @@ -1,12 +1,17 @@ use cef::{CefString, Frame, ImplBinaryValue, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t}; pub(crate) enum MessageType { + Initialized, SendToJS, SendToNative, } impl From for MessageInfo { fn from(val: MessageType) -> Self { match val { + MessageType::Initialized => MessageInfo { + name: "initialized".to_string(), + target: cef_process_id_t::PID_BROWSER.into(), + }, MessageType::SendToJS => MessageInfo { name: "send_to_js".to_string(), target: cef_process_id_t::PID_RENDERER.into(), @@ -22,6 +27,7 @@ impl TryFrom for MessageType { type Error = (); fn try_from(value: String) -> Result { match value.as_str() { + "initialized" => Ok(MessageType::Initialized), "send_to_js" => Ok(MessageType::SendToJS), "send_to_native" => Ok(MessageType::SendToNative), _ => Err(()), diff --git a/desktop/src/main.rs b/desktop/src/main.rs index bc86f1d8..40234a09 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -21,6 +21,7 @@ use graphite_desktop_wrapper::{NodeGraphExecutionResult, WgpuContext}; pub(crate) enum CustomEvent { UiUpdate(wgpu::Texture), ScheduleBrowserWork(Instant), + WebCommunicationInitialized, DesktopWrapperMessage(DesktopWrapperMessage), NodeGraphExecutionResult(NodeGraphExecutionResult), } diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 86dae3a9..c6d66589 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -242,6 +242,9 @@ impl EditorHandle { #[wasm_bindgen(js_name = initAfterFrontendReady)] pub fn init_after_frontend_ready(&self, platform: String) { + #[cfg(feature = "native")] + crate::native_communcation::initialize_native_communication(); + // Send initialization messages let platform = match platform.as_str() { "Windows" => Platform::Windows, diff --git a/frontend/wasm/src/native_communcation.rs b/frontend/wasm/src/native_communcation.rs index 3b843371..97cfd486 100644 --- a/frontend/wasm/src/native_communcation.rs +++ b/frontend/wasm/src/native_communcation.rs @@ -20,6 +20,17 @@ pub fn receive_native_message(buffer: ArrayBuffer) { } } +pub fn initialize_native_communication() { + let global = js_sys::global(); + + // Get the function by name + let func = js_sys::Reflect::get(&global, &JsValue::from_str("initializeNativeCommunication")).expect("Function not found"); + let func = func.dyn_into::().expect("Not a function"); + + // Call it + func.call0(&JsValue::NULL).expect("Function call failed"); +} + pub fn send_message_to_cef(message: String) { let global = js_sys::global();