summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/client/crashreporter_win.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/crashreporter/client/crashreporter_win.cpp')
-rw-r--r--toolkit/crashreporter/client/crashreporter_win.cpp1568
1 files changed, 1568 insertions, 0 deletions
diff --git a/toolkit/crashreporter/client/crashreporter_win.cpp b/toolkit/crashreporter/client/crashreporter_win.cpp
new file mode 100644
index 000000000..57ca495ba
--- /dev/null
+++ b/toolkit/crashreporter/client/crashreporter_win.cpp
@@ -0,0 +1,1568 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifdef WIN32_LEAN_AND_MEAN
+#undef WIN32_LEAN_AND_MEAN
+#endif
+
+#include "crashreporter.h"
+
+#include <windows.h>
+#include <versionhelpers.h>
+#include <commctrl.h>
+#include <richedit.h>
+#include <shellapi.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <math.h>
+#include <set>
+#include <algorithm>
+#include "resource.h"
+#include "client/windows/sender/crash_report_sender.h"
+#include "common/windows/string_utils-inl.h"
+
+#define CRASH_REPORTER_VALUE L"Enabled"
+#define SUBMIT_REPORT_VALUE L"SubmitCrashReport"
+#define SUBMIT_REPORT_OLD L"SubmitReport"
+#define INCLUDE_URL_VALUE L"IncludeURL"
+#define EMAIL_ME_VALUE L"EmailMe"
+#define EMAIL_VALUE L"Email"
+#define MAX_EMAIL_LENGTH 1024
+
+#define SENDURL_ORIGINAL L"https://crash-reports.mozilla.com/submit"
+#define SENDURL_XPSP2 L"https://crash-reports-xpsp2.mozilla.com/submit"
+
+#define WM_UPLOADCOMPLETE WM_APP
+
+// Thanks, Windows.h :(
+#undef min
+#undef max
+
+using std::string;
+using std::wstring;
+using std::map;
+using std::vector;
+using std::set;
+using std::ios;
+using std::ifstream;
+using std::ofstream;
+
+using namespace CrashReporter;
+
+typedef struct {
+ HWND hDlg;
+ map<wstring,wstring> queryParameters;
+ map<wstring,wstring> files;
+ wstring sendURL;
+
+ wstring serverResponse;
+} SendThreadData;
+
+/*
+ * Per http://msdn2.microsoft.com/en-us/library/ms645398(VS.85).aspx
+ * "The DLGTEMPLATEEX structure is not defined in any standard header file.
+ * The structure definition is provided here to explain the format of an
+ * extended template for a dialog box.
+*/
+typedef struct {
+ WORD dlgVer;
+ WORD signature;
+ DWORD helpID;
+ DWORD exStyle;
+ // There's more to this struct, but it has weird variable-length
+ // members, and I only actually need to touch exStyle on an existing
+ // instance, so I've omitted the rest.
+} DLGTEMPLATEEX;
+
+static HANDLE gThreadHandle;
+static SendThreadData gSendData = { 0, };
+static vector<string> gRestartArgs;
+static map<wstring,wstring> gQueryParameters;
+static wstring gCrashReporterKey(L"Software\\Mozilla\\Crash Reporter");
+static wstring gURLParameter;
+static int gCheckboxPadding = 6;
+static bool gRTLlayout = false;
+
+// When vertically resizing the dialog, these items should move down
+static set<UINT> gAttachedBottom;
+
+// Default set of items for gAttachedBottom
+static const UINT kDefaultAttachedBottom[] = {
+ IDC_SUBMITREPORTCHECK,
+ IDC_VIEWREPORTBUTTON,
+ IDC_COMMENTTEXT,
+ IDC_INCLUDEURLCHECK,
+ IDC_EMAILMECHECK,
+ IDC_EMAILTEXT,
+ IDC_PROGRESSTEXT,
+ IDC_THROBBER,
+ IDC_CLOSEBUTTON,
+ IDC_RESTARTBUTTON,
+};
+
+static wstring UTF8ToWide(const string& utf8, bool *success = 0);
+static DWORD WINAPI SendThreadProc(LPVOID param);
+
+static wstring Str(const char* key)
+{
+ return UTF8ToWide(gStrings[key]);
+}
+
+/* === win32 helper functions === */
+
+static void DoInitCommonControls()
+{
+ INITCOMMONCONTROLSEX ic;
+ ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
+ ic.dwICC = ICC_PROGRESS_CLASS;
+ InitCommonControlsEx(&ic);
+ // also get the rich edit control
+ LoadLibrary(L"Msftedit.dll");
+}
+
+static bool GetBoolValue(HKEY hRegKey, LPCTSTR valueName, DWORD* value)
+{
+ DWORD type, dataSize;
+ dataSize = sizeof(DWORD);
+ if (RegQueryValueEx(hRegKey, valueName, nullptr,
+ &type, (LPBYTE)value, &dataSize) == ERROR_SUCCESS &&
+ type == REG_DWORD)
+ return true;
+
+ return false;
+}
+
+// Removes a value from HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER, if it exists.
+static void RemoveUnusedValues(const wchar_t* key, LPCTSTR valueName)
+{
+ HKEY hRegKey;
+
+ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_SET_VALUE, &hRegKey)
+ == ERROR_SUCCESS) {
+ RegDeleteValue(hRegKey, valueName);
+ RegCloseKey(hRegKey);
+ }
+
+ if (RegOpenKeyEx(HKEY_CURRENT_USER, key, 0, KEY_SET_VALUE, &hRegKey)
+ == ERROR_SUCCESS) {
+ RegDeleteValue(hRegKey, valueName);
+ RegCloseKey(hRegKey);
+ }
+}
+
+static bool CheckBoolKey(const wchar_t* key,
+ const wchar_t* valueName,
+ bool* enabled)
+{
+ /*
+ * NOTE! This code needs to stay in sync with the preference checking
+ * code in in nsExceptionHandler.cpp.
+ */
+ *enabled = false;
+ bool found = false;
+ HKEY hRegKey;
+ DWORD val;
+ // see if our reg key is set globally
+ if (RegOpenKey(HKEY_LOCAL_MACHINE, key, &hRegKey) == ERROR_SUCCESS) {
+ if (GetBoolValue(hRegKey, valueName, &val)) {
+ *enabled = (val == 1);
+ found = true;
+ }
+ RegCloseKey(hRegKey);
+ } else {
+ // look for it in user settings
+ if (RegOpenKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) {
+ if (GetBoolValue(hRegKey, valueName, &val)) {
+ *enabled = (val == 1);
+ found = true;
+ }
+ RegCloseKey(hRegKey);
+ }
+ }
+
+ return found;
+}
+
+static void SetBoolKey(const wchar_t* key, const wchar_t* value, bool enabled)
+{
+ /*
+ * NOTE! This code needs to stay in sync with the preference setting
+ * code in in nsExceptionHandler.cpp.
+ */
+ HKEY hRegKey;
+
+ // remove the old value from the registry if it exists
+ RemoveUnusedValues(key, SUBMIT_REPORT_OLD);
+
+ if (RegCreateKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) {
+ DWORD data = (enabled ? 1 : 0);
+ RegSetValueEx(hRegKey, value, 0, REG_DWORD, (LPBYTE)&data, sizeof(data));
+ RegCloseKey(hRegKey);
+ }
+}
+
+static bool GetStringValue(HKEY hRegKey, LPCTSTR valueName, wstring& value)
+{
+ DWORD type, dataSize;
+ wchar_t buf[2048];
+ dataSize = sizeof(buf);
+ if (RegQueryValueEx(hRegKey, valueName, nullptr,
+ &type, (LPBYTE)buf, &dataSize) == ERROR_SUCCESS &&
+ type == REG_SZ) {
+ value = buf;
+ return true;
+ }
+
+ return false;
+}
+
+static bool GetStringKey(const wchar_t* key,
+ const wchar_t* valueName,
+ wstring& value)
+{
+ value = L"";
+ bool found = false;
+ HKEY hRegKey;
+ // see if our reg key is set globally
+ if (RegOpenKey(HKEY_LOCAL_MACHINE, key, &hRegKey) == ERROR_SUCCESS) {
+ if (GetStringValue(hRegKey, valueName, value)) {
+ found = true;
+ }
+ RegCloseKey(hRegKey);
+ } else {
+ // look for it in user settings
+ if (RegOpenKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) {
+ if (GetStringValue(hRegKey, valueName, value)) {
+ found = true;
+ }
+ RegCloseKey(hRegKey);
+ }
+ }
+
+ return found;
+}
+
+static void SetStringKey(const wchar_t* key,
+ const wchar_t* valueName,
+ const wstring& value)
+{
+ HKEY hRegKey;
+ if (RegCreateKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) {
+ RegSetValueEx(hRegKey, valueName, 0, REG_SZ,
+ (LPBYTE)value.c_str(),
+ (value.length() + 1) * sizeof(wchar_t));
+ RegCloseKey(hRegKey);
+ }
+}
+
+static string FormatLastError()
+{
+ DWORD err = GetLastError();
+ LPWSTR s;
+ string message = "Crash report submission failed: ";
+ // odds are it's a WinInet error
+ HANDLE hInetModule = GetModuleHandle(L"WinInet.dll");
+ if(FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_FROM_HMODULE,
+ hInetModule,
+ err,
+ 0,
+ (LPWSTR)&s,
+ 0,
+ nullptr) != 0) {
+ message += WideToUTF8(s, nullptr);
+ LocalFree(s);
+ // strip off any trailing newlines
+ string::size_type n = message.find_last_not_of("\r\n");
+ if (n < message.size() - 1) {
+ message.erase(n+1);
+ }
+ }
+ else {
+ char buf[64];
+ sprintf(buf, "Unknown error, error code: 0x%08x", err);
+ message += buf;
+ }
+ return message;
+}
+
+#define TS_DRAW 2
+#define BP_CHECKBOX 3
+
+typedef HANDLE (WINAPI*OpenThemeDataPtr)(HWND hwnd, LPCWSTR pszClassList);
+typedef HRESULT (WINAPI*CloseThemeDataPtr)(HANDLE hTheme);
+typedef HRESULT (WINAPI*GetThemePartSizePtr)(HANDLE hTheme, HDC hdc, int iPartId,
+ int iStateId, RECT* prc, int ts,
+ SIZE* psz);
+typedef HRESULT (WINAPI*GetThemeContentRectPtr)(HANDLE hTheme, HDC hdc, int iPartId,
+ int iStateId, const RECT* pRect,
+ RECT* pContentRect);
+
+
+static void GetThemeSizes(HWND hwnd)
+{
+ HMODULE themeDLL = LoadLibrary(L"uxtheme.dll");
+
+ if (!themeDLL)
+ return;
+
+ OpenThemeDataPtr openTheme =
+ (OpenThemeDataPtr)GetProcAddress(themeDLL, "OpenThemeData");
+ CloseThemeDataPtr closeTheme =
+ (CloseThemeDataPtr)GetProcAddress(themeDLL, "CloseThemeData");
+ GetThemePartSizePtr getThemePartSize =
+ (GetThemePartSizePtr)GetProcAddress(themeDLL, "GetThemePartSize");
+
+ if (!openTheme || !closeTheme || !getThemePartSize) {
+ FreeLibrary(themeDLL);
+ return;
+ }
+
+ HANDLE buttonTheme = openTheme(hwnd, L"Button");
+ if (!buttonTheme) {
+ FreeLibrary(themeDLL);
+ return;
+ }
+ HDC hdc = GetDC(hwnd);
+ SIZE s;
+ getThemePartSize(buttonTheme, hdc, BP_CHECKBOX, 0, nullptr, TS_DRAW, &s);
+ gCheckboxPadding = s.cx;
+ closeTheme(buttonTheme);
+ FreeLibrary(themeDLL);
+}
+
+// Gets the position of a window relative to another window's client area
+static void GetRelativeRect(HWND hwnd, HWND hwndParent, RECT* r)
+{
+ GetWindowRect(hwnd, r);
+ MapWindowPoints(nullptr, hwndParent, (POINT*)r, 2);
+}
+
+static void SetDlgItemVisible(HWND hwndDlg, UINT item, bool visible)
+{
+ HWND hwnd = GetDlgItem(hwndDlg, item);
+
+ ShowWindow(hwnd, visible ? SW_SHOW : SW_HIDE);
+}
+
+static void SetDlgItemDisabled(HWND hwndDlg, UINT item, bool disabled)
+{
+ HWND hwnd = GetDlgItem(hwndDlg, item);
+ LONG style = GetWindowLong(hwnd, GWL_STYLE);
+ if (!disabled)
+ style |= WS_DISABLED;
+ else
+ style &= ~WS_DISABLED;
+
+ SetWindowLong(hwnd, GWL_STYLE, style);
+}
+
+/* === Crash Reporting Dialog === */
+
+static void StretchDialog(HWND hwndDlg, int ydiff)
+{
+ RECT r;
+ GetWindowRect(hwndDlg, &r);
+ r.bottom += ydiff;
+ MoveWindow(hwndDlg, r.left, r.top,
+ r.right - r.left, r.bottom - r.top, TRUE);
+}
+
+static void ReflowDialog(HWND hwndDlg, int ydiff)
+{
+ // Move items attached to the bottom down/up by as much as
+ // the window resize
+ for (set<UINT>::const_iterator item = gAttachedBottom.begin();
+ item != gAttachedBottom.end();
+ item++) {
+ RECT r;
+ HWND hwnd = GetDlgItem(hwndDlg, *item);
+ GetRelativeRect(hwnd, hwndDlg, &r);
+ r.top += ydiff;
+ r.bottom += ydiff;
+ MoveWindow(hwnd, r.left, r.top,
+ r.right - r.left, r.bottom - r.top, TRUE);
+ }
+}
+
+static DWORD WINAPI SendThreadProc(LPVOID param)
+{
+ bool finishedOk;
+ SendThreadData* td = (SendThreadData*)param;
+
+ if (td->sendURL.empty()) {
+ finishedOk = false;
+ LogMessage("No server URL, not sending report");
+ } else {
+ google_breakpad::CrashReportSender sender(L"");
+ finishedOk = (sender.SendCrashReport(td->sendURL,
+ td->queryParameters,
+ td->files,
+ &td->serverResponse)
+ == google_breakpad::RESULT_SUCCEEDED);
+ if (finishedOk) {
+ LogMessage("Crash report submitted successfully");
+ }
+ else {
+ // get an error string and print it to the log
+ //XXX: would be nice to get the HTTP status code here, filed:
+ // http://code.google.com/p/google-breakpad/issues/detail?id=220
+ LogMessage(FormatLastError());
+ }
+ }
+
+ PostMessage(td->hDlg, WM_UPLOADCOMPLETE, finishedOk ? 1 : 0, 0);
+
+ return 0;
+}
+
+static void EndCrashReporterDialog(HWND hwndDlg, int code)
+{
+ // Save the current values to the registry
+ wchar_t email[MAX_EMAIL_LENGTH];
+ GetDlgItemTextW(hwndDlg, IDC_EMAILTEXT, email,
+ sizeof(email) / sizeof(email[0]));
+ SetStringKey(gCrashReporterKey.c_str(), EMAIL_VALUE, email);
+
+ SetBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE,
+ IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK) != 0);
+ SetBoolKey(gCrashReporterKey.c_str(), EMAIL_ME_VALUE,
+ IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK) != 0);
+ SetBoolKey(gCrashReporterKey.c_str(), SUBMIT_REPORT_VALUE,
+ IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0);
+
+ EndDialog(hwndDlg, code);
+}
+
+static void MaybeResizeProgressText(HWND hwndDlg)
+{
+ HWND hwndProgress = GetDlgItem(hwndDlg, IDC_PROGRESSTEXT);
+ HDC hdc = GetDC(hwndProgress);
+ HFONT hfont = (HFONT)SendMessage(hwndProgress, WM_GETFONT, 0, 0);
+ if (hfont)
+ SelectObject(hdc, hfont);
+ SIZE size;
+ RECT rect;
+ GetRelativeRect(hwndProgress, hwndDlg, &rect);
+
+ wchar_t text[1024];
+ GetWindowText(hwndProgress, text, 1024);
+
+ if (!GetTextExtentPoint32(hdc, text, wcslen(text), &size))
+ return;
+
+ if (size.cx < (rect.right - rect.left))
+ return;
+
+ // Figure out how much we need to resize things vertically
+ // This is sort of a fudge, but it should be good enough.
+ int wantedHeight = size.cy *
+ (int)ceil((float)size.cx / (float)(rect.right - rect.left));
+ int diff = wantedHeight - (rect.bottom - rect.top);
+ if (diff <= 0)
+ return;
+
+ MoveWindow(hwndProgress, rect.left, rect.top,
+ rect.right - rect.left,
+ wantedHeight,
+ TRUE);
+
+ gAttachedBottom.clear();
+ gAttachedBottom.insert(IDC_CLOSEBUTTON);
+ gAttachedBottom.insert(IDC_RESTARTBUTTON);
+
+ StretchDialog(hwndDlg, diff);
+
+ for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) {
+ gAttachedBottom.insert(kDefaultAttachedBottom[i]);
+ }
+}
+
+static void MaybeSendReport(HWND hwndDlg)
+{
+ if (!IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK)) {
+ EndCrashReporterDialog(hwndDlg, 0);
+ return;
+ }
+
+ // disable all the form controls
+ EnableWindow(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK), false);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), false);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), false);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), false);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), false);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), false);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CLOSEBUTTON), false);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTBUTTON), false);
+
+ SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, Str(ST_REPORTDURINGSUBMIT).c_str());
+ MaybeResizeProgressText(hwndDlg);
+ // start throbber
+ // play entire AVI, and loop
+ Animate_Play(GetDlgItem(hwndDlg, IDC_THROBBER), 0, -1, -1);
+ SetDlgItemVisible(hwndDlg, IDC_THROBBER, true);
+ gThreadHandle = nullptr;
+ gSendData.hDlg = hwndDlg;
+ gSendData.queryParameters = gQueryParameters;
+
+ gThreadHandle = CreateThread(nullptr, 0, SendThreadProc, &gSendData, 0,
+ nullptr);
+}
+
+static void RestartApplication()
+{
+ wstring cmdLine;
+
+ for (unsigned int i = 0; i < gRestartArgs.size(); i++) {
+ cmdLine += L"\"" + UTF8ToWide(gRestartArgs[i]) + L"\" ";
+ }
+
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_SHOWNORMAL;
+ ZeroMemory(&pi, sizeof(pi));
+
+ if (CreateProcess(nullptr, (LPWSTR)cmdLine.c_str(), nullptr, nullptr, FALSE,
+ 0, nullptr, nullptr, &si, &pi)) {
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+}
+
+static void ShowReportInfo(HWND hwndDlg)
+{
+ wstring description;
+
+ for (map<wstring,wstring>::const_iterator i = gQueryParameters.begin();
+ i != gQueryParameters.end();
+ i++) {
+ description += i->first;
+ description += L": ";
+ description += i->second;
+ description += L"\n";
+ }
+
+ description += L"\n";
+ description += Str(ST_EXTRAREPORTINFO);
+
+ SetDlgItemText(hwndDlg, IDC_VIEWREPORTTEXT, description.c_str());
+}
+
+static void UpdateURL(HWND hwndDlg)
+{
+ if (IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK)) {
+ gQueryParameters[L"URL"] = gURLParameter;
+ } else {
+ gQueryParameters.erase(L"URL");
+ }
+}
+
+static void UpdateEmail(HWND hwndDlg)
+{
+ if (IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK)) {
+ wchar_t email[MAX_EMAIL_LENGTH];
+ GetDlgItemTextW(hwndDlg, IDC_EMAILTEXT, email,
+ sizeof(email) / sizeof(email[0]));
+ gQueryParameters[L"Email"] = email;
+ if (IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK))
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), true);
+ } else {
+ gQueryParameters.erase(L"Email");
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), false);
+ }
+}
+
+static void UpdateComment(HWND hwndDlg)
+{
+ wchar_t comment[MAX_COMMENT_LENGTH + 1];
+ GetDlgItemTextW(hwndDlg, IDC_COMMENTTEXT, comment,
+ sizeof(comment) / sizeof(comment[0]));
+ if (wcslen(comment) > 0)
+ gQueryParameters[L"Comments"] = comment;
+ else
+ gQueryParameters.erase(L"Comments");
+}
+
+/*
+ * Dialog procedure for the "view report" dialog.
+ */
+static BOOL CALLBACK ViewReportDialogProc(HWND hwndDlg, UINT message,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (message) {
+ case WM_INITDIALOG: {
+ SetWindowText(hwndDlg, Str(ST_VIEWREPORTTITLE).c_str());
+ SetDlgItemText(hwndDlg, IDOK, Str(ST_OK).c_str());
+ SendDlgItemMessage(hwndDlg, IDC_VIEWREPORTTEXT,
+ EM_SETTARGETDEVICE, (WPARAM)nullptr, 0);
+ ShowReportInfo(hwndDlg);
+ SetFocus(GetDlgItem(hwndDlg, IDOK));
+ return FALSE;
+ }
+
+ case WM_COMMAND: {
+ if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDOK)
+ EndDialog(hwndDlg, 0);
+ return FALSE;
+ }
+ }
+ return FALSE;
+}
+
+// Return the number of bytes this string will take encoded
+// in UTF-8
+static inline int BytesInUTF8(wchar_t* str)
+{
+ // Just count size of buffer for UTF-8, minus one
+ // (we don't need to count the null terminator)
+ return WideCharToMultiByte(CP_UTF8, 0, str, -1,
+ nullptr, 0, nullptr, nullptr) - 1;
+}
+
+// Calculate the length of the text in this edit control (in bytes,
+// in the UTF-8 encoding) after replacing the current selection
+// with |insert|.
+static int NewTextLength(HWND hwndEdit, wchar_t* insert)
+{
+ wchar_t current[MAX_COMMENT_LENGTH + 1];
+
+ GetWindowText(hwndEdit, current, MAX_COMMENT_LENGTH + 1);
+ DWORD selStart, selEnd;
+ SendMessage(hwndEdit, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd);
+
+ int selectionLength = 0;
+ if (selEnd - selStart > 0) {
+ wchar_t selection[MAX_COMMENT_LENGTH + 1];
+ google_breakpad::WindowsStringUtils::safe_wcsncpy(selection,
+ MAX_COMMENT_LENGTH + 1,
+ current + selStart,
+ selEnd - selStart);
+ selection[selEnd - selStart] = '\0';
+ selectionLength = BytesInUTF8(selection);
+ }
+
+ // current string length + replacement text length
+ // - replaced selection length
+ return BytesInUTF8(current) + BytesInUTF8(insert) - selectionLength;
+}
+
+// Window procedure for subclassing edit controls
+static LRESULT CALLBACK EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+ LPARAM lParam)
+{
+ static WNDPROC super = nullptr;
+
+ if (super == nullptr)
+ super = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+
+ switch (uMsg) {
+ case WM_PAINT: {
+ HDC hdc;
+ PAINTSTRUCT ps;
+ RECT r;
+ wchar_t windowText[1024];
+
+ GetWindowText(hwnd, windowText, 1024);
+ // if the control contains text or is focused, draw it normally
+ if (GetFocus() == hwnd || windowText[0] != '\0')
+ return CallWindowProc(super, hwnd, uMsg, wParam, lParam);
+
+ GetClientRect(hwnd, &r);
+ hdc = BeginPaint(hwnd, &ps);
+ FillRect(hdc, &r, GetSysColorBrush(IsWindowEnabled(hwnd)
+ ? COLOR_WINDOW : COLOR_BTNFACE));
+ SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT));
+ SelectObject(hdc, (HFONT)GetStockObject(DEFAULT_GUI_FONT));
+ SetBkMode(hdc, TRANSPARENT);
+ wchar_t* txt = (wchar_t*)GetProp(hwnd, L"PROP_GRAYTEXT");
+ // Get the actual edit control rect
+ CallWindowProc(super, hwnd, EM_GETRECT, 0, (LPARAM)&r);
+ UINT format = DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK | DT_INTERNAL;
+ if (gRTLlayout)
+ format |= DT_RIGHT;
+ if (txt)
+ DrawText(hdc, txt, wcslen(txt), &r, format);
+ EndPaint(hwnd, &ps);
+ return 0;
+ }
+
+ // We handle WM_CHAR and WM_PASTE to limit the comment box to 500
+ // bytes in UTF-8.
+ case WM_CHAR: {
+ // Leave accelerator keys and non-printing chars (except LF) alone
+ if (wParam & (1<<24) || wParam & (1<<29) ||
+ (wParam < ' ' && wParam != '\n'))
+ break;
+
+ wchar_t ch[2] = { (wchar_t)wParam, 0 };
+ if (NewTextLength(hwnd, ch) > MAX_COMMENT_LENGTH)
+ return 0;
+
+ break;
+ }
+
+ case WM_PASTE: {
+ if (IsClipboardFormatAvailable(CF_UNICODETEXT) &&
+ OpenClipboard(hwnd)) {
+ HGLOBAL hg = GetClipboardData(CF_UNICODETEXT);
+ wchar_t* pastedText = (wchar_t*)GlobalLock(hg);
+ int newSize = 0;
+
+ if (pastedText)
+ newSize = NewTextLength(hwnd, pastedText);
+
+ GlobalUnlock(hg);
+ CloseClipboard();
+
+ if (newSize > MAX_COMMENT_LENGTH)
+ return 0;
+ }
+ break;
+ }
+
+ case WM_SETFOCUS:
+ case WM_KILLFOCUS: {
+ RECT r;
+ GetClientRect(hwnd, &r);
+ InvalidateRect(hwnd, &r, TRUE);
+ break;
+ }
+
+ case WM_DESTROY: {
+ // cleanup our property
+ HGLOBAL hData = RemoveProp(hwnd, L"PROP_GRAYTEXT");
+ if (hData)
+ GlobalFree(hData);
+ }
+ }
+
+ return CallWindowProc(super, hwnd, uMsg, wParam, lParam);
+}
+
+// Resize a control to fit this text
+static int ResizeControl(HWND hwndButton, RECT& rect, wstring text,
+ bool shiftLeft, int userDefinedPadding)
+{
+ HDC hdc = GetDC(hwndButton);
+ HFONT hfont = (HFONT)SendMessage(hwndButton, WM_GETFONT, 0, 0);
+ if (hfont)
+ SelectObject(hdc, hfont);
+ SIZE size, oldSize;
+ int sizeDiff = 0;
+
+ wchar_t oldText[1024];
+ GetWindowText(hwndButton, oldText, 1024);
+
+ if (GetTextExtentPoint32(hdc, text.c_str(), text.length(), &size)
+ // default text on the button
+ && GetTextExtentPoint32(hdc, oldText, wcslen(oldText), &oldSize)) {
+ /*
+ Expand control widths to accomidate wider text strings. For most
+ controls (including buttons) the text padding is defined by the
+ dialog's rc file. Some controls (such as checkboxes) have padding
+ that extends to the end of the dialog, in which case we ignore the
+ rc padding and rely on a user defined value passed in through
+ userDefinedPadding.
+ */
+ int textIncrease = size.cx - oldSize.cx;
+ if (textIncrease < 0)
+ return 0;
+ int existingTextPadding;
+ if (userDefinedPadding == 0)
+ existingTextPadding = (rect.right - rect.left) - oldSize.cx;
+ else
+ existingTextPadding = userDefinedPadding;
+ sizeDiff = textIncrease + existingTextPadding;
+
+ if (shiftLeft) {
+ // shift left by the amount the button should grow
+ rect.left -= sizeDiff;
+ }
+ else {
+ // grow right instead
+ rect.right += sizeDiff;
+ }
+ MoveWindow(hwndButton, rect.left, rect.top,
+ rect.right - rect.left,
+ rect.bottom - rect.top,
+ TRUE);
+ }
+ return sizeDiff;
+}
+
+// The window was resized horizontally, so widen some of our
+// controls to make use of the space
+static void StretchControlsToFit(HWND hwndDlg)
+{
+ int controls[] = {
+ IDC_DESCRIPTIONTEXT,
+ IDC_SUBMITREPORTCHECK,
+ IDC_COMMENTTEXT,
+ IDC_INCLUDEURLCHECK,
+ IDC_EMAILMECHECK,
+ IDC_EMAILTEXT,
+ IDC_PROGRESSTEXT
+ };
+
+ RECT dlgRect;
+ GetClientRect(hwndDlg, &dlgRect);
+
+ for (int i=0; i<sizeof(controls)/sizeof(controls[0]); i++) {
+ RECT r;
+ HWND hwndControl = GetDlgItem(hwndDlg, controls[i]);
+ GetRelativeRect(hwndControl, hwndDlg, &r);
+ // 6 pixel spacing on the right
+ if (r.right + 6 != dlgRect.right) {
+ r.right = dlgRect.right - 6;
+ MoveWindow(hwndControl, r.left, r.top,
+ r.right - r.left,
+ r.bottom - r.top,
+ TRUE);
+ }
+ }
+}
+
+static void SubmitReportChecked(HWND hwndDlg)
+{
+ bool enabled = (IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT),
+ enabled && (IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK)
+ != 0));
+ SetDlgItemVisible(hwndDlg, IDC_PROGRESSTEXT, enabled);
+}
+
+static INT_PTR DialogBoxParamMaybeRTL(UINT idd, HWND hwndParent,
+ DLGPROC dlgProc, LPARAM param)
+{
+ INT_PTR rv = 0;
+ if (gRTLlayout) {
+ // We need to toggle the WS_EX_LAYOUTRTL style flag on the dialog
+ // template.
+ HRSRC hDialogRC = FindResource(nullptr, MAKEINTRESOURCE(idd),
+ RT_DIALOG);
+ HGLOBAL hDlgTemplate = LoadResource(nullptr, hDialogRC);
+ DLGTEMPLATEEX* pDlgTemplate = (DLGTEMPLATEEX*)LockResource(hDlgTemplate);
+ unsigned long sizeDlg = SizeofResource(nullptr, hDialogRC);
+ HGLOBAL hMyDlgTemplate = GlobalAlloc(GPTR, sizeDlg);
+ DLGTEMPLATEEX* pMyDlgTemplate =
+ (DLGTEMPLATEEX*)GlobalLock(hMyDlgTemplate);
+ memcpy(pMyDlgTemplate, pDlgTemplate, sizeDlg);
+
+ pMyDlgTemplate->exStyle |= WS_EX_LAYOUTRTL;
+
+ rv = DialogBoxIndirectParam(nullptr, (LPCDLGTEMPLATE)pMyDlgTemplate,
+ hwndParent, dlgProc, param);
+ GlobalUnlock(hMyDlgTemplate);
+ GlobalFree(hMyDlgTemplate);
+ }
+ else {
+ rv = DialogBoxParam(nullptr, MAKEINTRESOURCE(idd), hwndParent,
+ dlgProc, param);
+ }
+
+ return rv;
+}
+
+
+static BOOL CALLBACK CrashReporterDialogProc(HWND hwndDlg, UINT message,
+ WPARAM wParam, LPARAM lParam)
+{
+ static int sHeight = 0;
+
+ bool success;
+ bool enabled;
+
+ switch (message) {
+ case WM_INITDIALOG: {
+ GetThemeSizes(hwndDlg);
+ RECT r;
+ GetClientRect(hwndDlg, &r);
+ sHeight = r.bottom - r.top;
+
+ SetWindowText(hwndDlg, Str(ST_CRASHREPORTERTITLE).c_str());
+ HICON hIcon = LoadIcon(GetModuleHandle(nullptr),
+ MAKEINTRESOURCE(IDI_MAINICON));
+ SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
+ SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
+
+ // resize the "View Report" button based on the string length
+ RECT rect;
+ HWND hwnd = GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON);
+ GetRelativeRect(hwnd, hwndDlg, &rect);
+ ResizeControl(hwnd, rect, Str(ST_VIEWREPORT), false, 0);
+ SetDlgItemText(hwndDlg, IDC_VIEWREPORTBUTTON, Str(ST_VIEWREPORT).c_str());
+
+ hwnd = GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK);
+ GetRelativeRect(hwnd, hwndDlg, &rect);
+ long maxdiff = ResizeControl(hwnd, rect, Str(ST_CHECKSUBMIT), false,
+ gCheckboxPadding);
+ SetDlgItemText(hwndDlg, IDC_SUBMITREPORTCHECK,
+ Str(ST_CHECKSUBMIT).c_str());
+
+ if (!CheckBoolKey(gCrashReporterKey.c_str(),
+ SUBMIT_REPORT_VALUE, &enabled))
+ enabled = ShouldEnableSending();
+
+ CheckDlgButton(hwndDlg, IDC_SUBMITREPORTCHECK, enabled ? BST_CHECKED
+ : BST_UNCHECKED);
+ SubmitReportChecked(hwndDlg);
+
+ HWND hwndComment = GetDlgItem(hwndDlg, IDC_COMMENTTEXT);
+ WNDPROC OldWndProc = (WNDPROC)SetWindowLongPtr(hwndComment,
+ GWLP_WNDPROC,
+ (LONG_PTR)EditSubclassProc);
+
+ // Subclass comment edit control to get placeholder text
+ SetWindowLongPtr(hwndComment, GWLP_USERDATA, (LONG_PTR)OldWndProc);
+ wstring commentGrayText = Str(ST_COMMENTGRAYTEXT);
+ wchar_t* hMem = (wchar_t*)GlobalAlloc(GPTR, (commentGrayText.length() + 1)*sizeof(wchar_t));
+ wcscpy(hMem, commentGrayText.c_str());
+ SetProp(hwndComment, L"PROP_GRAYTEXT", hMem);
+
+ hwnd = GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK);
+ GetRelativeRect(hwnd, hwndDlg, &rect);
+ long diff = ResizeControl(hwnd, rect, Str(ST_CHECKURL), false,
+ gCheckboxPadding);
+ maxdiff = std::max(diff, maxdiff);
+ SetDlgItemText(hwndDlg, IDC_INCLUDEURLCHECK, Str(ST_CHECKURL).c_str());
+
+ // want this on by default
+ if (CheckBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE, &enabled) &&
+ !enabled) {
+ CheckDlgButton(hwndDlg, IDC_INCLUDEURLCHECK, BST_UNCHECKED);
+ } else {
+ CheckDlgButton(hwndDlg, IDC_INCLUDEURLCHECK, BST_CHECKED);
+ }
+
+ hwnd = GetDlgItem(hwndDlg, IDC_EMAILMECHECK);
+ GetRelativeRect(hwnd, hwndDlg, &rect);
+ diff = ResizeControl(hwnd, rect, Str(ST_CHECKEMAIL), false,
+ gCheckboxPadding);
+ maxdiff = std::max(diff, maxdiff);
+ SetDlgItemText(hwndDlg, IDC_EMAILMECHECK, Str(ST_CHECKEMAIL).c_str());
+
+ if (CheckBoolKey(gCrashReporterKey.c_str(), EMAIL_ME_VALUE, &enabled) &&
+ enabled) {
+ CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_CHECKED);
+ } else {
+ CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_UNCHECKED);
+ }
+
+ wstring email;
+ if (GetStringKey(gCrashReporterKey.c_str(), EMAIL_VALUE, email)) {
+ SetDlgItemText(hwndDlg, IDC_EMAILTEXT, email.c_str());
+ }
+
+ // Subclass email edit control to get placeholder text
+ HWND hwndEmail = GetDlgItem(hwndDlg, IDC_EMAILTEXT);
+ OldWndProc = (WNDPROC)SetWindowLongPtr(hwndEmail,
+ GWLP_WNDPROC,
+ (LONG_PTR)EditSubclassProc);
+ SetWindowLongPtr(hwndEmail, GWLP_USERDATA, (LONG_PTR)OldWndProc);
+ wstring emailGrayText = Str(ST_EMAILGRAYTEXT);
+ hMem = (wchar_t*)GlobalAlloc(GPTR, (emailGrayText.length() + 1)*sizeof(wchar_t));
+ wcscpy(hMem, emailGrayText.c_str());
+ SetProp(hwndEmail, L"PROP_GRAYTEXT", hMem);
+
+ SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, Str(ST_REPORTPRESUBMIT).c_str());
+
+ RECT closeRect;
+ HWND hwndClose = GetDlgItem(hwndDlg, IDC_CLOSEBUTTON);
+ GetRelativeRect(hwndClose, hwndDlg, &closeRect);
+
+ RECT restartRect;
+ HWND hwndRestart = GetDlgItem(hwndDlg, IDC_RESTARTBUTTON);
+ GetRelativeRect(hwndRestart, hwndDlg, &restartRect);
+
+ // set the close button text and shift the buttons around
+ // since the size may need to change
+ int sizeDiff = ResizeControl(hwndClose, closeRect, Str(ST_QUIT),
+ true, 0);
+ restartRect.left -= sizeDiff;
+ restartRect.right -= sizeDiff;
+ SetDlgItemText(hwndDlg, IDC_CLOSEBUTTON, Str(ST_QUIT).c_str());
+
+ if (gRestartArgs.size() > 0) {
+ // Resize restart button to fit text
+ ResizeControl(hwndRestart, restartRect, Str(ST_RESTART), true, 0);
+ SetDlgItemText(hwndDlg, IDC_RESTARTBUTTON, Str(ST_RESTART).c_str());
+ } else {
+ // No restart arguments, so just hide the restart button
+ SetDlgItemVisible(hwndDlg, IDC_RESTARTBUTTON, false);
+ }
+ // See if we need to widen the window
+ // Leave 6 pixels on either side + 6 pixels between the buttons
+ int neededSize = closeRect.right - closeRect.left +
+ restartRect.right - restartRect.left + 6 * 3;
+ GetClientRect(hwndDlg, &r);
+ // We may already have resized one of the checkboxes above
+ maxdiff = std::max(maxdiff, neededSize - (r.right - r.left));
+
+ if (maxdiff > 0) {
+ // widen window
+ GetWindowRect(hwndDlg, &r);
+ r.right += maxdiff;
+ MoveWindow(hwndDlg, r.left, r.top,
+ r.right - r.left, r.bottom - r.top, TRUE);
+ // shift both buttons right
+ if (restartRect.left + maxdiff < 6)
+ maxdiff += 6;
+ closeRect.left += maxdiff;
+ closeRect.right += maxdiff;
+ restartRect.left += maxdiff;
+ restartRect.right += maxdiff;
+ MoveWindow(hwndClose, closeRect.left, closeRect.top,
+ closeRect.right - closeRect.left,
+ closeRect.bottom - closeRect.top,
+ TRUE);
+ StretchControlsToFit(hwndDlg);
+ }
+ // need to move the restart button regardless
+ MoveWindow(hwndRestart, restartRect.left, restartRect.top,
+ restartRect.right - restartRect.left,
+ restartRect.bottom - restartRect.top,
+ TRUE);
+
+ // Resize the description text last, in case the window was resized
+ // before this.
+ SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT,
+ EM_SETEVENTMASK, (WPARAM)nullptr,
+ ENM_REQUESTRESIZE);
+
+ wstring description = Str(ST_CRASHREPORTERHEADER);
+ description += L"\n\n";
+ description += Str(ST_CRASHREPORTERDESCRIPTION);
+ SetDlgItemText(hwndDlg, IDC_DESCRIPTIONTEXT, description.c_str());
+
+
+ // Make the title bold.
+ CHARFORMAT fmt = { 0, };
+ fmt.cbSize = sizeof(fmt);
+ fmt.dwMask = CFM_BOLD;
+ fmt.dwEffects = CFE_BOLD;
+ SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL,
+ 0, Str(ST_CRASHREPORTERHEADER).length());
+ SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETCHARFORMAT,
+ SCF_SELECTION, (LPARAM)&fmt);
+ SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL, 0, 0);
+ // Force redraw.
+ SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT,
+ EM_SETTARGETDEVICE, (WPARAM)nullptr, 0);
+ // Force resize.
+ SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT,
+ EM_REQUESTRESIZE, 0, 0);
+
+ // if no URL was given, hide the URL checkbox
+ if (gQueryParameters.find(L"URL") == gQueryParameters.end()) {
+ RECT urlCheckRect, emailCheckRect;
+ GetWindowRect(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), &urlCheckRect);
+ GetWindowRect(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), &emailCheckRect);
+
+ SetDlgItemVisible(hwndDlg, IDC_INCLUDEURLCHECK, false);
+
+ gAttachedBottom.erase(IDC_VIEWREPORTBUTTON);
+ gAttachedBottom.erase(IDC_SUBMITREPORTCHECK);
+ gAttachedBottom.erase(IDC_COMMENTTEXT);
+
+ StretchDialog(hwndDlg, urlCheckRect.top - emailCheckRect.top);
+
+ gAttachedBottom.insert(IDC_VIEWREPORTBUTTON);
+ gAttachedBottom.insert(IDC_SUBMITREPORTCHECK);
+ gAttachedBottom.insert(IDC_COMMENTTEXT);
+ }
+
+ MaybeResizeProgressText(hwndDlg);
+
+ // Open the AVI resource for the throbber
+ Animate_Open(GetDlgItem(hwndDlg, IDC_THROBBER),
+ MAKEINTRESOURCE(IDR_THROBBER));
+
+ UpdateURL(hwndDlg);
+ UpdateEmail(hwndDlg);
+
+ SetFocus(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK));
+ return FALSE;
+ }
+ case WM_SIZE: {
+ ReflowDialog(hwndDlg, HIWORD(lParam) - sHeight);
+ sHeight = HIWORD(lParam);
+ InvalidateRect(hwndDlg, nullptr, TRUE);
+ return FALSE;
+ }
+ case WM_NOTIFY: {
+ NMHDR* notification = reinterpret_cast<NMHDR*>(lParam);
+ if (notification->code == EN_REQUESTRESIZE) {
+ // Resizing the rich edit control to fit the description text.
+ REQRESIZE* reqresize = reinterpret_cast<REQRESIZE*>(lParam);
+ RECT newSize = reqresize->rc;
+ RECT oldSize;
+ GetRelativeRect(notification->hwndFrom, hwndDlg, &oldSize);
+
+ // resize the text box as requested
+ MoveWindow(notification->hwndFrom, newSize.left, newSize.top,
+ newSize.right - newSize.left, newSize.bottom - newSize.top,
+ TRUE);
+
+ // Resize the dialog to fit (the WM_SIZE handler will move the controls)
+ StretchDialog(hwndDlg, newSize.bottom - oldSize.bottom);
+ }
+ return FALSE;
+ }
+ case WM_COMMAND: {
+ if (HIWORD(wParam) == BN_CLICKED) {
+ switch(LOWORD(wParam)) {
+ case IDC_VIEWREPORTBUTTON:
+ DialogBoxParamMaybeRTL(IDD_VIEWREPORTDIALOG, hwndDlg,
+ (DLGPROC)ViewReportDialogProc, 0);
+ break;
+ case IDC_SUBMITREPORTCHECK:
+ SubmitReportChecked(hwndDlg);
+ break;
+ case IDC_INCLUDEURLCHECK:
+ UpdateURL(hwndDlg);
+ break;
+ case IDC_EMAILMECHECK:
+ UpdateEmail(hwndDlg);
+ break;
+ case IDC_CLOSEBUTTON:
+ MaybeSendReport(hwndDlg);
+ break;
+ case IDC_RESTARTBUTTON:
+ RestartApplication();
+ MaybeSendReport(hwndDlg);
+ break;
+ }
+ } else if (HIWORD(wParam) == EN_CHANGE) {
+ switch(LOWORD(wParam)) {
+ case IDC_EMAILTEXT:
+ UpdateEmail(hwndDlg);
+ break;
+ case IDC_COMMENTTEXT:
+ UpdateComment(hwndDlg);
+ }
+ }
+
+ return FALSE;
+ }
+ case WM_UPLOADCOMPLETE: {
+ WaitForSingleObject(gThreadHandle, INFINITE);
+ success = (wParam == 1);
+ SendCompleted(success, WideToUTF8(gSendData.serverResponse));
+ // hide throbber
+ Animate_Stop(GetDlgItem(hwndDlg, IDC_THROBBER));
+ SetDlgItemVisible(hwndDlg, IDC_THROBBER, false);
+
+ SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT,
+ success ?
+ Str(ST_REPORTSUBMITSUCCESS).c_str() :
+ Str(ST_SUBMITFAILED).c_str());
+ MaybeResizeProgressText(hwndDlg);
+ // close dialog after 5 seconds
+ SetTimer(hwndDlg, 0, 5000, nullptr);
+ //
+ return TRUE;
+ }
+
+ case WM_LBUTTONDOWN: {
+ HWND hwndEmail = GetDlgItem(hwndDlg, IDC_EMAILTEXT);
+ POINT p = { LOWORD(lParam), HIWORD(lParam) };
+ // if the email edit control is clicked, enable it,
+ // check the email checkbox, and focus the email edit control
+ if (ChildWindowFromPoint(hwndDlg, p) == hwndEmail &&
+ IsWindowEnabled(GetDlgItem(hwndDlg, IDC_RESTARTBUTTON)) &&
+ !IsWindowEnabled(hwndEmail) &&
+ IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0) {
+ CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_CHECKED);
+ UpdateEmail(hwndDlg);
+ SetFocus(hwndEmail);
+ }
+ break;
+ }
+
+ case WM_TIMER: {
+ // The "1" gets used down in UIShowCrashUI to indicate that we at least
+ // tried to send the report.
+ EndCrashReporterDialog(hwndDlg, 1);
+ return FALSE;
+ }
+
+ case WM_CLOSE: {
+ EndCrashReporterDialog(hwndDlg, 0);
+ return FALSE;
+ }
+ }
+ return FALSE;
+}
+
+static wstring UTF8ToWide(const string& utf8, bool *success)
+{
+ wchar_t* buffer = nullptr;
+ int buffer_size = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(),
+ -1, nullptr, 0);
+ if(buffer_size == 0) {
+ if (success)
+ *success = false;
+ return L"";
+ }
+
+ buffer = new wchar_t[buffer_size];
+ if(buffer == nullptr) {
+ if (success)
+ *success = false;
+ return L"";
+ }
+
+ MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(),
+ -1, buffer, buffer_size);
+ wstring str = buffer;
+ delete [] buffer;
+
+ if (success)
+ *success = true;
+
+ return str;
+}
+
+static string WideToMBCP(const wstring& wide,
+ unsigned int cp,
+ bool* success = nullptr)
+{
+ char* buffer = nullptr;
+ int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(),
+ -1, nullptr, 0, nullptr, nullptr);
+ if(buffer_size == 0) {
+ if (success)
+ *success = false;
+ return "";
+ }
+
+ buffer = new char[buffer_size];
+ if(buffer == nullptr) {
+ if (success)
+ *success = false;
+ return "";
+ }
+
+ WideCharToMultiByte(cp, 0, wide.c_str(),
+ -1, buffer, buffer_size, nullptr, nullptr);
+ string mb = buffer;
+ delete [] buffer;
+
+ if (success)
+ *success = true;
+
+ return mb;
+}
+
+string WideToUTF8(const wstring& wide, bool* success)
+{
+ return WideToMBCP(wide, CP_UTF8, success);
+}
+
+/* === Crashreporter UI Functions === */
+
+bool UIInit()
+{
+ for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) {
+ gAttachedBottom.insert(kDefaultAttachedBottom[i]);
+ }
+
+ DoInitCommonControls();
+
+ return true;
+}
+
+void UIShutdown()
+{
+}
+
+void UIShowDefaultUI()
+{
+ MessageBox(nullptr, Str(ST_CRASHREPORTERDEFAULT).c_str(),
+ L"Crash Reporter",
+ MB_OK | MB_ICONSTOP);
+}
+
+static bool CanUseMainCrashReportServer()
+{
+ // Any NT from 6.0 and above is fine.
+ if (IsWindowsVersionOrGreater(6, 0, 0)) {
+ return true;
+ }
+
+ // On NT 5 servers, we need Server 2003 SP2.
+ if (IsWindowsServer()) {
+ return IsWindowsVersionOrGreater(5, 2, 2);
+ }
+
+ // Otherwise we have an NT 5 client.
+ // We need exactly XP SP3 (version 5.1 SP3 but not version 5.2).
+ return (IsWindowsVersionOrGreater(5, 1, 3) &&
+ !IsWindowsVersionOrGreater(5, 2, 0));
+}
+
+bool UIShowCrashUI(const StringTable& files,
+ const StringTable& queryParameters,
+ const string& sendURL,
+ const vector<string>& restartArgs)
+{
+ gSendData.hDlg = nullptr;
+ gSendData.sendURL = UTF8ToWide(sendURL);
+
+ // Older Windows don't support the crash report server's crypto.
+ // This is a hack to use an alternate server.
+ if (!CanUseMainCrashReportServer() &&
+ gSendData.sendURL.find(SENDURL_ORIGINAL) == 0) {
+ gSendData.sendURL.replace(0, ARRAYSIZE(SENDURL_ORIGINAL) - 1,
+ SENDURL_XPSP2);
+ }
+
+ for (StringTable::const_iterator i = files.begin();
+ i != files.end();
+ i++) {
+ gSendData.files[UTF8ToWide(i->first)] = UTF8ToWide(i->second);
+ }
+
+ for (StringTable::const_iterator i = queryParameters.begin();
+ i != queryParameters.end();
+ i++) {
+ gQueryParameters[UTF8ToWide(i->first)] = UTF8ToWide(i->second);
+ }
+
+ if (gQueryParameters.find(L"Vendor") != gQueryParameters.end()) {
+ gCrashReporterKey = L"Software\\";
+ if (!gQueryParameters[L"Vendor"].empty()) {
+ gCrashReporterKey += gQueryParameters[L"Vendor"] + L"\\";
+ }
+ gCrashReporterKey += gQueryParameters[L"ProductName"] + L"\\Crash Reporter";
+ }
+
+ if (gQueryParameters.find(L"URL") != gQueryParameters.end())
+ gURLParameter = gQueryParameters[L"URL"];
+
+ gRestartArgs = restartArgs;
+
+ if (gStrings.find("isRTL") != gStrings.end() &&
+ gStrings["isRTL"] == "yes")
+ gRTLlayout = true;
+
+ return 1 == DialogBoxParamMaybeRTL(IDD_SENDDIALOG, nullptr,
+ (DLGPROC)CrashReporterDialogProc, 0);
+}
+
+void UIError_impl(const string& message)
+{
+ wstring title = Str(ST_CRASHREPORTERTITLE);
+ if (title.empty())
+ title = L"Crash Reporter Error";
+
+ MessageBox(nullptr, UTF8ToWide(message).c_str(), title.c_str(),
+ MB_OK | MB_ICONSTOP);
+}
+
+bool UIGetIniPath(string& path)
+{
+ wchar_t fileName[MAX_PATH];
+ if (GetModuleFileName(nullptr, fileName, MAX_PATH)) {
+ // get crashreporter ini
+ wchar_t* s = wcsrchr(fileName, '.');
+ if (s) {
+ wcscpy(s, L".ini");
+ path = WideToUTF8(fileName);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool UIGetSettingsPath(const string& vendor,
+ const string& product,
+ string& settings_path)
+{
+ wchar_t path[MAX_PATH];
+ HRESULT hRes = SHGetFolderPath(nullptr,
+ CSIDL_APPDATA,
+ nullptr,
+ 0,
+ path);
+ if (FAILED(hRes)) {
+ // This provides a fallback for getting the path to APPDATA by querying the
+ // registry when the call to SHGetFolderPath is unable to provide this path
+ // (Bug 513958).
+ HKEY key;
+ DWORD type, size, dwRes;
+ dwRes = ::RegOpenKeyExW(HKEY_CURRENT_USER,
+ L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders",
+ 0,
+ KEY_READ,
+ &key);
+ if (dwRes != ERROR_SUCCESS)
+ return false;
+
+ dwRes = RegQueryValueExW(key,
+ L"AppData",
+ nullptr,
+ &type,
+ (LPBYTE)&path,
+ &size);
+ ::RegCloseKey(key);
+ // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the
+ // buffer size must not equal 0, and the buffer size be a multiple of 2.
+ if (dwRes != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0)
+ return false;
+ }
+
+ if (!vendor.empty()) {
+ PathAppend(path, UTF8ToWide(vendor).c_str());
+ }
+ PathAppend(path, UTF8ToWide(product).c_str());
+ PathAppend(path, L"Crash Reports");
+ settings_path = WideToUTF8(path);
+ return true;
+}
+
+bool UIEnsurePathExists(const string& path)
+{
+ if (CreateDirectory(UTF8ToWide(path).c_str(), nullptr) == 0) {
+ if (GetLastError() != ERROR_ALREADY_EXISTS)
+ return false;
+ }
+
+ return true;
+}
+
+bool UIFileExists(const string& path)
+{
+ DWORD attrs = GetFileAttributes(UTF8ToWide(path).c_str());
+ return (attrs != INVALID_FILE_ATTRIBUTES);
+}
+
+bool UIMoveFile(const string& oldfile, const string& newfile)
+{
+ if (oldfile == newfile)
+ return true;
+
+ return MoveFile(UTF8ToWide(oldfile).c_str(), UTF8ToWide(newfile).c_str())
+ == TRUE;
+}
+
+bool UIDeleteFile(const string& oldfile)
+{
+ return DeleteFile(UTF8ToWide(oldfile).c_str()) == TRUE;
+}
+
+ifstream* UIOpenRead(const string& filename)
+{
+ // adapted from breakpad's src/common/windows/http_upload.cc
+
+#if defined(_MSC_VER)
+ ifstream* file = new ifstream();
+ file->open(UTF8ToWide(filename).c_str(), ios::in);
+#else // GCC
+ ifstream* file = new ifstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(),
+ ios::in);
+#endif // _MSC_VER
+
+ return file;
+}
+
+ofstream* UIOpenWrite(const string& filename,
+ bool append, // append=false
+ bool binary) // binary=false
+{
+ // adapted from breakpad's src/common/windows/http_upload.cc
+ std::ios_base::openmode mode = ios::out;
+ if (append) {
+ mode = mode | ios::app;
+ }
+ if (binary) {
+ mode = mode | ios::binary;
+ }
+
+#if defined(_MSC_VER)
+ ofstream* file = new ofstream();
+ file->open(UTF8ToWide(filename).c_str(), mode);
+#else // GCC
+ ofstream* file = new ofstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(),
+ mode);
+#endif // _MSC_VER
+
+ return file;
+}
+
+struct FileData
+{
+ FILETIME timestamp;
+ wstring path;
+};
+
+static bool CompareFDTime(const FileData& fd1, const FileData& fd2)
+{
+ return CompareFileTime(&fd1.timestamp, &fd2.timestamp) > 0;
+}
+
+void UIPruneSavedDumps(const std::string& directory)
+{
+ wstring wdirectory = UTF8ToWide(directory);
+
+ WIN32_FIND_DATA fdata;
+ wstring findpath = wdirectory + L"\\*.dmp";
+ HANDLE dirlist = FindFirstFile(findpath.c_str(), &fdata);
+ if (dirlist == INVALID_HANDLE_VALUE)
+ return;
+
+ vector<FileData> dumpfiles;
+
+ for (BOOL ok = true; ok; ok = FindNextFile(dirlist, &fdata)) {
+ FileData fd = {fdata.ftLastWriteTime, wdirectory + L"\\" + fdata.cFileName};
+ dumpfiles.push_back(fd);
+ }
+
+ sort(dumpfiles.begin(), dumpfiles.end(), CompareFDTime);
+
+ while (dumpfiles.size() > kSaveCount) {
+ // get the path of the oldest file
+ wstring path = (--dumpfiles.end())->path;
+ DeleteFile(path.c_str());
+
+ // s/.dmp/.extra/
+ path.replace(path.size() - 4, 4, L".extra");
+ DeleteFile(path.c_str());
+
+ dumpfiles.pop_back();
+ }
+}
+
+void UIRunMinidumpAnalyzer(const string& exename, const string& filename)
+{
+ wstring cmdLine;
+
+ cmdLine += L"\"" + UTF8ToWide(exename) + L"\" ";
+ cmdLine += L"\"" + UTF8ToWide(filename) + L"\" ";
+
+ STARTUPINFO si = {};
+ PROCESS_INFORMATION pi = {};
+
+ si.cb = sizeof(si);
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_SHOWNORMAL;
+
+ if (CreateProcess(nullptr, (LPWSTR)cmdLine.c_str(), nullptr, nullptr, FALSE,
+ 0, nullptr, nullptr, &si, &pi)) {
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+}