[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