diff --git a/.changes/web-content-process-termination.md b/.changes/web-content-process-termination.md new file mode 100644 index 000000000000..c10fc18cdb69 --- /dev/null +++ b/.changes/web-content-process-termination.md @@ -0,0 +1,7 @@ +--- +"tauri": "minor:feat" +"tauri-runtime": "minor:feat" +"tauri-runtime-wry": "minor:feat" +--- + +Add handler for web content process termination on macOS and iOS. diff --git a/crates/tauri-runtime-wry/src/lib.rs b/crates/tauri-runtime-wry/src/lib.rs index 61799d0394c9..5e8b1f8f7c72 100644 --- a/crates/tauri-runtime-wry/src/lib.rs +++ b/crates/tauri-runtime-wry/src/lib.rs @@ -4929,6 +4929,35 @@ You may have it installed on another user account, but it is not available for t webview_builder = webview_builder.with_allow_link_preview(webview_attributes.allow_link_preview); + + if let Some(on_web_content_process_terminate_handler) = + pending.on_web_content_process_terminate_handler + { + webview_builder = webview_builder + .with_on_web_content_process_terminate_handler(on_web_content_process_terminate_handler); + } else { + log::debug!("web content process terminated"); + let context_ = context.clone(); + let window_id_ = window_id.clone(); + webview_builder = webview_builder.with_on_web_content_process_terminate_handler(move || { + if let Ok(windows) = &context_.main_thread.windows.0.try_borrow() { + if let Some(window) = windows.get(&*window_id_.lock().unwrap()) { + if let Some(webview) = window.webviews.iter().find(|w| w.id == id) { + match webview.reload() { + Ok(_) => log::debug!("webview reloaded"), + Err(e) => log::error!("failed to reload webview: {}", e), + } + } else { + log::error!("failed to find webview") + } + } else { + log::error!("failed to get window") + } + } else { + log::error!("failed to borrow windows") + } + }); + } } #[cfg(target_os = "ios")] diff --git a/crates/tauri-runtime/src/webview.rs b/crates/tauri-runtime/src/webview.rs index 80f7edf0e94b..d704c721e862 100644 --- a/crates/tauri-runtime/src/webview.rs +++ b/crates/tauri-runtime/src/webview.rs @@ -41,6 +41,9 @@ type DocumentTitleChangedHandler = dyn Fn(String) + Send + 'static; type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync; +#[cfg(any(target_os = "macos", target_os = "ios"))] +type OnWebContentProcessTerminateHandler = dyn Fn() + Send; + #[cfg(target_os = "ios")] type InputAccessoryViewBuilderFn = dyn Fn(&objc2_ui_kit::UIView) -> Option> + Send @@ -225,6 +228,9 @@ pub struct PendingWebview> { pub on_page_load_handler: Option>, pub download_handler: Option>, + + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub on_web_content_process_terminate_handler: Option>, } impl> PendingWebview { @@ -251,6 +257,8 @@ impl> PendingWebview { web_resource_request_handler: None, on_page_load_handler: None, download_handler: None, + #[cfg(any(target_os = "macos", target_os = "ios"))] + on_web_content_process_terminate_handler: None, }) } } diff --git a/crates/tauri/src/app.rs b/crates/tauri/src/app.rs index e8e5343d9abf..30927e12ba53 100644 --- a/crates/tauri/src/app.rs +++ b/crates/tauri/src/app.rs @@ -67,6 +67,9 @@ pub type SetupHook = Box) -> std::result::Result<(), Box> + Send>; /// A closure that is run every time a page starts or finishes loading. pub type OnPageLoad = dyn Fn(&Webview, &PageLoadPayload<'_>) + Send + Sync + 'static; +/// A closure that is run when the web content process terminates. +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub type OnWebContentProcessTerminate = dyn Fn(&Webview) + Send + Sync + 'static; pub type ChannelInterceptor = Box, CallbackFn, usize, &InvokeResponseBody) -> bool + Send + Sync + 'static>; @@ -1390,6 +1393,10 @@ pub struct Builder { /// Page load hook. on_page_load: Option>>, + /// Web content process termination hook. + #[cfg(any(target_os = "macos", target_os = "ios"))] + on_web_content_process_terminate: Option>>, + /// All passed plugins plugins: PluginStore, @@ -1476,6 +1483,8 @@ impl Builder { .into_string(), channel_interceptor: None, on_page_load: None, + #[cfg(any(target_os = "macos", target_os = "ios"))] + on_web_content_process_terminate: None, plugins: PluginStore::default(), uri_scheme_protocols: Default::default(), state: StateManager::new(), @@ -1656,6 +1665,23 @@ tauri::Builder::default() self } + /// Defines the web content process termination hook. + /// + /// ## Platform-specific + /// + /// - **Linux / Windows / Android:** Unsupported. + #[cfg(any(target_os = "macos", target_os = "ios"))] + #[must_use] + pub fn on_web_content_process_terminate(mut self, on_web_content_process_terminate: F) -> Self + where + F: Fn(&Webview) + Send + Sync + 'static, + { + self + .on_web_content_process_terminate + .replace(Arc::new(on_web_content_process_terminate)); + self + } + /// Adds a Tauri application plugin. /// /// A plugin is created using the [`crate::plugin::Builder`] struct.Check its documentation for more information. @@ -2104,6 +2130,8 @@ tauri::Builder::default() self.plugins, self.invoke_handler, self.on_page_load, + #[cfg(any(target_os = "macos", target_os = "ios"))] + self.on_web_content_process_terminate, self.uri_scheme_protocols, self.state, #[cfg(desktop)] diff --git a/crates/tauri/src/ipc/protocol.rs b/crates/tauri/src/ipc/protocol.rs index 1b1e08b8f9b4..72dea2821b07 100644 --- a/crates/tauri/src/ipc/protocol.rs +++ b/crates/tauri/src/ipc/protocol.rs @@ -571,6 +571,8 @@ mod tests { PluginStore::default(), Box::new(|_| false), None, + #[cfg(any(target_os = "macos", target_os = "ios"))] + None, Default::default(), StateManager::new(), Default::default(), @@ -687,6 +689,8 @@ mod tests { PluginStore::default(), Box::new(|_| false), None, + #[cfg(any(target_os = "macos", target_os = "ios"))] + None, Default::default(), StateManager::new(), Default::default(), diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 25ae8eaabe91..7b1f224137a8 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -31,6 +31,9 @@ use crate::{ Assets, Context, DebugAppIcon, EventName, Pattern, Runtime, StateManager, Webview, Window, }; +#[cfg(any(target_os = "macos", target_os = "ios"))] +use crate::app::OnWebContentProcessTerminate; + #[cfg(desktop)] mod menu; #[cfg(all(desktop, feature = "tray-icon"))] @@ -251,6 +254,9 @@ impl AppManager { plugins: PluginStore, invoke_handler: Box>, on_page_load: Option>>, + #[cfg(any(target_os = "macos", target_os = "ios"))] on_web_content_process_terminate: Option< + Arc>, + >, uri_scheme_protocols: HashMap>>, state: StateManager, #[cfg(desktop)] menu_event_listener: Vec>>, @@ -284,6 +290,8 @@ impl AppManager { webviews: Mutex::default(), invoke_handler, on_page_load, + #[cfg(any(target_os = "macos", target_os = "ios"))] + on_web_content_process_terminate, uri_scheme_protocols: Mutex::new(uri_scheme_protocols), event_listeners: Arc::new(webview_event_listeners), invoke_initialization_script, @@ -762,6 +770,8 @@ mod test { PluginStore::default(), Box::new(|_| false), None, + #[cfg(any(target_os = "macos", target_os = "ios"))] + None, Default::default(), StateManager::new(), Default::default(), diff --git a/crates/tauri/src/manager/webview.rs b/crates/tauri/src/manager/webview.rs index 98c470b76e60..ee15597e39f5 100644 --- a/crates/tauri/src/manager/webview.rs +++ b/crates/tauri/src/manager/webview.rs @@ -28,6 +28,9 @@ use crate::{ EventLoopMessage, EventTarget, Manager, Runtime, Scopes, UriSchemeContext, Webview, Window, }; +#[cfg(any(target_os = "macos", target_os = "ios"))] +use crate::app::OnWebContentProcessTerminate; + use super::{ window::{DragDropPayload, DRAG_DROP_EVENT, DRAG_ENTER_EVENT, DRAG_LEAVE_EVENT, DRAG_OVER_EVENT}, {AppManager, EmitPayload}, @@ -70,6 +73,9 @@ pub struct WebviewManager { pub invoke_handler: Box>, /// The page load hook, invoked when the webview performs a navigation. pub on_page_load: Option>>, + /// The web content process termination hook. + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub on_web_content_process_terminate: Option>>, /// The webview protocols available to all webviews. pub uri_scheme_protocols: Mutex>>>, /// Webview event listeners to all webviews. @@ -304,6 +310,29 @@ impl WebviewManager { } })); + #[cfg(any(target_os = "macos", target_os = "ios"))] + if pending.on_web_content_process_terminate_handler.is_none() { + let app_manager_ = manager.manager_owned(); + if app_manager_ + .webview + .on_web_content_process_terminate + .is_some() + { + let label_ = pending.label.clone(); + pending + .on_web_content_process_terminate_handler + .replace(Box::new(move || { + if let Some(w) = app_manager_.get_webview(&label_) { + if let Some(on_web_content_process_terminate) = + &app_manager_.webview.on_web_content_process_terminate + { + on_web_content_process_terminate(&w); + } + } + })); + } + } + #[cfg(feature = "protocol-asset")] if !registered_scheme_protocols.contains(&"asset".into()) { let asset_scope = app_manager