[PATCH 4/6] msi: Process ShowDialog/EndDialog after all other control events.

Zebediah Figura z.figura12 at gmail.com
Thu Jul 20 23:43:13 CDT 2017


Only one of these events is processed on Windows, and it is always
processed last regardless of ordering. This is a more correct fix
for bug #28219.

Signed-off-by: Zebediah Figura <z.figura12 at gmail.com>
---
 dlls/msi/dialog.c        |  45 +++++++++++--
 dlls/msi/tests/package.c | 165 +++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 199 insertions(+), 11 deletions(-)

diff --git a/dlls/msi/dialog.c b/dlls/msi/dialog.c
index 6c215b1..6b57606 100644
--- a/dlls/msi/dialog.c
+++ b/dlls/msi/dialog.c
@@ -55,6 +55,7 @@ typedef struct msi_control_tag msi_control;
 typedef UINT (*msi_handler)( msi_dialog *, msi_control *, WPARAM );
 typedef void (*msi_update)( msi_dialog *, msi_control * );
 typedef UINT (*control_event_handler)( msi_dialog *, const WCHAR *, const WCHAR * );
+typedef UINT (*event_handler)( msi_dialog *, const WCHAR * );
 
 struct msi_control_tag
 {
@@ -100,6 +101,8 @@ struct msi_dialog_tag
     HWND hWndFocus;
     LPWSTR control_default;
     LPWSTR control_cancel;
+    event_handler pending_event;
+    LPWSTR pending_argument;
     WCHAR name[1];
 };
 
@@ -993,6 +996,16 @@ static UINT msi_dialog_button_handler( msi_dialog *dialog, msi_control *control,
     }
     r = MSI_IterateRecords( view, 0, msi_dialog_control_event, dialog );
     msiobj_release( &view->hdr );
+
+    /* dialog control events must be processed last regardless of ordering */
+    if (dialog->pending_event)
+    {
+        r = dialog->pending_event( dialog, dialog->pending_argument );
+
+        msi_free( dialog->pending_argument );
+        dialog->pending_event = NULL;
+        dialog->pending_argument = NULL;
+    }
     return r;
 }
 
@@ -4317,8 +4330,6 @@ UINT WINAPI MsiPreviewBillboardA( MSIHANDLE hPreview, LPCSTR szControlName, LPCS
     return ERROR_CALL_NOT_IMPLEMENTED;
 }
 
-typedef UINT (*event_handler)( msi_dialog *, const WCHAR * );
-
 struct control_event
 {
     const WCHAR  *event;
@@ -4386,6 +4397,14 @@ static UINT event_end_dialog( msi_dialog *dialog, const WCHAR *argument )
     return ERROR_SUCCESS;
 }
 
+static UINT pending_event_end_dialog( msi_dialog *dialog, const WCHAR *argument )
+{
+    dialog->pending_event = event_end_dialog;
+    if (dialog->pending_argument) msi_free( dialog->pending_argument );
+    dialog->pending_argument = strdupW( argument );
+    return ERROR_SUCCESS;
+}
+
 /* transition from one modal dialog to another modal dialog */
 static UINT event_new_dialog( msi_dialog *dialog, const WCHAR *argument )
 {
@@ -4396,6 +4415,14 @@ static UINT event_new_dialog( msi_dialog *dialog, const WCHAR *argument )
     return ERROR_SUCCESS;
 }
 
+static UINT pending_event_new_dialog( msi_dialog *dialog, const WCHAR *argument )
+{
+    dialog->pending_event = event_new_dialog;
+    if (dialog->pending_argument) msi_free( dialog->pending_argument );
+    dialog->pending_argument = strdupW( argument );
+    return ERROR_SUCCESS;
+}
+
 /* create a new child dialog of an existing modal dialog */
 static UINT event_spawn_dialog( msi_dialog *dialog, const WCHAR *argument )
 {
@@ -4409,6 +4436,14 @@ static UINT event_spawn_dialog( msi_dialog *dialog, const WCHAR *argument )
     return ERROR_SUCCESS;
 }
 
