Desktop: Cleaner CEF bidirectional message implementation (#2950)

* Rename CEF implementations to match the process they are called in

* Rename CEF implementations to match the process they are called in

* Implement ipc abstraction

* Call js function `receiveNativeMessage` for all SendToJS ipc messages

* Allow js to call `sendNativeMessage` for sending messages to the browser process

Co-authored-by: Adam <adamgerhant@gmail.com>
Co-authored-by: Dennis Kobert <dennis@kobert.dev>

* Fix missing safety consideration

---------

Co-authored-by: Adam <adamgerhant@gmail.com>
Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
Timon 2025-07-28 15:04:15 +02:00 committed by GitHub
parent 7bdf1670b5
commit 83d39fb320
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 469 additions and 80 deletions

View file

@ -8,7 +8,9 @@ mod context;
mod dirs;
mod input;
mod internal;
mod ipc;
mod scheme_handler;
mod utility;
pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError};
use winit::event_loop::EventLoopProxy;
@ -19,6 +21,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 receive_web_message(&self, message: &[u8]);
}
#[derive(Clone, Copy)]
@ -116,4 +119,6 @@ impl CefEventHandler for CefHandler {
fn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) {
let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time));
}
fn receive_web_message(&self, message: &[u8]) {}
}

View file

@ -7,10 +7,11 @@ use winit::event::WindowEvent;
use crate::cef::dirs::{cef_cache_dir, cef_data_dir};
use super::input::InputState;
use super::ipc::{MessageType, SendMessage};
use super::scheme_handler::{FRONTEND_DOMAIN, GRAPHITE_SCHEME};
use super::{CefEventHandler, input};
use super::internal::{AppImpl, ClientImpl, NonBrowserAppImpl, RenderHandlerImpl};
use super::internal::{BrowserProcessAppImpl, BrowserProcessClientImpl, RenderHandlerImpl, RenderProcessAppImpl};
pub(crate) struct Setup {}
pub(crate) struct Initialized {}
@ -42,7 +43,7 @@ impl Context<Setup> {
if !is_browser_process {
let process_type = CefString::from(&cmd.switch_value(Some(&switch)));
let mut app = NonBrowserAppImpl::app();
let mut app = RenderProcessAppImpl::app();
let ret = execute_process(Some(args.as_main_args()), Some(&mut app), std::ptr::null_mut());
if ret >= 0 {
return Err(SetupError::SubprocessFailed(process_type.to_string()));
@ -70,7 +71,7 @@ impl Context<Setup> {
};
// Attention! Wrapping this in an extra App is necessary, otherwise the program still compiles but segfaults
let mut cef_app = App::new(AppImpl::new(event_handler.clone()));
let mut cef_app = App::new(BrowserProcessAppImpl::new(event_handler.clone()));
let result = initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut());
if result != 1 {
@ -81,8 +82,8 @@ impl Context<Setup> {
return Err(InitError::InitializationFailed(cef_exit_code));
}
let render_handler = RenderHandlerImpl::new(event_handler.clone());
let mut client = Client::new(ClientImpl::new(RenderHandler::new(render_handler)));
let render_handler = RenderHandler::new(RenderHandlerImpl::new(event_handler.clone()));
let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, event_handler.clone()));
let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str());
@ -129,6 +130,10 @@ impl Context<Initialized> {
browser.host().unwrap().was_resized();
}
}
pub(crate) fn send_web_message(&self, message: &[u8]) {
self.send_message(MessageType::SendToJS, message);
}
}
impl<S: ContextState> Drop for Context<S> {

View file

@ -1,10 +1,12 @@
mod app;
mod browser_process_app;
mod browser_process_client;
mod browser_process_handler;
mod client;
mod non_browser_app;
mod render_handler;
mod render_process_app;
mod render_process_handler;
mod render_process_v8_handler;
pub(crate) use app::AppImpl;
pub(crate) use client::ClientImpl;
pub(crate) use non_browser_app::NonBrowserAppImpl;
pub(crate) use browser_process_app::BrowserProcessAppImpl;
pub(crate) use browser_process_client::BrowserProcessClientImpl;
pub(crate) use render_handler::RenderHandlerImpl;
pub(crate) use render_process_app::RenderProcessAppImpl;

View file

@ -5,15 +5,16 @@ use cef::sys::{_cef_app_t, cef_base_ref_counted_t};
use cef::{BrowserProcessHandler, CefString, ImplApp, ImplCommandLine, SchemeRegistrar, WrapApp};
use crate::cef::CefEventHandler;
use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory;
use super::browser_process_handler::BrowserProcessHandlerImpl;
pub(crate) struct AppImpl<H: CefEventHandler> {
pub(crate) struct BrowserProcessAppImpl<H: CefEventHandler> {
object: *mut RcImpl<_cef_app_t, Self>,
event_handler: H,
}
impl<H: CefEventHandler + Clone> AppImpl<H> {
impl<H: CefEventHandler + Clone> BrowserProcessAppImpl<H> {
pub(crate) fn new(event_handler: H) -> Self {
Self {
object: std::ptr::null_mut(),
@ -22,7 +23,7 @@ impl<H: CefEventHandler + Clone> AppImpl<H> {
}
}
impl<H: CefEventHandler + Clone> ImplApp for AppImpl<H> {
impl<H: CefEventHandler + Clone> ImplApp for BrowserProcessAppImpl<H> {
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
Some(BrowserProcessHandler::new(BrowserProcessHandlerImpl::new(self.event_handler.clone())))
}
@ -58,7 +59,7 @@ impl<H: CefEventHandler + Clone> ImplApp for AppImpl<H> {
}
}
impl<H: CefEventHandler + Clone> Clone for AppImpl<H> {
impl<H: CefEventHandler + Clone> Clone for BrowserProcessAppImpl<H> {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
@ -70,7 +71,7 @@ impl<H: CefEventHandler + Clone> Clone for AppImpl<H> {
}
}
}
impl<H: CefEventHandler> Rc for AppImpl<H> {
impl<H: CefEventHandler> Rc for BrowserProcessAppImpl<H> {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
@ -78,7 +79,7 @@ impl<H: CefEventHandler> Rc for AppImpl<H> {
}
}
}
impl<H: CefEventHandler + Clone> WrapApp for AppImpl<H> {
impl<H: CefEventHandler + Clone> WrapApp for BrowserProcessAppImpl<H> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
self.object = object;
}

View file

@ -0,0 +1,80 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_client_t, cef_base_ref_counted_t};
use cef::{ImplClient, RenderHandler, WrapClient};
use crate::cef::CefEventHandler;
use crate::cef::ipc::{MessageType, UnpackMessage, UnpackedMessage};
pub(crate) struct BrowserProcessClientImpl<H: CefEventHandler> {
object: *mut RcImpl<_cef_client_t, Self>,
render_handler: RenderHandler,
event_handler: H,
}
impl<H: CefEventHandler> BrowserProcessClientImpl<H> {
pub(crate) fn new(render_handler: RenderHandler, event_handler: H) -> Self {
Self {
object: std::ptr::null_mut(),
render_handler,
event_handler,
}
}
}
impl<H: CefEventHandler> ImplClient for BrowserProcessClientImpl<H> {
fn on_process_message_received(
&self,
_browser: Option<&mut cef::Browser>,
_frame: Option<&mut cef::Frame>,
_source_process: cef::ProcessId,
message: Option<&mut cef::ProcessMessage>,
) -> ::std::os::raw::c_int {
let unpacked_message = unsafe { message.and_then(|m| m.unpack()) };
match unpacked_message {
Some(UnpackedMessage {
message_type: MessageType::SendToNative,
data,
}) => self.event_handler.receive_web_message(data),
_ => {
tracing::error!("Unexpected message type received in browser process");
return 0;
}
}
1
}
fn render_handler(&self) -> Option<RenderHandler> {
Some(self.render_handler.clone())
}
fn get_raw(&self) -> *mut _cef_client_t {
self.object.cast()
}
}
impl<H: CefEventHandler> Clone for BrowserProcessClientImpl<H> {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
render_handler: self.render_handler.clone(),
event_handler: self.event_handler.clone(),
}
}
}
impl<H: CefEventHandler> Rc for BrowserProcessClientImpl<H> {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl<H: CefEventHandler> WrapClient for BrowserProcessClientImpl<H> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_client_t, Self>) {
self.object = object;
}
}

View file

@ -1,52 +0,0 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_client_t, cef_base_ref_counted_t};
use cef::{ImplClient, RenderHandler, WrapClient};
pub(crate) struct ClientImpl {
object: *mut RcImpl<_cef_client_t, Self>,
render_handler: RenderHandler,
}
impl ClientImpl {
pub(crate) fn new(render_handler: RenderHandler) -> Self {
Self {
object: std::ptr::null_mut(),
render_handler,
}
}
}
impl ImplClient for ClientImpl {
fn render_handler(&self) -> Option<RenderHandler> {
Some(self.render_handler.clone())
}
fn get_raw(&self) -> *mut _cef_client_t {
self.object.cast()
}
}
impl Clone for ClientImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
render_handler: self.render_handler.clone(),
}
}
}
impl Rc for ClientImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapClient for ClientImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_client_t, Self>) {
self.object = object;
}
}

