Desktop: Buffer web messages until connection is initialized (#3082)
Some checks are pending
Editor: Dev & CI / build (push) Waiting to run
Editor: Dev & CI / cargo-deny (push) Waiting to run

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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 74 additions and 14 deletions

View file

@ -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<Vec<u8>>,
}
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<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 {
@ -215,6 +227,12 @@ impl ApplicationHandler<CustomEvent> 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) => {

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
/// [`_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");

View file

@ -32,6 +32,10 @@ impl<H: CefEventHandler> ImplClient for BrowserProcessClientImpl<H> {
) -> ::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,

View file

@ -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 {

View file

@ -21,8 +21,11 @@ impl ImplV8Handler for BrowserProcessV8HandlerImpl {
_retval: Option<&mut Option<V8Value>>,
_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
}

View file

@ -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<MessageType> 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<String> for MessageType {
type Error = ();
fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() {
"initialized" => Ok(MessageType::Initialized),
"send_to_js" => Ok(MessageType::SendToJS),
"send_to_native" => Ok(MessageType::SendToNative),
_ => Err(()),

View file

@ -21,6 +21,7 @@ use graphite_desktop_wrapper::{NodeGraphExecutionResult, WgpuContext};
pub(crate) enum CustomEvent {
UiUpdate(wgpu::Texture),
ScheduleBrowserWork(Instant),
WebCommunicationInitialized,
DesktopWrapperMessage(DesktopWrapperMessage),
NodeGraphExecutionResult(NodeGraphExecutionResult),
}

View file

@ -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,

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) {
let global = js_sys::global();