diff --git a/crates/tauri-runtime-cef/src/cef_impl.rs b/crates/tauri-runtime-cef/src/cef_impl.rs index 10b98ba8b..a3ebb181d 100644 --- a/crates/tauri-runtime-cef/src/cef_impl.rs +++ b/crates/tauri-runtime-cef/src/cef_impl.rs @@ -6,17 +6,18 @@ use std::{ collections::HashMap, sync::{ atomic::{AtomicU32, Ordering}, - Arc, + mpsc::channel, + Arc, Mutex, }, }; use tauri_runtime::{ webview::{InitializationScript, PendingWebview, UriSchemeProtocol}, - window::{PendingWindow, WindowId}, - RunEvent, UserEvent, + window::{PendingWindow, WindowEvent, WindowId}, + ExitRequestedEventAction, RunEvent, UserEvent, }; use tauri_utils::html::normalize_script_for_csp; -use crate::{AppWindow, BrowserViewWrapper, CefRuntime, Message}; +use crate::{AppWindow, BrowserViewWrapper, CefRuntime, Message, WindowMessage}; mod request_handler; @@ -44,8 +45,6 @@ fn hash_script(script: &str) -> String { ) } -// Initialization scripts are now injected into HTML responses via ResourceHandler - #[derive(Clone)] pub struct Context { pub windows: Arc>>, @@ -84,15 +83,8 @@ wrap_app! { Some(AppBrowserProcessHandler::new(self.context.clone())) } - /// Called before the process starts to register custom schemes. - /// This is where we mark schemes as fetch-enabled, secure, and CORS-enabled. fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) { if let Some(registrar) = registrar { - // Standard CEF scheme options for custom protocols: - // - FETCH_ENABLED: Allows Fetch API requests - // - SECURE: Treats as secure like https (no mixed content warnings) - // - CORS_ENABLED: Allows CORS requests - // - STANDARD: Standard URL scheme behavior let scheme_options = (cef_dll_sys::cef_scheme_options_t::CEF_SCHEME_OPTION_FETCH_ENABLED as i32) | (cef_dll_sys::cef_scheme_options_t::CEF_SCHEME_OPTION_SECURE as i32) | (cef_dll_sys::cef_scheme_options_t::CEF_SCHEME_OPTION_CORS_ENABLED as i32) @@ -113,7 +105,6 @@ wrap_browser_process_handler! { } impl BrowserProcessHandler { - // The real lifespan of cef starts from `on_context_initialized`, so all the cef objects should be manipulated after that. fn on_context_initialized(&self) { (self.context.callback.borrow_mut())(RunEvent::Ready); } @@ -132,20 +123,16 @@ wrap_load_handler! { frame: Option<&mut Frame>, http_status_code: ::std::os::raw::c_int, ) { - // Only execute scripts for successful loads (200-299) if http_status_code < 200 || http_status_code >= 300 { return; } let Some(frame) = frame else { return }; - // Get the URL to check if it's a remote URL let url = frame.url(); let url_str = cef::CefString::from(&url).to_string(); let url_obj = url::Url::parse(&url_str).ok(); - // Only execute scripts for remote URLs (http/https) - // Custom schemes use HTML injection let is_remote_url = url_obj .as_ref() .map(|u| matches!(u.scheme(), "http" | "https")) @@ -157,7 +144,6 @@ wrap_load_handler! { let is_main_frame = frame.is_main() == 1; - // Filter scripts based on frame type let scripts_to_execute: Vec<_> = if is_main_frame { self.initialization_scripts.clone() } else { @@ -168,12 +154,10 @@ wrap_load_handler! { .collect() }; - // Execute each script via frame.execute_java_script for script in scripts_to_execute { let script_text = script.script.script.clone(); let script_url = format!("{}://__tauri_init_script__", url_obj.as_ref().map(|u| u.scheme()).unwrap_or("http")); - // Execute JavaScript in the frame frame.execute_java_script( Some(&cef::CefString::from(script_text.as_str())), Some(&cef::CefString::from(script_url.as_str())), @@ -191,7 +175,6 @@ wrap_client! { impl Client { fn request_handler(&self) -> Option { - // Use pre-computed script hashes (computed once at webview creation) Some(request_handler::WebRequestHandler::new( self.initialization_scripts.clone(), )) @@ -217,10 +200,9 @@ wrap_browser_view_delegate! { use cef::sys::cef_runtime_style_t; if self.use_alloy_style { - // Use Alloy style for additional webviews (multiwebview support) + // Use Alloy style for multiwebview support RuntimeStyle::from(cef_runtime_style_t::CEF_RUNTIME_STYLE_ALLOY) } else { - // Use Chrome style (default) for the first webview RuntimeStyle::from(cef_runtime_style_t::CEF_RUNTIME_STYLE_CHROME) } } @@ -228,37 +210,19 @@ wrap_browser_view_delegate! { } wrap_window_delegate! { - struct AppWindowDelegate { - initial_browser_view: Option, + struct AppWindowDelegate { + window_id: WindowId, + callback: Arc)>>>, + windows: Arc>>, } - impl ViewDelegate { - fn on_child_view_changed( - &self, - _view: Option<&mut View>, - _added: ::std::os::raw::c_int, - _child: Option<&mut View>, - ) { - // view.as_panel().map(|x| x.as_window().map(|w| w.close())); - } - } + impl ViewDelegate {} impl PanelDelegate {} impl WindowDelegate { - fn on_window_created(&self, window: Option<&mut Window>) { - if let Some(window) = window { - // If we have an initial browser view, add it - if let Some(ref browser_view) = self.initial_browser_view { - let mut view = View::from(browser_view); - window.add_child_view(Some(&mut view)); - } - window.show(); - } - } - fn on_window_destroyed(&self, _window: Option<&mut Window>) { - // TODO: send destroyed event + on_window_destroyed(self.window_id, &self.windows, &self.callback); } fn with_standard_window_buttons(&self, _window: Option<&mut Window>) -> ::std::os::raw::c_int { @@ -278,7 +242,18 @@ wrap_window_delegate! { } fn can_close(&self, _window: Option<&mut Window>) -> ::std::os::raw::c_int { - 1 + let (tx, rx) = channel(); + let event = WindowEvent::CloseRequested { signal_tx: tx }; + + send_window_event(self.window_id, &self.windows, &self.callback, event.clone()); + + let should_prevent = matches!(rx.try_recv(), Ok(true)); + + if should_prevent { + 0 + } else { + 1 + } } } } @@ -315,8 +290,6 @@ pub fn handle_message(context: &Context, message: Message) { { if let Some(browser) = browser_view_wrapper.browser_view.browser() { if let Some(host) = browser.host() { - // ShowDevTools(window_info, client, settings, inspect_element_at) - // Using None for client and default settings, inspect at (0,0) let window_info = cef::WindowInfo::default(); let settings = cef::BrowserSettings::default(); let inspect_at = cef::Point { x: 0, y: 0 }; @@ -350,6 +323,37 @@ pub fn handle_message(context: &Context, message: Message) { } } } + Message::Window { window_id, message } => match message { + WindowMessage::Close => { + on_close_requested(window_id, &context.windows, &context.callback); + } + WindowMessage::Destroy => { + on_window_close(window_id, &context.windows); + } + WindowMessage::AddEventListener(event_id, handler) => { + if let Some(app_window) = context.windows.borrow().get(&window_id) { + app_window + .window_event_listeners + .lock() + .unwrap() + .insert(event_id, handler); + } + } + }, + Message::RequestExit(code) => { + let (tx, rx) = channel(); + (context.callback.borrow_mut())(RunEvent::ExitRequested { + code: Some(code), + tx, + }); + + let recv = rx.try_recv(); + let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent)); + + if !should_prevent { + cef::quit_message_loop(); + } + } Message::Task(t) => t(), Message::UserEvent(evt) => { (context.callback.borrow_mut())(RunEvent::UserEvent(evt)); @@ -379,14 +383,12 @@ fn create_window( ) { let label = pending.label.clone(); - // Create window delegate - we'll handle webviews separately - // For windows without webviews, we use a delegate without initial browser view - let mut delegate = AppWindowDelegate::new(None); + let mut delegate = + AppWindowDelegate::::new(window_id, context.callback.clone(), context.windows.clone()); let window = window_create_top_level(Some(&mut delegate)).expect("Failed to create window"); window.show(); - // Insert window with empty webviews list context.windows.borrow_mut().insert( window_id, AppWindow { @@ -394,10 +396,10 @@ fn create_window( window, webviews: Vec::new(), content_panel: None, + window_event_listeners: Arc::new(Mutex::new(HashMap::new())), }, ); - // If a webview was provided, create it now if let Some(webview) = pending.webview { let webview_id = context.next_webview_id(); create_webview( @@ -418,6 +420,80 @@ enum WebviewKind { WindowChild, } +fn send_window_event( + window_id: WindowId, + windows: &Arc>>, + callback: &Arc)>>>, + event: WindowEvent, +) { + let windows_ref = windows.borrow(); + if let Some(w) = windows_ref.get(&window_id) { + let label = w.label.clone(); + let window_event_listeners = w.window_event_listeners.clone(); + + drop(windows_ref); + + { + let listeners = window_event_listeners.lock().unwrap(); + let handlers: Vec<_> = listeners.values().collect(); + for handler in handlers.iter() { + handler(&event); + } + } + + (callback.borrow_mut())(RunEvent::WindowEvent { label, event }); + } +} + +fn on_close_requested( + window_id: WindowId, + windows: &Arc>>, + callback: &Arc)>>>, +) { + let (tx, rx) = channel(); + let event = WindowEvent::CloseRequested { signal_tx: tx }; + + send_window_event(window_id, windows, callback, event.clone()); + + let prevent = rx.try_recv().unwrap_or_default(); + + if !prevent { + on_window_close(window_id, windows); + } +} + +fn on_window_close(window_id: WindowId, windows: &Arc>>) { + if let Some(window) = windows.borrow().get(&window_id) { + window.window.close(); + } +} + +fn on_window_destroyed( + window_id: WindowId, + windows: &Arc>>, + callback: &Arc)>>>, +) { + let event = WindowEvent::Destroyed; + send_window_event(window_id, windows, callback, event); + + let removed = windows.borrow_mut().remove(&window_id).is_some(); + + if removed { + let is_empty = windows.borrow().is_empty(); + if is_empty { + let (tx, rx) = channel(); + (callback.borrow_mut())(RunEvent::ExitRequested { code: None, tx }); + + let recv = rx.try_recv(); + let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent)); + + if !should_prevent { + cef::quit_message_loop(); + } + } + } +} + fn create_webview( kind: WebviewKind, context: &Context, @@ -425,7 +501,6 @@ fn create_webview( webview_id: u32, pending: PendingWebview>, ) { - // Get the window - return early if not found let mut windows = context.windows.borrow_mut(); let app_window = match windows.get_mut(&window_id) { Some(w) => w, @@ -459,7 +534,6 @@ fn create_webview( Option::<&mut RequestContextHandler>::None, ); if let Some(request_context) = &request_context { - // Ensure schemes are registered with proper flags (fetch-enabled, secure, etc.) for (scheme, handler) in pending.uri_scheme_protocols { let label = app_window.label.clone(); request_context.register_scheme_handler_factory( diff --git a/crates/tauri-runtime-cef/src/lib.rs b/crates/tauri-runtime-cef/src/lib.rs index 8daaccd67..ec2c454ff 100644 --- a/crates/tauri-runtime-cef/src/lib.rs +++ b/crates/tauri-runtime-cef/src/lib.rs @@ -65,10 +65,21 @@ enum Message { window_id: WindowId, webview_id: u32, }, + Window { + window_id: WindowId, + message: WindowMessage, + }, + RequestExit(i32), UserEvent(T), Noop, } +enum WindowMessage { + Close, + Destroy, + AddEventListener(WindowEventId, Box), +} + impl fmt::Debug for Message { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -79,6 +90,8 @@ impl fmt::Debug for Message { Self::OpenDevTools { .. } => write!(f, "OpenDevTools"), #[cfg(any(debug_assertions, feature = "devtools"))] Self::CloseDevTools { .. } => write!(f, "CloseDevTools"), + Self::Window { .. } => write!(f, "Window"), + Self::RequestExit(_) => write!(f, "RequestExit"), Self::UserEvent(_) => write!(f, "UserEvent"), Self::Noop => write!(f, "Noop"), } @@ -100,11 +113,15 @@ pub(crate) struct BrowserViewWrapper { pub overlay: Option, } +pub type WindowEventHandler = Box; +pub type WindowEventListeners = Arc>>; + pub(crate) struct AppWindow { pub label: String, pub window: cef::Window, pub webviews: Vec, pub content_panel: Option, // Panel container for multiwebview (similar to Electron's contentView) + pub window_event_listeners: WindowEventListeners, } #[derive(Clone)] @@ -256,7 +273,8 @@ impl RuntimeHandle for CefRuntimeHandle { } fn request_exit(&self, code: i32) -> Result<()> { - unimplemented!() + // Request exit by posting a task to quit the message loop + self.context.post_message(Message::RequestExit(code)) } /// Create a new webview window. @@ -744,7 +762,18 @@ impl WindowDispatch for CefWindowDispatcher { } fn on_window_event(&self, f: F) -> WindowEventId { - 0 + let context = self.context.clone(); + let window_id = self.window_id; + let event_id = context.cef_context.next_window_event_id(); + let handler = Box::new(f); + + // Register the listener on the main thread + let _ = context.post_message(Message::Window { + window_id, + message: WindowMessage::AddEventListener(event_id, handler), + }); + + event_id } fn scale_factor(&self) -> Result { @@ -956,11 +985,17 @@ impl WindowDispatch for CefWindowDispatcher { } fn close(&self) -> Result<()> { - Ok(()) + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::Close, + }) } fn destroy(&self) -> Result<()> { - Ok(()) + self.context.post_message(Message::Window { + window_id: self.window_id, + message: WindowMessage::Destroy, + }) } fn set_decorations(&self, decorations: bool) -> Result<()> {