From 4d968a2aca65a0529f84df429a05d673109bc1cd Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sun, 8 Nov 2026 17:46:10 +0800 Subject: [PATCH] fix(window): stop the app panicking when windows close and open together (#2124) Closing a window posts its full-screen notification while the runtime still holds its window table; the title bar observer emitted from there and the emit re-entered the table ("RefCell already mutably borrowed"). The emit now goes out after the notification returns. Opening several project windows at once could also hand the same project-N label to each request, so label choice and window creation now take turns. Co-authored-by: Claude --- src-tauri/src/titlebar.rs | 7 ++++++- src-tauri/src/windows.rs | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src-tauri/src/titlebar.rs b/src-tauri/src/titlebar.rs index d79a75523..58eb68019 100644 --- a/src-tauri/src/titlebar.rs +++ b/src-tauri/src/titlebar.rs @@ -89,7 +89,12 @@ fn track(window: &NSWindow, app: &tauri::AppHandle) -> Tracked { crate::macos::hide_tab_bar(&window); } // The web title bar releases the room kept for the window buttons in full screen. - app.state::().sink.emit(ide_core::IdeEvent::WindowFullscreenChanged); + // Closing a window posts this while the runtime still holds its window table, and an + // emit from here evaluates script in that table; send it from outside the notification. + let app = app.clone(); + tauri::async_runtime::spawn(async move { + app.state::().sink.emit(ide_core::IdeEvent::WindowFullscreenChanged); + }); }); let observer = unsafe { NSNotificationCenter::defaultCenter().addObserverForName_object_queue_usingBlock( diff --git a/src-tauri/src/windows.rs b/src-tauri/src/windows.rs index fd9c9fd26..97c1ffbd9 100644 --- a/src-tauri/src/windows.rs +++ b/src-tauri/src/windows.rs @@ -286,6 +286,9 @@ pub fn settings_label(parent: &str) -> String { format!("{}-{parent}", AuxKind::Settings.name()) } +/// Held from choosing a project window's label until the window exists. +static CREATING: Mutex<()> = Mutex::new(()); + /// Windows being built to join a window group, which takes their frame from the group. #[cfg(target_os = "macos")] static JOINING_GROUP: Mutex> = Mutex::new(Vec::new()); @@ -665,6 +668,8 @@ fn create_window_for( ) -> IdeResult { #[cfg(target_os = "macos")] let tab_target = if _restoring { None } else { crate::macos::new_project_tab_target(app, &launch)? }; + // The label is free only until the window exists, so concurrent opens take turns. + let creating = CREATING.lock(); let windows = app.webview_windows(); let label = next_label(windows.keys().map(String::as_str)); let mut config = window_config(app)?; @@ -691,6 +696,7 @@ fn create_window_for( config.visible = false; } let window = build_project_window(app, config); + drop(creating); // Plugins hear of the window from the main thread's queue; forget it after them. #[cfg(target_os = "macos")] if tab_target.is_some() {