+static UINT pending_event_spawn_dialog( msi_dialog *dialog, const WCHAR *argument )
+{
+    dialog->pending_event = event_spawn_dialog;
+    if (dialog->pending_argument) msi_free( dialog->pending_argument );
+    dialog->pending_argument = strdupW( argument );
+    return ERROR_SUCCESS;
+}
+
 /* creates a dialog that remains up for a period of time based on a condition */
 static UINT event_spawn_wait_dialog( msi_dialog *dialog, const WCHAR *argument )
 {
@@ -4634,9 +4669,9 @@ static const WCHAR validate_product_idW[] = {'V','a','l','i','d','a','t','e','P'
 
 static const struct control_event control_events[] =
 {
-    { end_dialogW, event_end_dialog },
-    { new_dialogW, event_new_dialog },
-    { spawn_dialogW, event_spawn_dialog },
+    { end_dialogW, pending_event_end_dialog },
+    { new_dialogW, pending_event_new_dialog },
+    { spawn_dialogW, pending_event_spawn_dialog },
     { spawn_wait_dialogW, event_spawn_wait_dialog },
     { do_actionW, event_do_action },
     { add_localW, event_add_local },
diff --git a/dlls/msi/tests/package.c b/dlls/msi/tests/package.c
index 0b287f4..d978286 100644
--- a/dlls/msi/tests/package.c
+++ b/dlls/msi/tests/package.c
@@ -674,6 +674,19 @@ static UINT create_control_table( MSIHANDLE hdb )
             "PRIMARY KEY `Dialog_`, `Control`)");
 }
 
+static UINT create_controlevent_table( MSIHANDLE hdb )
+{
+    return run_query(hdb,
+            "CREATE TABLE `ControlEvent` ("
+            "`Dialog_` CHAR(72) NOT NULL, "
+            "`Control_` CHAR(50) NOT NULL, "
+            "`Event` CHAR(50) NOT NULL, "
+            "`Argument` CHAR(255) NOT NULL, "
+            "`Condition` CHAR(255), "
+            "`Ordering` SHORT "
+            "PRIMARY KEY `Dialog_`, `Control_`, `Event`, `Argument`, `Condition`)");
+}
+
 #define make_add_entry(type, qtext) \
     static UINT add##_##type##_##entry( MSIHANDLE hdb, const char *values ) \
     { \
@@ -757,6 +770,18 @@ make_add_entry(custom_action,
                "INSERT INTO `CustomAction`  "
                "(`Action`, `Type`, `Source`, `Target`) VALUES( %s )")
 
+make_add_entry(dialog,
+               "INSERT INTO `Dialog` "
+               "(`Dialog`, `HCentering`, `VCentering`, `Width`, `Height`, `Attributes`, `Control_First`) VALUES ( %s )")
+
+make_add_entry(control,
+               "INSERT INTO `Control` "
+               "(`Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Text`) VALUES( %s )");
+
+make_add_entry(controlevent,
+               "INSERT INTO `ControlEvent` "
+               "(`Dialog_`, `Control_`, `Event`, `Argument`, `Condition`, `Ordering`) VALUES( %s )");
+
 static UINT add_reglocator_entry( MSIHANDLE hdb, const char *sig, UINT root, const char *path,
                                   const char *name, UINT type )
 {
@@ -9562,16 +9587,12 @@ static void test_externalui_message(void)
 
     r = create_dialog_table(hdb);
     ok(r == ERROR_SUCCESS, "failed to create dialog table %u\n", r);
-    r = run_query(hdb, "INSERT INTO `Dialog` (`Dialog`, `HCentering`, "
-                  "`VCentering`, `Width`, `Height`, `Control_First`) "
-                  "VALUES ('dialog', 5, 5, 100, 100, 'dummy')");
+    r = add_dialog_entry(hdb, "'dialog', 50, 50, 100, 100, 0, 'dummy'");
     ok(r == ERROR_SUCCESS, "failed to insert into dialog table %u\n", r);
 
     r = create_control_table(hdb);
     ok(r == ERROR_SUCCESS, "failed to create control table %u\n", r);
-    r = run_query(hdb, "INSERT INTO `Control` (`Dialog_`, `Control`, "
-                  "`Type`, `X`, `Y`, `Width`, `Height`) "
-                  "VALUES('dialog', 'dummy', 'Text', 5, 5, 5, 5)");
+    r = add_control_entry(hdb, "'dialog', 'dummy', 'Text', 5, 5, 5, 5, 3, 'dummy'");
     ok(r == ERROR_SUCCESS, "failed to insert into control table %u", r);
 
     r = package_from_db(hdb, &hpkg);
@@ -9630,6 +9651,137 @@ static void test_externalui_message(void)
     DeleteFileA("forcecodepage.idt");
 }
 
+static const struct externalui_message controlevent_spawn_sequence[] = {
+    {INSTALLMESSAGE_ACTIONSTART, 3, {"", "spawn", "", ""}, {0, 1, 1, 1}},
+    {INSTALLMESSAGE_INFO, 2, {"", "spawn", ""}, {0, 1, 1}},
+    {INSTALLMESSAGE_SHOWDIALOG, 0, {"spawn"}, {1}},
+    {INSTALLMESSAGE_ACTIONSTART, 2, {"", "spawn", "Dialog created"}, {0, 1, 1}},
+
+    {INSTALLMESSAGE_ACTIONSTART, 3, {"", "custom", "", ""}, {0, 1, 1, 1}},
+    {INSTALLMESSAGE_INFO, 2, {"", "custom", ""}, {0, 1, 1}},
+    {INSTALLMESSAGE_INFO, 2, {"", "custom", "1"}, {0, 1, 1}},
+
+    {INSTALLMESSAGE_ACTIONSTART, 2, {"", "child1", "Dialog created"}, {0, 1, 1}},
+
+    {INSTALLMESSAGE_INFO, 2, {"", "spawn", "2"}, {0, 1, 1}},
+    {0}
+};
+
+static const struct externalui_message controlevent_spawn2_sequence[] = {
+    {INSTALLMESSAGE_ACTIONSTART, 3, {"", "spawn2", "", ""}, {0, 1, 1, 1}},
+    {INSTALLMESSAGE_INFO, 2, {"", "spawn2", "2"}, {0, 1, 1}},
+    {INSTALLMESSAGE_SHOWDIALOG, 0, {"spawn2"}, {1}},
+    {INSTALLMESSAGE_ACTIONSTART, 2, {"", "spawn2", "Dialog created"}, {0, 1, 1}},
+
+    {INSTALLMESSAGE_ACTIONSTART, 3, {"", "custom", "", ""}, {0, 1, 1, 1}},
+    {INSTALLMESSAGE_INFO, 2, {"", "custom", "2"}, {0, 1, 1}},
+    {INSTALLMESSAGE_INFO, 2, {"", "custom", "1"}, {0, 1, 1}},
+
+    {INSTALLMESSAGE_ACTIONSTART, 2, {"", "child2", "Dialog created"}, {0, 1, 1}},
+
+    {INSTALLMESSAGE_INFO, 2, {"", "spawn2", "2"}, {0, 1, 1}},
+    {0}
+};
+
+static void test_controlevent(void)
+{
+    INSTALLUI_HANDLER_RECORD prev;
+    MSIHANDLE hdb, hpkg;
+    UINT r;
+
+    if (!winetest_interactive)
+    {
+        skip("interactive ControlEvent tests\n");
+        return;
+    }
+
+    MsiSetInternalUI(INSTALLUILEVEL_FULL, NULL);
+
+    /* processing SHOWDIALOG with a record handler causes a crash on XP */
+    MsiSetExternalUIA(externalui_message_string_callback, INSTALLLOGMODE_SHOWDIALOG, NULL);
+    r = MsiSetExternalUIRecord(externalui_message_callback, 0xffffffff ^ INSTALLLOGMODE_PROGRESS ^ INSTALLLOGMODE_SHOWDIALOG, NULL, &prev);
+
+    flush_sequence();
+
+    CoInitialize(NULL);
+
+    hdb = create_package_db();
+    ok(hdb, "failed to create database\n");
+
+    create_file_data("forcecodepage.idt", "\r\n\r\n1252\t_ForceCodepage\r\n");
+    r = MsiDatabaseImportA(hdb, CURR_DIR, "forcecodepage.idt");
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r);
+
+    r = create_dialog_table(hdb);
+    ok(r == ERROR_SUCCESS, "failed to create Dialog table: %u\n", r);
+    r = add_dialog_entry(hdb, "'spawn', 50, 50, 100, 100, 3, 'button'");
+    ok(r == ERROR_SUCCESS, "failed to insert into Dialog table: %u\n", r);
+    r = add_dialog_entry(hdb, "'spawn2', 50, 50, 100, 100, 3, 'button'");
+    ok(r == ERROR_SUCCESS, "failed to insert into Dialog table: %u\n", r);
+    r = add_dialog_entry(hdb, "'child1', 50, 50, 80, 40, 3, 'exit'");
+    ok(r == ERROR_SUCCESS, "failed to insert into Dialog table: %u\n", r);
+    r = add_dialog_entry(hdb, "'child2', 50, 50, 80, 40, 3, 'exit'");
+    ok(r == ERROR_SUCCESS, "failed to insert into Dialog table: %u\n", r);
+
+    r = create_control_table(hdb);
+    ok(r == ERROR_SUCCESS, "failed to create Control table: %u\n", r);
+    r = add_control_entry(hdb, "'spawn', 'button', 'PushButton', 10, 10, 66, 17, 3, 'Click me'");
+    ok(r == ERROR_SUCCESS, "failed to insert into Control table: %u\n", r);
+    r = add_control_entry(hdb, "'spawn2', 'button', 'PushButton', 10, 10, 66, 17, 3, 'Click me'");
+    ok(r == ERROR_SUCCESS, "failed to insert into Control table: %u\n", r);
+    r = add_control_entry(hdb, "'child1', 'exit', 'PushButton', 10, 10, 66, 17, 3, 'Click me'");
+    ok(r == ERROR_SUCCESS, "failed to insert into Control table: %u\n", r);
+    r = add_control_entry(hdb, "'child2', 'exit', 'PushButton', 10, 10, 66, 17, 3, 'Click me'");
+    ok(r == ERROR_SUCCESS, "failed to insert into Control table: %u\n", r);
+
+    r = create_controlevent_table(hdb);
+    ok(r == ERROR_SUCCESS, "failed to create ControlEvent table: %u\n", r);
+    r = add_controlevent_entry(hdb, "'child1', 'exit', 'EndDialog', 'Exit', 1, 1");
+    ok(r == ERROR_SUCCESS, "failed to insert into ControlEvent table: %u\n", r);
+    r = add_controlevent_entry(hdb, "'child2', 'exit', 'EndDialog', 'Exit', 1, 1");
+    ok(r == ERROR_SUCCESS, "failed to insert into ControlEvent table: %u\n", r);
+
+    r = create_custom_action_table(hdb);
+    ok(r == ERROR_SUCCESS, "failed to create CustomAction table: %u\n", r);
+    r = add_custom_action_entry(hdb, "'custom', 51, 'dummy', 'dummy value'");
+    ok(r == ERROR_SUCCESS, "failed to insert into CustomAction table: %u\n", r);
+
+    /* SpawnDialog and EndDialog should trigger after all other events */
+    r = add_controlevent_entry(hdb, "'spawn', 'button', 'SpawnDialog', 'child1', 1, 1");
+    ok(r == ERROR_SUCCESS, "failed to insert into ControlEvent table: %u\n", r);
+    r = add_controlevent_entry(hdb, "'spawn', 'button', 'DoAction', 'custom', 1, 2");
+    ok(r == ERROR_SUCCESS, "failed to insert into ControlEvent table: %u\n", r);
+
+    /* Multiple dialog events cause only the last one to be triggered */
+    r = add_controlevent_entry(hdb, "'spawn2', 'button', 'SpawnDialog', 'child1', 1, 1");
+    ok(r == ERROR_SUCCESS, "failed to insert into ControlEvent table: %u\n", r);
+    r = add_controlevent_entry(hdb, "'spawn2', 'button', 'SpawnDialog', 'child2', 1, 2");
+    ok(r == ERROR_SUCCESS, "failed to insert into ControlEvent table: %u\n", r);
+    r = add_controlevent_entry(hdb, "'spawn2', 'button', 'DoAction', 'custom', 1, 3");
+    ok(r == ERROR_SUCCESS, "failed to insert into ControlEvent table: %u\n", r);
+
+    r = package_from_db(hdb, &hpkg);
+    ok(r == ERROR_SUCCESS, "failed to create package: %u\n", r);
+    ok_sequence(openpackage_sequence, "MsiOpenPackage()", FALSE);
+
+    r = MsiDoActionA(hpkg, "spawn");
+    todo_wine
+    ok(r == ERROR_INSTALL_USEREXIT, "expected ERROR_INSTALL_USEREXIT, got %u\n", r);
+    ok_sequence(controlevent_spawn_sequence, "control event: spawn", TRUE);
+
+    r = MsiDoActionA(hpkg, "spawn2");
+    todo_wine
+    ok(r == ERROR_INSTALL_USEREXIT, "expected ERROR_INSTALL_USEREXIT, got %u\n", r);
+    ok_sequence(controlevent_spawn2_sequence, "control event: spawn2", TRUE);
+
+    MsiCloseHandle(hpkg);
+    ok_sequence(closehandle_sequence, "MsiCloseHandle()", FALSE);
+
+    CoUninitialize();
+    DeleteFileA(msifile);
+    DeleteFileA("forcecodepage.idt");
+}
+
 START_TEST(package)
 {
     STATEMGRSTATUS status;
@@ -9689,6 +9841,7 @@ START_TEST(package)
     test_MsiDatabaseCommit();
     test_externalui();
     test_externalui_message();
+    test_controlevent();
 
     if (pSRSetRestorePointA && !pMsiGetComponentPathExA && ret)
     {
-- 
2.7.4




More information about the wine-patches mailing list