Windows: fix crash with unhandled window (#9164)

白山風露 created

On Windows, some windows may be created that are not managed by the
application.
For example, the Japanese IME creates pop-ups like this one.

<img width="325" alt="image"
src="https://github.com/zed-industries/zed/assets/6465609/503aaa0a-7568-485a-a138-e689ae67001c">

The internal data associated with such a window is different from
`WindowsWindowInner` and will crash if referenced.
Therefore, before calling `try_get_window_inner`, it checks if it is an
owned window.


Release Notes:

- N/A

Change summary

crates/gpui/src/platform/windows/platform.rs | 34 +++++++++++++++++----
crates/gpui/src/platform/windows/window.rs   |  9 +++-
2 files changed, 33 insertions(+), 10 deletions(-)

Detailed changes

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -72,13 +72,15 @@ pub(crate) struct WindowsPlatformSystemSettings {
     pub(crate) wheel_scroll_lines: u32,
 }
 
+type WindowHandleValues = HashSet<isize>;
+
 pub(crate) struct WindowsPlatformInner {
     background_executor: BackgroundExecutor,
     pub(crate) foreground_executor: ForegroundExecutor,
     main_receiver: flume::Receiver<Runnable>,
     text_system: Arc<WindowsTextSystem>,
     callbacks: Mutex<Callbacks>,
-    pub(crate) window_handles: RefCell<HashSet<AnyWindowHandle>>,
+    pub(crate) window_handle_values: RefCell<WindowHandleValues>,
     pub(crate) event: HANDLE,
     pub(crate) settings: RefCell<WindowsPlatformSystemSettings>,
 }
@@ -165,7 +167,7 @@ impl WindowsPlatform {
         let foreground_executor = ForegroundExecutor::new(dispatcher);
         let text_system = Arc::new(WindowsTextSystem::new());
         let callbacks = Mutex::new(Callbacks::default());
-        let window_handles = RefCell::new(HashSet::new());
+        let window_handle_values = RefCell::new(HashSet::new());
         let settings = RefCell::new(WindowsPlatformSystemSettings::new());
         let inner = Rc::new(WindowsPlatformInner {
             background_executor,
@@ -173,7 +175,7 @@ impl WindowsPlatform {
             main_receiver,
             text_system,
             callbacks,
-            window_handles,
+            window_handle_values,
             event,
             settings,
         });
@@ -188,6 +190,15 @@ impl WindowsPlatform {
             return true;
         }
 
+        if !self
+            .inner
+            .window_handle_values
+            .borrow()
+            .contains(&msg.hwnd.0)
+        {
+            return false;
+        }
+
         if let Some(inner) = try_get_window_inner(msg.hwnd) {
             inner.handle_immediate_msg(msg.message, msg.wParam, msg.lParam)
         } else {
@@ -202,7 +213,11 @@ impl WindowsPlatform {
     }
 }
 
-unsafe extern "system" fn invalidate_window_callback(hwnd: HWND, _: LPARAM) -> BOOL {
+unsafe extern "system" fn invalidate_window_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
+    let window_handle_values = unsafe { &*(lparam.0 as *const WindowHandleValues) };
+    if !window_handle_values.contains(&hwnd.0) {
+        return TRUE;
+    }
     if let Some(inner) = try_get_window_inner(hwnd) {
         inner.invalidate_client_area();
     }
@@ -210,12 +225,12 @@ unsafe extern "system" fn invalidate_window_callback(hwnd: HWND, _: LPARAM) -> B
 }
 
 /// invalidates all windows belonging to a thread causing a paint message to be scheduled
-fn invalidate_thread_windows(win32_thread_id: u32) {
+fn invalidate_thread_windows(win32_thread_id: u32, window_handle_values: &WindowHandleValues) {
     unsafe {
         EnumThreadWindows(
             win32_thread_id,
             Some(invalidate_window_callback),
-            LPARAM::default(),
+            LPARAM(window_handle_values as *const _ as isize),
         )
     };
 }
@@ -244,7 +259,12 @@ impl Platform for WindowsPlatform {
 
             // compositor clock ticked so we should draw a frame
             if wait_result == 1 {
-                unsafe { invalidate_thread_windows(GetCurrentThreadId()) };
+                unsafe {
+                    invalidate_thread_windows(
+                        GetCurrentThreadId(),
+                        &self.inner.window_handle_values.borrow(),
+                    )
+                };
 
                 while unsafe { PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE) }.as_bool()
                 {

crates/gpui/src/platform/windows/window.rs 🔗

@@ -302,8 +302,8 @@ impl WindowsWindowInner {
         if let Some(callback) = callbacks.close.take() {
             callback()
         }
-        let mut window_handles = self.platform_inner.window_handles.borrow_mut();
-        window_handles.remove(&self.handle);
+        let mut window_handles = self.platform_inner.window_handle_values.borrow_mut();
+        window_handles.remove(&self.hwnd.0);
         if window_handles.is_empty() {
             self.platform_inner
                 .foreground_executor
@@ -680,7 +680,10 @@ impl WindowsWindow {
             inner: context.inner.unwrap(),
             drag_drop_handler,
         };
-        platform_inner.window_handles.borrow_mut().insert(handle);
+        platform_inner
+            .window_handle_values
+            .borrow_mut()
+            .insert(wnd.inner.hwnd.0);
         match options.bounds {
             WindowBounds::Fullscreen => wnd.toggle_full_screen(),
             WindowBounds::Maximized => wnd.maximize(),