View file

@ -1,38 +1,50 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_app_t, cef_base_ref_counted_t};
use cef::{App, ImplApp, SchemeRegistrar, WrapApp};
use cef::{App, ImplApp, RenderProcessHandler, SchemeRegistrar, WrapApp};
use super::render_process_handler::RenderProcessHandlerImpl;
use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory;
pub(crate) struct NonBrowserAppImpl {
pub(crate) struct RenderProcessAppImpl {
object: *mut RcImpl<_cef_app_t, Self>,
render_process_handler: RenderProcessHandler,
}
impl NonBrowserAppImpl {
impl RenderProcessAppImpl {
pub(crate) fn app() -> App {
App::new(Self { object: std::ptr::null_mut() })
App::new(Self {
object: std::ptr::null_mut(),
render_process_handler: RenderProcessHandler::new(RenderProcessHandlerImpl::new()),
})
}
}
impl ImplApp for NonBrowserAppImpl {
impl ImplApp for RenderProcessAppImpl {
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
GraphiteSchemeHandlerFactory::register_schemes(registrar);
}
fn render_process_handler(&self) -> Option<RenderProcessHandler> {
Some(self.render_process_handler.clone())
}
fn get_raw(&self) -> *mut _cef_app_t {
self.object.cast()
}
}
impl Clone for NonBrowserAppImpl {
impl Clone for RenderProcessAppImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
Self {
object: self.object,
render_process_handler: self.render_process_handler.clone(),
}
}
}
impl Rc for NonBrowserAppImpl {
impl Rc for RenderProcessAppImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
@ -40,7 +52,7 @@ impl Rc for NonBrowserAppImpl {
}
}
}
impl WrapApp for NonBrowserAppImpl {
impl WrapApp for RenderProcessAppImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
self.object = object;
}

