[PATCH] user32: During destroy, don't send WM_ERASEBKGND to parent window if WS_CHILD is not set

Andrew Eikum aeikum at codeweavers.com
Thu Jul 15 10:05:24 CDT 2021


Prior to this change, Wine would hang during the DestroyWindow(child)
call because it is waiting for the parent window thread to process a
WM_ERASEBKGND message.

Signed-off-by: Andrew Eikum <aeikum at codeweavers.com>
---
 dlls/user32/tests/msg.c | 130 ++++++++++++++++++++++++++++++++++++++++
 dlls/user32/winpos.c    |   6 +-
 2 files changed, 134 insertions(+), 2 deletions(-)

diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c
index 9867e319d58..a3d30dd005b 100644
--- a/dlls/user32/tests/msg.c
+++ b/dlls/user32/tests/msg.c
@@ -11374,6 +11374,20 @@ static const struct message destroy_window_with_children[] = {
     { 0 }
 };
 
+static const struct message destroy_child_window_with_ws_child[] = {
+    { HCBT_DESTROYWND, hook },
+    { WM_PARENTNOTIFY, sent },
+    { WM_ERASEBKGND, sent },
+    { 0 }
+};
+
+static const struct message destroy_child_window_no_ws_child[] = {
+    { HCBT_DESTROYWND, hook },
+    { WM_PAINT, sent },
+    { WM_ERASEBKGND, sent|beginpaint },
+    { 0 }
+};
+
 static void test_DestroyWindow(void)
 {
     BOOL ret;
@@ -11478,6 +11492,121 @@ static void test_DestroyWindow(void)
     ok(!test, "wrong capture window %p\n", test);
 }
 
+struct destroy_child_window_test_data {
+    HANDLE evt;
+    HWND parent;
+    BOOL stop_processing;
+    BOOL quit;
+};
+
+static DWORD WINAPI destroy_child_window_parent_threadproc(void *user)
+{
+    struct destroy_child_window_test_data *data = user;
+
+    data->parent = CreateWindowA("TestWindowClass", "test parent",
+            WS_VISIBLE | WS_OVERLAPPEDWINDOW, 100, 200, 300, 400,
+            NULL, NULL, NULL, NULL);
+
+    SetEvent(data->evt);
+
+    while(!data->stop_processing)
+        flush_events();
+
+    SetEvent(data->evt);
+
+    while(data->stop_processing)
+        Sleep(10);
+
+    flush_events();
+
+    SetEvent(data->evt);
+
+    while(!data->quit)
+        Sleep(10);
+
+    DestroyWindow(data->parent);
+
+    flush_events();
+
+    return 0;
+}
+
+static void test_destroy_child_window(void)
+{
+    struct destroy_child_window_test_data data = {0};
+    HWND child;
+    HANDLE parent_thread;
+
+    /* create parent on this thread to demonstrate WM_ERASEBKGND message */
+    data.parent = CreateWindowA("TestWindowClass", "test parent",
+            WS_VISIBLE | WS_OVERLAPPEDWINDOW, 100, 200, 300, 400,
+            NULL, NULL, NULL, NULL);
+    ok(data.parent != NULL, "parent window create failed\n");
+
+    child = CreateWindowA("SimpleWindowClass", "test child",
+            WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
+            100, 200, 50, 50, NULL, NULL, NULL, NULL);
+    ok(child != NULL, "child window create failed\n");
+
+    SetWindowLongPtrW(child, GWL_STYLE, GetWindowLongPtrW(child, GWL_STYLE) | WS_CHILD);
+    SetParent(child, data.parent);
+
+    ShowWindow(child, SW_SHOW);
+    ShowWindow(data.parent, SW_SHOW);
+
+    flush_events();
+    flush_sequence();
+
+    DestroyWindow(child);
+
+    ok_sequence(destroy_child_window_with_ws_child, "destroy child with WS_CHILD", FALSE);
+
+    DestroyWindow(data.parent);
+
+    /* create parent on separate thread to demonstrate avoiding deadlock on WM_ERASEBKGND message */
+    data.evt = CreateEventW(NULL, FALSE, FALSE, NULL);
+
+    parent_thread = CreateThread(NULL, 0, destroy_child_window_parent_threadproc, &data, 0, NULL);
+
+    WaitForSingleObject(data.evt, INFINITE);
+    ok(data.parent != NULL, "parent window create failed\n");
+
+    child = CreateWindowA("SimpleWindowClass", "test child",
+            WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
+            100, 200, 50, 50, NULL, NULL, NULL, NULL);
+    ok(child != NULL, "child window create failed\n");
+
+    SetWindowLongPtrW(child, GWL_STYLE, GetWindowLongPtrW(child, GWL_STYLE) | WS_CHILD);
+    SetParent(child, data.parent);
+
+    ShowWindow(child, SW_SHOW);
+    ShowWindow(data.parent, SW_SHOW);
+
+    /* removing WS_CHILD avoids deadlock on WM_PARENTNOTIFY */
+    SetWindowLongPtrW(child, GWL_STYLE, GetWindowLongPtrW(child, GWL_STYLE) & ~WS_CHILD);
+
+    data.stop_processing = TRUE;
+    WaitForSingleObject(data.evt, INFINITE);
+
+    flush_events();
+    flush_sequence();
+
+    DestroyWindow(child);
+
+    data.stop_processing = FALSE;
+    WaitForSingleObject(data.evt, INFINITE);
+
+    ok_sequence(destroy_child_window_no_ws_child, "destroy child without WS_CHILD", FALSE);
+
+    data.quit = TRUE;
+    WaitForSingleObject(parent_thread, INFINITE);
+
+    flush_events();
+    flush_sequence();
+
+    CloseHandle(data.evt);
+    CloseHandle(parent_thread);
+}
 
 static const struct message WmDispatchPaint[] = {
     { WM_NCPAINT, sent },
@@ -18333,6 +18462,7 @@ START_TEST(msg)
         test_recursive_hook();
     }
     test_DestroyWindow();
+    test_destroy_child_window();
     test_DispatchMessage();
     test_SendMessageTimeout();
     test_edit_messages();
diff --git a/dlls/user32/winpos.c b/dlls/user32/winpos.c
index 6e96a4b5964..d047d714e79 100644
--- a/dlls/user32/winpos.c
+++ b/dlls/user32/winpos.c
@@ -2367,8 +2367,10 @@ BOOL USER_SetWindowPos( WINDOWPOS * winpos, int parent_x, int parent_y )
           (winpos->flags & SWP_AGG_STATUSFLAGS) != SWP_AGG_NOGEOMETRYCHANGE))
         {
             HWND parent = GetAncestor( winpos->hwnd, GA_PARENT );
-            if (!parent || parent == GetDesktopWindow()) parent = winpos->hwnd;
-            erase_now( parent, 0 );
+            if (!parent || parent == GetDesktopWindow())
+                erase_now( winpos->hwnd, 0 );
+            else if((GetWindowLongW(winpos->hwnd, GWL_STYLE) & WS_CHILD) == WS_CHILD)
+                erase_now( parent, 0 );
         }
 
         /* Give newly shown windows a chance to redraw */
-- 
2.32.0




More information about the wine-devel mailing list