diff --git a/Cargo.toml b/Cargo.toml index 31033ebd0..fa261b95d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -163,6 +163,7 @@ web-sys = { version = "0.3", features = [ "KeyboardEventInit", "EventInit", "EventTarget", + "BeforeUnloadEvent", # Error banner (web_diag): forward wgpu/naga errors to the browser console # and inject a copyable DOM overlay (document.body → append_child). "console", diff --git a/src/app/mod.rs b/src/app/mod.rs index cfb3956e9..be91f45e1 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -3178,6 +3178,8 @@ pub fn run() -> iced::Result { impl Drop for OpenCADStudio { fn drop(&mut self) { + #[cfg(target_arch = "wasm32")] + crate::sys::set_unsaved_changes_warning(false); // Kill plugin runner processes as soon as the application state is // dropped, instead of waiting for the thread-local manager destructor. // This makes host shutdown deterministic and fast on every exit path. diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 8dfc046cf..6fd071748 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -217,6 +217,8 @@ impl OpenCADStudio { ); } } + #[cfg(target_arch = "wasm32")] + crate::sys::set_unsaved_changes_warning(self.tabs.iter().any(|tab| tab.dirty)); task } diff --git a/src/sys.rs b/src/sys.rs index 2fba7d603..08bf15b17 100644 --- a/src/sys.rs +++ b/src/sys.rs @@ -17,6 +17,47 @@ pub fn open_url(url: &str) { } } +#[cfg(target_arch = "wasm32")] +thread_local! { + static BEFORE_UNLOAD_WARNING: std::cell::RefCell< + Option> + > = const { std::cell::RefCell::new(None) }; +} + +/// Enable the browser's standard leave-page confirmation while drawings have +/// unsaved changes. Browsers control the dialog text; preventing the event and +/// setting `returnValue` are the portable signal that a warning is required. +/// +/// The callback is installed only while needed so clean sessions remain +/// eligible for the browser back/forward cache. +#[cfg(target_arch = "wasm32")] +pub fn set_unsaved_changes_warning(active: bool) { + use wasm_bindgen::JsCast; + + let Some(window) = web_sys::window() else { + return; + }; + BEFORE_UNLOAD_WARNING.with(|warning| { + let mut warning = warning.borrow_mut(); + if active == warning.is_some() { + return; + } + if active { + let callback = wasm_bindgen::closure::Closure::new( + move |event: web_sys::BeforeUnloadEvent| { + event.prevent_default(); + event.set_return_value(""); + }, + ); + window.set_onbeforeunload(Some(callback.as_ref().unchecked_ref())); + *warning = Some(callback); + } else { + window.set_onbeforeunload(None); + warning.take(); + } + }); +} + /// Reveal a saved drawing in the native platform's file manager. #[cfg(not(target_arch = "wasm32"))] pub fn reveal_in_file_manager(path: &std::path::Path) -> Result<(), String> {