|
@@ -11,8 +11,11 @@
|
|
|
#include <map>
|
|
|
#include <vector>
|
|
|
|
|
|
+#include "base/containers/contains.h"
|
|
|
+#include "base/no_destructor.h"
|
|
|
#include "base/strings/string_util.h"
|
|
|
#include "base/strings/utf_string_conversions.h"
|
|
|
+#include "base/synchronization/lock.h"
|
|
|
#include "base/task/post_task.h"
|
|
|
#include "base/win/scoped_gdi_object.h"
|
|
|
#include "shell/browser/browser.h"
|
|
@@ -30,6 +33,41 @@ MessageBoxSettings::~MessageBoxSettings() = default;
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
+struct DialogResult {
|
|
|
+ int button_id;
|
|
|
+ bool verification_flag_checked;
|
|
|
+};
|
|
|
+
|
|
|
+// <ID, messageBox> map.
|
|
|
+//
|
|
|
+// Note that the HWND is stored in a unique_ptr, because the pointer of HWND
|
|
|
+// will be passed between threads and we need to ensure the memory of HWND is
|
|
|
+// not changed while dialogs map is modified.
|
|
|
+std::map<int, std::unique_ptr<HWND>>& GetDialogsMap() {
|
|
|
+ static base::NoDestructor<std::map<int, std::unique_ptr<HWND>>> dialogs;
|
|
|
+ return *dialogs;
|
|
|
+}
|
|
|
+
|
|
|
+// Speical HWND used by the dialogs map.
|
|
|
+//
|
|
|
+// - ID is used but window has not been created yet.
|
|
|
+const HWND kHwndReserve = reinterpret_cast<HWND>(-1);
|
|
|
+// - Notification to cancel message box.
|
|
|
+const HWND kHwndCancel = reinterpret_cast<HWND>(-2);
|
|
|
+
|
|
|
+// Lock used for modifying HWND between threads.
|
|
|
+//
|
|
|
+// Note that there might be multiple dialogs being opened at the same time, but
|
|
|
+// we only use one lock for them all, because each dialog is independent from
|
|
|
+// each other and there is no need to use different lock for each one.
|
|
|
+// Also note that the |GetDialogsMap| is only used in the main thread, what is
|
|
|
+// shared between threads is the memory of HWND, so there is no need to use lock
|
|
|
+// when accessing dialogs map.
|
|
|
+base::Lock& GetHWNDLock() {
|
|
|
+ static base::NoDestructor<base::Lock> lock;
|
|
|
+ return *lock;
|
|
|
+}
|
|
|
+
|
|
|
// Small command ID values are already taken by Windows, we have to start from
|
|
|
// a large number to avoid conflicts with Windows.
|
|
|
const int kIDStart = 100;
|
|
@@ -76,6 +114,31 @@ void MapToCommonID(const std::vector<std::wstring>& buttons,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// Callback of the task dialog. The TaskDialogIndirect API does not provide the
|
|
|
+// HWND of the dialog, and we have to listen to the TDN_CREATED message to get
|
|
|
+// it.
|
|
|
+// Note that this callback runs in dialog thread instead of main thread, so it
|
|
|
+// is possible for CloseMessageBox to be called before or all after the dialog
|
|
|
+// window is created.
|
|
|
+HRESULT CALLBACK
|
|
|
+TaskDialogCallback(HWND hwnd, UINT msg, WPARAM, LPARAM, LONG_PTR data) {
|
|
|
+ if (msg == TDN_CREATED) {
|
|
|
+ HWND* target = reinterpret_cast<HWND*>(data);
|
|
|
+ // Lock since CloseMessageBox might be called.
|
|
|
+ base::AutoLock lock(GetHWNDLock());
|
|
|
+ if (*target == kHwndCancel) {
|
|
|
+ // The dialog is cancelled before it is created, close it directly.
|
|
|
+ ::PostMessage(hwnd, WM_CLOSE, 0, 0);
|
|
|
+ } else if (*target == kHwndReserve) {
|
|
|
+ // Otherwise save the hwnd.
|
|
|
+ *target = hwnd;
|
|
|
+ } else {
|
|
|
+ NOTREACHED();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return S_OK;
|
|
|
+}
|
|
|
+
|
|
|
DialogResult ShowTaskDialogWstr(NativeWindow* parent,
|
|
|
MessageBoxType type,
|
|
|
const std::vector<std::wstring>& buttons,
|
|
@@ -87,7 +150,8 @@ DialogResult ShowTaskDialogWstr(NativeWindow* parent,
|
|
|
const std::u16string& detail,
|
|
|
const std::u16string& checkbox_label,
|
|
|
bool checkbox_checked,
|
|
|
- const gfx::ImageSkia& icon) {
|
|
|
+ const gfx::ImageSkia& icon,
|
|
|
+ HWND* hwnd) {
|
|
|
TASKDIALOG_FLAGS flags =
|
|
|
TDF_SIZE_TO_CONTENT | // Show all content.
|
|
|
TDF_ALLOW_DIALOG_CANCELLATION; // Allow canceling the dialog.
|
|
@@ -169,12 +233,17 @@ DialogResult ShowTaskDialogWstr(NativeWindow* parent,
|
|
|
config.dwFlags |= TDF_USE_COMMAND_LINKS; // custom buttons as links.
|
|
|
}
|
|
|
|
|
|
- int button_id;
|
|
|
+ // Pass a callback to receive the HWND of the message box.
|
|
|
+ if (hwnd) {
|
|
|
+ config.pfCallback = &TaskDialogCallback;
|
|
|
+ config.lpCallbackData = reinterpret_cast<LONG_PTR>(hwnd);
|
|
|
+ }
|
|
|
|
|
|
int id = 0;
|
|
|
- BOOL verificationFlagChecked = FALSE;
|
|
|
- TaskDialogIndirect(&config, &id, nullptr, &verificationFlagChecked);
|
|
|
+ BOOL verification_flag_checked = FALSE;
|
|
|
+ TaskDialogIndirect(&config, &id, nullptr, &verification_flag_checked);
|
|
|
|
|
|
+ int button_id;
|
|
|
if (id_map.find(id) != id_map.end()) // common button.
|
|
|
button_id = id_map[id];
|
|
|
else if (id >= kIDStart) // custom button.
|
|
@@ -182,10 +251,11 @@ DialogResult ShowTaskDialogWstr(NativeWindow* parent,
|
|
|
else
|
|
|
button_id = cancel_id;
|
|
|
|
|
|
- return std::make_pair(button_id, verificationFlagChecked);
|
|
|
+ return {button_id, static_cast<bool>(verification_flag_checked)};
|
|
|
}
|
|
|
|
|
|
-DialogResult ShowTaskDialogUTF8(const MessageBoxSettings& settings) {
|
|
|
+DialogResult ShowTaskDialogUTF8(const MessageBoxSettings& settings,
|
|
|
+ HWND* hwnd) {
|
|
|
std::vector<std::wstring> buttons;
|
|
|
for (const auto& button : settings.buttons)
|
|
|
buttons.push_back(base::UTF8ToWide(button));
|
|
@@ -199,31 +269,67 @@ DialogResult ShowTaskDialogUTF8(const MessageBoxSettings& settings) {
|
|
|
return ShowTaskDialogWstr(
|
|
|
settings.parent_window, settings.type, buttons, settings.default_id,
|
|
|
settings.cancel_id, settings.no_link, title, message, detail,
|
|
|
- checkbox_label, settings.checkbox_checked, settings.icon);
|
|
|
+ checkbox_label, settings.checkbox_checked, settings.icon, hwnd);
|
|
|
}
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
int ShowMessageBoxSync(const MessageBoxSettings& settings) {
|
|
|
electron::UnresponsiveSuppressor suppressor;
|
|
|
- DialogResult result = ShowTaskDialogUTF8(settings);
|
|
|
- return result.first;
|
|
|
+ DialogResult result = ShowTaskDialogUTF8(settings, nullptr);
|
|
|
+ return result.button_id;
|
|
|
}
|
|
|
|
|
|
void ShowMessageBox(const MessageBoxSettings& settings,
|
|
|
MessageBoxCallback callback) {
|
|
|
- dialog_thread::Run(base::BindOnce(&ShowTaskDialogUTF8, settings),
|
|
|
- base::BindOnce(
|
|
|
- [](MessageBoxCallback callback, DialogResult result) {
|
|
|
- std::move(callback).Run(result.first, result.second);
|
|
|
- },
|
|
|
- std::move(callback)));
|
|
|
+ // The dialog is created in a new thread so we don't know its HWND yet, put
|
|
|
+ // kHwndReserve in the dialogs map for now.
|
|
|
+ HWND* hwnd = nullptr;
|
|
|
+ if (settings.id) {
|
|
|
+ if (base::Contains(GetDialogsMap(), *settings.id))
|
|
|
+ CloseMessageBox(*settings.id);
|
|
|
+ auto it = GetDialogsMap().emplace(*settings.id,
|
|
|
+ std::make_unique<HWND>(kHwndReserve));
|
|
|
+ hwnd = it.first->second.get();
|
|
|
+ }
|
|
|
+
|
|
|
+ dialog_thread::Run(
|
|
|
+ base::BindOnce(&ShowTaskDialogUTF8, settings, base::Unretained(hwnd)),
|
|
|
+ base::BindOnce(
|
|
|
+ [](MessageBoxCallback callback, absl::optional<int> id,
|
|
|
+ DialogResult result) {
|
|
|
+ if (id)
|
|
|
+ GetDialogsMap().erase(*id);
|
|
|
+ std::move(callback).Run(result.button_id,
|
|
|
+ result.verification_flag_checked);
|
|
|
+ },
|
|
|
+ std::move(callback), settings.id));
|
|
|
+}
|
|
|
+
|
|
|
+void CloseMessageBox(int id) {
|
|
|
+ auto it = GetDialogsMap().find(id);
|
|
|
+ if (it == GetDialogsMap().end()) {
|
|
|
+ LOG(ERROR) << "CloseMessageBox called with nonexistent ID";
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ HWND* hwnd = it->second.get();
|
|
|
+ // Lock since the TaskDialogCallback might be saving the dialog's HWND.
|
|
|
+ base::AutoLock lock(GetHWNDLock());
|
|
|
+ DCHECK(*hwnd != kHwndCancel);
|
|
|
+ if (*hwnd == kHwndReserve) {
|
|
|
+ // If the dialog window has not been created yet, tell it to cancel.
|
|
|
+ *hwnd = kHwndCancel;
|
|
|
+ } else {
|
|
|
+ // Otherwise send a message to close it.
|
|
|
+ ::PostMessage(*hwnd, WM_CLOSE, 0, 0);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
void ShowErrorBox(const std::u16string& title, const std::u16string& content) {
|
|
|
electron::UnresponsiveSuppressor suppressor;
|
|
|
ShowTaskDialogWstr(nullptr, MessageBoxType::kError, {}, -1, 0, false,
|
|
|
- u"Error", title, content, u"", false, gfx::ImageSkia());
|
|
|
+ u"Error", title, content, u"", false, gfx::ImageSkia(),
|
|
|
+ nullptr);
|
|
|
}
|
|
|
|
|
|
} // namespace electron
|