Desktop: Buffer web messages until connection is initialized (#3082)

Buffer web messages until connection is initialized
This commit is contained in:
Timon
2025-08-22 15:15:17 +00:00
committed by GitHub
parent a4ec50d8ba
commit c6ec3a27ca
9 changed files with 74 additions and 14 deletions
+19 -1
View File
@@ -35,6 +35,8 @@ pub(crate) struct WinitApp {
last_ui_update: Instant, last_ui_update: Instant,
avg_frame_time: f32, avg_frame_time: f32,
start_render_sender: SyncSender<()>, start_render_sender: SyncSender<()>,
web_communication_initialized: bool,
web_communication_startup_buffer: Vec<Vec<u8>>,
} }
impl WinitApp { impl WinitApp {
@@ -61,6 +63,8 @@ impl WinitApp {
last_ui_update: Instant::now(), last_ui_update: Instant::now(),
avg_frame_time: 0., avg_frame_time: 0.,
start_render_sender, 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"); tracing::error!("Failed to serialize frontend messages");
return; return;
}; };
self.cef_context.send_web_message(bytes); self.send_or_queue_web_message(bytes);
} }
DesktopFrontendMessage::OpenFileDialog { title, filters, context } => { DesktopFrontendMessage::OpenFileDialog { title, filters, context } => {
let event_loop_proxy = self.event_loop_proxy.clone(); let event_loop_proxy = self.event_loop_proxy.clone();
@@ -161,6 +165,14 @@ impl WinitApp {
let responses = self.desktop_wrapper.dispatch(message); let responses = self.desktop_wrapper.dispatch(message);
self.handle_desktop_frontend_messages(responses); self.handle_desktop_frontend_messages(responses);
} }
fn send_or_queue_web_message(&mut self, message: Vec<u8>) {
if self.web_communication_initialized {
self.cef_context.send_web_message(message);
} else {
self.web_communication_startup_buffer.push(message);
}
}
} }
impl ApplicationHandler<CustomEvent> for WinitApp { impl ApplicationHandler<CustomEvent> for WinitApp {
@@ -215,6 +227,12 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) { fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) {
match event { 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::DesktopWrapperMessage(message) => self.dispatch_desktop_wrapper_message(message),
CustomEvent::NodeGraphExecutionResult(result) => match result { CustomEvent::NodeGraphExecutionResult(result) => match result {
NodeGraphExecutionResult::HasRun(texture) => { NodeGraphExecutionResult::HasRun(texture) => {
+5
View File
@@ -46,6 +46,7 @@ pub(crate) trait CefEventHandler: Clone {
/// Scheudule the main event loop to run the cef event loop after the timeout /// 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. /// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation.
fn schedule_cef_message_loop_work(&self, scheduled_time: Instant); fn schedule_cef_message_loop_work(&self, scheduled_time: Instant);
fn initialized_web_communication(&self);
fn receive_web_message(&self, message: &[u8]); 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)); 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]) { fn receive_web_message(&self, message: &[u8]) {
let Some(desktop_wrapper_message) = deserialize_editor_message(message) else { let Some(desktop_wrapper_message) = deserialize_editor_message(message) else {
tracing::error!("Failed to deserialize web message"); tracing::error!("Failed to deserialize web message");
@@ -32,6 +32,10 @@ impl<H: CefEventHandler> ImplClient for BrowserProcessClientImpl<H> {
) -> ::std::os::raw::c_int { ) -> ::std::os::raw::c_int {
let unpacked_message = unsafe { message.and_then(|m| m.unpack()) }; let unpacked_message = unsafe { message.and_then(|m| m.unpack()) };
match unpacked_message { match unpacked_message {
Some(UnpackedMessage {
message_type: MessageType::Initialized,
data: _,
}) => self.event_handler.initialized_web_communication(),
Some(UnpackedMessage { Some(UnpackedMessage {
message_type: MessageType::SendToNative, message_type: MessageType::SendToNative,
data, data,
@@ -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>) { 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 { let Some(context) = context else {
tracing::error!("V8 context is not available"); tracing::error!("V8 context is not available");
return; return;
}; };
let mut v8_handler = V8Handler::new(BrowserProcessV8HandlerImpl::new()); let initialized_function_name = "initializeNativeCommunication";
let Some(mut function) = v8_value_create_function(Some(&CefString::from(function_name)), Some(&mut v8_handler)) else { let send_function_name = "sendNativeMessage";
tracing::error!("Failed to create V8 function {function_name}");
return;
};
let Some(global) = context.global() else { register_js_function(context, initialized_function_name);
tracing::error!("Global object is not available in V8 context"); register_js_function(context, send_function_name);
return;
};
global.set_value_bykey(Some(&CefString::from(function_name)), Some(&mut function), V8Propertyattribute::default());
} }
fn get_raw(&self) -> *mut _cef_render_process_handler_t { fn get_raw(&self) -> *mut _cef_render_process_handler_t {
@@ -21,8 +21,11 @@ impl ImplV8Handler for BrowserProcessV8HandlerImpl {
_retval: Option<&mut Option<V8Value>>, _retval: Option<&mut Option<V8Value>>,
_exception: Option<&mut cef::CefString>, _exception: Option<&mut cef::CefString>,
) -> ::std::os::raw::c_int { ) -> ::std::os::raw::c_int {
if let Some(name) = name { match name.map(|s| s.to_string()).unwrap_or_default().as_str() {
if name.to_string() == "sendNativeMessage" { "initializeNativeCommunication" => {
v8_context_get_current_context().send_message(MessageType::Initialized, vec![0u8].as_slice());
}
"sendNativeMessage" => {
let Some(args) = arguments else { let Some(args) = arguments else {
tracing::error!("No arguments provided to sendNativeMessage"); tracing::error!("No arguments provided to sendNativeMessage");
return 0; return 0;
@@ -48,6 +51,9 @@ impl ImplV8Handler for BrowserProcessV8HandlerImpl {
return 1; return 1;
} }
name => {
tracing::error!("Unknown V8 function called: {}", name);
}
} }
1 1
} }
+6
View File
@@ -1,12 +1,17 @@
use cef::{CefString, Frame, ImplBinaryValue, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t}; use cef::{CefString, Frame, ImplBinaryValue, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t};
pub(crate) enum MessageType { pub(crate) enum MessageType {
Initialized,
SendToJS, SendToJS,
SendToNative, SendToNative,
} }
impl From<MessageType> for MessageInfo { impl From<MessageType> for MessageInfo {
fn from(val: MessageType) -> Self { fn from(val: MessageType) -> Self {
match val { match val {
MessageType::Initialized => MessageInfo {
name: "initialized".to_string(),
target: cef_process_id_t::PID_BROWSER.into(),
},
MessageType::SendToJS => MessageInfo { MessageType::SendToJS => MessageInfo {
name: "send_to_js".to_string(), name: "send_to_js".to_string(),
target: cef_process_id_t::PID_RENDERER.into(), target: cef_process_id_t::PID_RENDERER.into(),
@@ -22,6 +27,7 @@ impl TryFrom<String> for MessageType {
type Error = (); type Error = ();
fn try_from(value: String) -> Result<Self, Self::Error> { fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() { match value.as_str() {
"initialized" => Ok(MessageType::Initialized),
"send_to_js" => Ok(MessageType::SendToJS), "send_to_js" => Ok(MessageType::SendToJS),
"send_to_native" => Ok(MessageType::SendToNative), "send_to_native" => Ok(MessageType::SendToNative),
_ => Err(()), _ => Err(()),
+1
View File
@@ -21,6 +21,7 @@ use graphite_desktop_wrapper::{NodeGraphExecutionResult, WgpuContext};
pub(crate) enum CustomEvent { pub(crate) enum CustomEvent {
UiUpdate(wgpu::Texture), UiUpdate(wgpu::Texture),
ScheduleBrowserWork(Instant), ScheduleBrowserWork(Instant),
WebCommunicationInitialized,
DesktopWrapperMessage(DesktopWrapperMessage), DesktopWrapperMessage(DesktopWrapperMessage),
NodeGraphExecutionResult(NodeGraphExecutionResult), NodeGraphExecutionResult(NodeGraphExecutionResult),
} }
+3
View File
@@ -242,6 +242,9 @@ impl EditorHandle {
#[wasm_bindgen(js_name = initAfterFrontendReady)] #[wasm_bindgen(js_name = initAfterFrontendReady)]
pub fn init_after_frontend_ready(&self, platform: String) { pub fn init_after_frontend_ready(&self, platform: String) {
#[cfg(feature = "native")]
crate::native_communcation::initialize_native_communication();
// Send initialization messages // Send initialization messages
let platform = match platform.as_str() { let platform = match platform.as_str() {
"Windows" => Platform::Windows, "Windows" => Platform::Windows,
+11
View File
@@ -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::<js_sys::Function>().expect("Not a function");
// Call it
func.call0(&JsValue::NULL).expect("Function call failed");
}
pub fn send_message_to_cef(message: String) { pub fn send_message_to_cef(message: String) {
let global = js_sys::global(); let global = js_sys::global();