use crate::shell::events::host_bridge_event_script; use crate::shell::navigation::register_desktop_navigation_events; use serde_json::json; use tauri::webview::PageLoadEvent; use tauri::{WebviewWindow, WindowEvent}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct DesktopLifecyclePayload { state: &'static str, focused: bool, native_state: &'static str, } pub(crate) fn resolve_desktop_lifecycle_payload( focused: bool, visible: bool, minimized: bool, ) -> DesktopLifecyclePayload { if !visible { return DesktopLifecyclePayload { state: "background", focused: false, native_state: "hidden", }; } if minimized { return DesktopLifecyclePayload { state: "background", focused: false, native_state: "minimized", }; } if focused { DesktopLifecyclePayload { state: "active", focused: true, native_state: "focused", } } else { DesktopLifecyclePayload { state: "inactive", focused: false, native_state: "blurred", } } } pub(crate) fn emit_desktop_lifecycle_event( window: &WebviewWindow, state: &'static str, focused: bool, native_state: &'static str, ) -> tauri::Result<()> { let script = host_bridge_event_script( "app.lifecycle", json!({ "state": state, "focused": focused, "nativeState": native_state, }), ) .map_err(tauri::Error::Json)?; window.eval(script) } pub(crate) fn emit_desktop_lifecycle_payload( window: &WebviewWindow, payload: DesktopLifecyclePayload, ) -> tauri::Result<()> { emit_desktop_lifecycle_event(window, payload.state, payload.focused, payload.native_state) } fn resolve_desktop_lifecycle_window_flag( label: &'static str, result: tauri::Result, default_value: bool, ) -> bool { match result { Ok(value) => value, Err(_error) => { eprintln!("desktop host event failed for app.lifecycle.{label}"); default_value } } } pub(crate) fn emit_current_desktop_lifecycle_event(window: &WebviewWindow) -> tauri::Result<()> { let visible = resolve_desktop_lifecycle_window_flag("visible", window.is_visible(), true); let minimized = resolve_desktop_lifecycle_window_flag("minimized", window.is_minimized(), false); let focused = resolve_desktop_lifecycle_window_flag( "focused", window.is_focused(), visible && !minimized, ); emit_desktop_lifecycle_payload( window, resolve_desktop_lifecycle_payload(focused, visible, minimized), ) } pub(crate) fn register_desktop_lifecycle_events(window: &WebviewWindow) { let lifecycle_window = window.clone(); window.on_window_event(move |event| { if matches!(event, WindowEvent::Focused(_) | WindowEvent::Resized(_)) { log_desktop_host_event_result( "app.lifecycle", emit_current_desktop_lifecycle_event(&lifecycle_window), ); } }); } pub(crate) fn should_replay_desktop_webview_state_on_page_load(event: PageLoadEvent) -> bool { event == PageLoadEvent::Finished } pub(crate) fn log_desktop_host_event_result( label: &'static str, result: tauri::Result<()>, ) -> bool { match result { Ok(()) => true, Err(_error) => { eprintln!("desktop host event failed for {label}"); false } } } pub(crate) fn replay_desktop_webview_state(window: &WebviewWindow) { log_desktop_host_event_result( "app.lifecycle", emit_current_desktop_lifecycle_event(window), ); log_desktop_host_event_result( "navigation.canGoBack", register_desktop_navigation_events(window), ); } #[cfg(test)] mod tests { use super::*; #[test] fn desktop_page_load_replays_state_only_after_finished_load() { assert!(should_replay_desktop_webview_state_on_page_load( PageLoadEvent::Finished )); assert!(!should_replay_desktop_webview_state_on_page_load( PageLoadEvent::Started )); } #[test] fn desktop_host_event_result_reports_success_and_failure() { assert!(log_desktop_host_event_result("network", Ok(()))); assert!(!log_desktop_host_event_result( "navigation", Err(tauri::Error::AssetNotFound("navigation".to_string())) )); } #[test] fn desktop_host_event_result_logs_stable_label_only() { assert!(!log_desktop_host_event_result( "navigation.canGoBack", Err(tauri::Error::AssetNotFound( "navigation?token=private".to_string() )) )); } #[test] fn desktop_lifecycle_window_flag_logs_failure_and_uses_default() { assert!(resolve_desktop_lifecycle_window_flag( "visible", Ok(true), false )); assert!(!resolve_desktop_lifecycle_window_flag( "focused", Err(tauri::Error::WindowNotFound), false )); } #[test] fn desktop_lifecycle_window_flag_logs_stable_label_only() { assert!(!resolve_desktop_lifecycle_window_flag( "focused", Err(tauri::Error::AssetNotFound( "window?token=private".to_string() )), false )); } #[test] fn desktop_lifecycle_payload_maps_hidden_and_minimized_to_background() { assert_eq!( resolve_desktop_lifecycle_payload(true, false, false), DesktopLifecyclePayload { state: "background", focused: false, native_state: "hidden", } ); assert_eq!( resolve_desktop_lifecycle_payload(true, true, true), DesktopLifecyclePayload { state: "background", focused: false, native_state: "minimized", } ); } #[test] fn desktop_lifecycle_payload_maps_visible_focus_state() { assert_eq!( resolve_desktop_lifecycle_payload(true, true, false), DesktopLifecyclePayload { state: "active", focused: true, native_state: "focused", } ); assert_eq!( resolve_desktop_lifecycle_payload(false, true, false), DesktopLifecyclePayload { state: "inactive", focused: false, native_state: "blurred", } ); } }