View file

@ -0,0 +1,126 @@
use cef::rc::{ConvertReturnValue, Rc, RcImpl};
use cef::sys::{_cef_render_process_handler_t, cef_base_ref_counted_t, cef_render_process_handler_t, cef_v8_propertyattribute_t, cef_v8_value_create_array_buffer_with_copy};
use cef::{
CefString, ImplFrame, ImplRenderProcessHandler, ImplV8Context, ImplV8Value, V8Handler, V8Propertyattribute, V8Value, WrapRenderProcessHandler, v8_context_get_entered_context,
v8_value_create_function,
};
use crate::cef::ipc::{MessageType, UnpackMessage, UnpackedMessage};
use super::render_process_v8_handler::BrowserProcessV8HandlerImpl;
pub(crate) struct RenderProcessHandlerImpl {
object: *mut RcImpl<cef_render_process_handler_t, Self>,
}
impl RenderProcessHandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplRenderProcessHandler for RenderProcessHandlerImpl {
fn on_process_message_received(
&self,
_browser: Option<&mut cef::Browser>,
frame: Option<&mut cef::Frame>,
_source_process: cef::ProcessId,
message: Option<&mut cef::ProcessMessage>,
) -> ::std::os::raw::c_int {
let unpacked_message = unsafe { message.and_then(|m| m.unpack()) };
match unpacked_message {
Some(UnpackedMessage {
message_type: MessageType::SendToJS,
data,
}) => {
let Some(frame) = frame else {
tracing::error!("Frame is not available");
return 0;
};
let Some(context) = frame.v8_context() else {
tracing::error!("V8 context is not available");
return 0;
};
if context.enter() == 0 {
tracing::error!("Failed to enter V8 context");
return 0;
}
let mut value: V8Value = unsafe { cef_v8_value_create_array_buffer_with_copy(data.as_ptr() as *mut std::ffi::c_void, data.len()) }.wrap_result();
let Some(global) = context.global() else {
tracing::error!("Global object is not available in V8 context");
return 0;
};
let function_name = "receiveNativeMessage";
let property_name = "receiveNativeMessageData";
let function_call = format!("window.{function_name}(window.{property_name})");
global.set_value_bykey(
Some(&CefString::from(property_name)),
Some(&mut value),
cef_v8_propertyattribute_t::V8_PROPERTY_ATTRIBUTE_READONLY.wrap_result(),
);
frame.execute_java_script(Some(&CefString::from(function_call.as_str())), None, 0);
if context.exit() == 0 {
tracing::error!("Failed to exit V8 context");
return 0;
}
}
_ => {
tracing::error!("Unexpected message type received in render process");
return 0;
}
}
1
}
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 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 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());
}
fn get_raw(&self) -> *mut _cef_render_process_handler_t {
self.object.cast()
}
}
impl Clone for RenderProcessHandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for RenderProcessHandlerImpl {
fn as_base(&self) -> &cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapRenderProcessHandler for RenderProcessHandlerImpl {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_process_handler_t, Self>) {
self.object = object;
}
}

View file

@ -0,0 +1,83 @@
use cef::{ImplV8Handler, ImplV8Value, V8Value, WrapV8Handler, rc::Rc, v8_context_get_current_context};
use crate::cef::ipc::{MessageType, SendMessage};
pub struct BrowserProcessV8HandlerImpl {
object: *mut cef::rc::RcImpl<cef::sys::_cef_v8_handler_t, Self>,
}
impl BrowserProcessV8HandlerImpl {
pub(crate) fn new() -> Self {
Self { object: std::ptr::null_mut() }
}
}
impl ImplV8Handler for BrowserProcessV8HandlerImpl {
fn execute(
&self,
name: Option<&cef::CefString>,
_object: Option<&mut V8Value>,
arguments: Option<&[Option<V8Value>]>,
_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" {
let Some(args) = arguments else {
tracing::error!("No arguments provided to sendNativeMessage");
return 0;
};
let Some(arg1) = args.first() else {
tracing::error!("No arguments provided to sendNativeMessage");
return 0;
};
let Some(arg1) = arg1.as_ref() else {
tracing::error!("First argument to sendNativeMessage is not an ArrayBuffer");
return 0;
};
if arg1.is_array_buffer() == 0 {
tracing::error!("First argument to sendNativeMessage is not an ArrayBuffer");
return 0;
}
let size = arg1.array_buffer_byte_length();
let ptr = arg1.array_buffer_data();
let data = unsafe { std::slice::from_raw_parts_mut(ptr as *mut u8, size) };
v8_context_get_current_context().send_message(MessageType::SendToNative, data);
return 1;
}
}
1
}
fn get_raw(&self) -> *mut cef::sys::_cef_v8_handler_t {
self.object.cast()
}
}
impl Clone for BrowserProcessV8HandlerImpl {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self { object: self.object }
}
}
impl Rc for BrowserProcessV8HandlerImpl {
fn as_base(&self) -> &cef::sys::cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapV8Handler for BrowserProcessV8HandlerImpl {
fn wrap_rc(&mut self, object: *mut cef::rc::RcImpl<cef::sys::_cef_v8_handler_t, Self>) {
self.object = object;
}
}

123
desktop/src/cef/ipc.rs Normal file
View file

@ -0,0 +1,123 @@
use cef::{CefString, Frame, ImplBinaryValue, ImplBrowser, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, rc::ConvertParam, sys::cef_process_id_t};
use super::{Context, Initialized};
pub(crate) enum MessageType {
SendToJS,
SendToNative,
}
impl From<MessageType> for MessageInfo {
fn from(val: MessageType) -> Self {
match val {
MessageType::SendToJS => MessageInfo {
name: "send_to_js".to_string(),
target: cef_process_id_t::PID_RENDERER.into(),
},
MessageType::SendToNative => MessageInfo {
name: "send_to_native".to_string(),
target: cef_process_id_t::PID_BROWSER.into(),
},
}
}
}
impl TryFrom<String> for MessageType {
type Error = ();
fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() {
"send_to_js" => Ok(MessageType::SendToJS),
"send_to_native" => Ok(MessageType::SendToNative),
_ => Err(()),
}
}
}
pub(crate) struct MessageInfo {
name: String,
target: ProcessId,
}
pub(crate) trait SendMessage {
fn send_message(&self, message_type: MessageType, message: &[u8]);
}
impl SendMessage for Context<Initialized> {
fn send_message(&self, message_type: MessageType, message: &[u8]) {
let Some(browser) = &self.browser else {
tracing::error!("Browser is not initialized, cannot send message");
return;
};
let Some(frame) = browser.main_frame() else {
tracing::error!("Main frame is not available, cannot send message");
return;
};
frame.send_message(message_type, message);
}
}
impl SendMessage for Option<V8Context> {
fn send_message(&self, message_type: MessageType, message: &[u8]) {
let Some(context) = self else {
tracing::error!("Current V8 context is not available, cannot send message");
return;
};
context.send_message(message_type, message);
}
}
impl SendMessage for V8Context {
fn send_message(&self, message_type: MessageType, message: &[u8]) {
let Some(frame) = self.frame() else {
tracing::error!("Current V8 context does not have a frame, cannot send message");
return;
};
frame.send_message(message_type, message);
}
}
impl SendMessage for Frame {
fn send_message(&self, message_type: MessageType, message: &[u8]) {
let MessageInfo { name, target } = message_type.into();
let Some(mut process_message) = cef::process_message_create(Some(&CefString::from(name.as_str()))) else {
tracing::error!("Failed to create process message: {}", name);
return;
};
let Some(arg_list) = process_message.argument_list() else { return };
let mut value = ::cef::binary_value_create(Some(message));
arg_list.set_binary(0, value.as_mut());
self.send_process_message(target, Some(&mut process_message));
}
}
pub(crate) struct UnpackedMessage<'a> {
pub(crate) message_type: MessageType,
pub(crate) data: &'a [u8],
}
trait Sealed {}
impl Sealed for cef::ProcessMessage {}
#[allow(private_bounds)]
pub(crate) trait UnpackMessage: Sealed {
/// # Safety
///
/// The caller must ensure that the message is valid.
/// Message should come from cef.
unsafe fn unpack(&self) -> Option<UnpackedMessage<'_>>;
}
impl UnpackMessage for cef::ProcessMessage {
unsafe fn unpack(&self) -> Option<UnpackedMessage<'_>> {
let pointer: *mut cef::sys::_cef_string_utf16_t = self.name().into();
let message = unsafe { super::utility::pointer_to_string(pointer) };
let Ok(message_type) = message.try_into() else {
tracing::error!("Failed to get message type from process message");
return None;
};
let arglist = self.argument_list()?;
let binary = arglist.binary(0)?;
let size = binary.size();
let ptr = binary.raw_data();
let buffer = unsafe { std::slice::from_raw_parts(ptr as *const u8, size) };
Some(UnpackedMessage { message_type, data: buffer })
}
}

View file

@ -0,0 +1,6 @@
pub unsafe fn pointer_to_string(pointer: *mut cef::sys::_cef_string_utf16_t) -> String {
let str = unsafe { (*pointer).str_ };
let len = unsafe { (*pointer).length };
let slice = unsafe { std::slice::from_raw_parts(str, len) };
String::from_utf16(slice).unwrap()
}

View file

@ -51,8 +51,6 @@ fn main() {
}
};
tracing::info!("Cef initialized successfully");
let mut winit_app = WinitApp::new(cef_context, window_size_sender, wgpu_context);
event_loop.run_app(&mut winit_app).unwrap();