Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #ifdef WIN32_LEAN_AND_MEAN
7 #undef WIN32_LEAN_AND_MEAN
8 #endif
10 #define NOMINMAX
12 #include "crashreporter.h"
14 #include <windows.h>
15 #include <commctrl.h>
16 #include <richedit.h>
17 #include <shellapi.h>
18 #include <shlobj.h>
19 #include <shlwapi.h>
20 #include <math.h>
21 #include <set>
22 #include <algorithm>
23 #include "resource.h"
24 #include "client/windows/sender/crash_report_sender.h"
25 #include "common/windows/string_utils-inl.h"
26 #include "mozilla/NullPtr.h"
28 #define CRASH_REPORTER_VALUE L"Enabled"
29 #define SUBMIT_REPORT_VALUE L"SubmitCrashReport"
30 #define SUBMIT_REPORT_OLD L"SubmitReport"
31 #define INCLUDE_URL_VALUE L"IncludeURL"
32 #define EMAIL_ME_VALUE L"EmailMe"
33 #define EMAIL_VALUE L"Email"
34 #define MAX_EMAIL_LENGTH 1024
36 #define WM_UPLOADCOMPLETE WM_APP
38 // Thanks, Windows.h :(
39 #undef min
40 #undef max
42 using std::string;
43 using std::wstring;
44 using std::map;
45 using std::vector;
46 using std::set;
47 using std::ios;
48 using std::ifstream;
49 using std::ofstream;
51 using namespace CrashReporter;
53 typedef struct {
54 HWND hDlg;
55 wstring dumpFile;
56 map<wstring,wstring> queryParameters;
57 wstring sendURL;
59 wstring serverResponse;
60 } SendThreadData;
62 /*
63 * Per http://msdn2.microsoft.com/en-us/library/ms645398(VS.85).aspx
64 * "The DLGTEMPLATEEX structure is not defined in any standard header file.
65 * The structure definition is provided here to explain the format of an
66 * extended template for a dialog box.
67 */
68 typedef struct {
69 WORD dlgVer;
70 WORD signature;
71 DWORD helpID;
72 DWORD exStyle;
73 // There's more to this struct, but it has weird variable-length
74 // members, and I only actually need to touch exStyle on an existing
75 // instance, so I've omitted the rest.
76 } DLGTEMPLATEEX;
78 static HANDLE gThreadHandle;
79 static SendThreadData gSendData = { 0, };
80 static vector<string> gRestartArgs;
81 static map<wstring,wstring> gQueryParameters;
82 static wstring gCrashReporterKey(L"Software\\Mozilla\\Crash Reporter");
83 static wstring gURLParameter;
84 static int gCheckboxPadding = 6;
85 static bool gRTLlayout = false;
87 // When vertically resizing the dialog, these items should move down
88 static set<UINT> gAttachedBottom;
90 // Default set of items for gAttachedBottom
91 static const UINT kDefaultAttachedBottom[] = {
92 IDC_SUBMITREPORTCHECK,
93 IDC_VIEWREPORTBUTTON,
94 IDC_COMMENTTEXT,
95 IDC_INCLUDEURLCHECK,
96 IDC_EMAILMECHECK,
97 IDC_EMAILTEXT,
98 IDC_PROGRESSTEXT,
99 IDC_THROBBER,
100 IDC_CLOSEBUTTON,
101 IDC_RESTARTBUTTON,
102 };
104 static wstring UTF8ToWide(const string& utf8, bool *success = 0);
105 static DWORD WINAPI SendThreadProc(LPVOID param);
107 static wstring Str(const char* key)
108 {
109 return UTF8ToWide(gStrings[key]);
110 }
112 /* === win32 helper functions === */
114 static void DoInitCommonControls()
115 {
116 INITCOMMONCONTROLSEX ic;
117 ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
118 ic.dwICC = ICC_PROGRESS_CLASS;
119 InitCommonControlsEx(&ic);
120 // also get the rich edit control
121 LoadLibrary(L"Msftedit.dll");
122 }
124 static bool GetBoolValue(HKEY hRegKey, LPCTSTR valueName, DWORD* value)
125 {
126 DWORD type, dataSize;
127 dataSize = sizeof(DWORD);
128 if (RegQueryValueEx(hRegKey, valueName, nullptr,
129 &type, (LPBYTE)value, &dataSize) == ERROR_SUCCESS &&
130 type == REG_DWORD)
131 return true;
133 return false;
134 }
136 // Removes a value from HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER, if it exists.
137 static void RemoveUnusedValues(const wchar_t* key, LPCTSTR valueName)
138 {
139 HKEY hRegKey;
141 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_SET_VALUE, &hRegKey)
142 == ERROR_SUCCESS) {
143 RegDeleteValue(hRegKey, valueName);
144 RegCloseKey(hRegKey);
145 }
147 if (RegOpenKeyEx(HKEY_CURRENT_USER, key, 0, KEY_SET_VALUE, &hRegKey)
148 == ERROR_SUCCESS) {
149 RegDeleteValue(hRegKey, valueName);
150 RegCloseKey(hRegKey);
151 }
152 }
154 static bool CheckBoolKey(const wchar_t* key,
155 const wchar_t* valueName,
156 bool* enabled)
157 {
158 /*
159 * NOTE! This code needs to stay in sync with the preference checking
160 * code in in nsExceptionHandler.cpp.
161 */
162 *enabled = false;
163 bool found = false;
164 HKEY hRegKey;
165 DWORD val;
166 // see if our reg key is set globally
167 if (RegOpenKey(HKEY_LOCAL_MACHINE, key, &hRegKey) == ERROR_SUCCESS) {
168 if (GetBoolValue(hRegKey, valueName, &val)) {
169 *enabled = (val == 1);
170 found = true;
171 }
172 RegCloseKey(hRegKey);
173 } else {
174 // look for it in user settings
175 if (RegOpenKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) {
176 if (GetBoolValue(hRegKey, valueName, &val)) {
177 *enabled = (val == 1);
178 found = true;
179 }
180 RegCloseKey(hRegKey);
181 }
182 }
184 return found;
185 }
187 static void SetBoolKey(const wchar_t* key, const wchar_t* value, bool enabled)
188 {
189 /*
190 * NOTE! This code needs to stay in sync with the preference setting
191 * code in in nsExceptionHandler.cpp.
192 */
193 HKEY hRegKey;
195 // remove the old value from the registry if it exists
196 RemoveUnusedValues(key, SUBMIT_REPORT_OLD);
198 if (RegCreateKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) {
199 DWORD data = (enabled ? 1 : 0);
200 RegSetValueEx(hRegKey, value, 0, REG_DWORD, (LPBYTE)&data, sizeof(data));
201 RegCloseKey(hRegKey);
202 }
203 }
205 static bool GetStringValue(HKEY hRegKey, LPCTSTR valueName, wstring& value)
206 {
207 DWORD type, dataSize;
208 wchar_t buf[2048];
209 dataSize = sizeof(buf);
210 if (RegQueryValueEx(hRegKey, valueName, nullptr,
211 &type, (LPBYTE)buf, &dataSize) == ERROR_SUCCESS &&
212 type == REG_SZ) {
213 value = buf;
214 return true;
215 }
217 return false;
218 }
220 static bool GetStringKey(const wchar_t* key,
221 const wchar_t* valueName,
222 wstring& value)
223 {
224 value = L"";
225 bool found = false;
226 HKEY hRegKey;
227 // see if our reg key is set globally
228 if (RegOpenKey(HKEY_LOCAL_MACHINE, key, &hRegKey) == ERROR_SUCCESS) {
229 if (GetStringValue(hRegKey, valueName, value)) {
230 found = true;
231 }
232 RegCloseKey(hRegKey);
233 } else {
234 // look for it in user settings
235 if (RegOpenKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) {
236 if (GetStringValue(hRegKey, valueName, value)) {
237 found = true;
238 }
239 RegCloseKey(hRegKey);
240 }
241 }
243 return found;
244 }
246 static void SetStringKey(const wchar_t* key,
247 const wchar_t* valueName,
248 const wstring& value)
249 {
250 HKEY hRegKey;
251 if (RegCreateKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) {
252 RegSetValueEx(hRegKey, valueName, 0, REG_SZ,
253 (LPBYTE)value.c_str(),
254 (value.length() + 1) * sizeof(wchar_t));
255 RegCloseKey(hRegKey);
256 }
257 }
259 static string FormatLastError()
260 {
261 DWORD err = GetLastError();
262 LPWSTR s;
263 string message = "Crash report submission failed: ";
264 // odds are it's a WinInet error
265 HANDLE hInetModule = GetModuleHandle(L"WinInet.dll");
266 if(FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
267 FORMAT_MESSAGE_FROM_SYSTEM |
268 FORMAT_MESSAGE_FROM_HMODULE,
269 hInetModule,
270 err,
271 0,
272 (LPWSTR)&s,
273 0,
274 nullptr) != 0) {
275 message += WideToUTF8(s, nullptr);
276 LocalFree(s);
277 // strip off any trailing newlines
278 string::size_type n = message.find_last_not_of("\r\n");
279 if (n < message.size() - 1) {
280 message.erase(n+1);
281 }
282 }
283 else {
284 char buf[64];
285 sprintf(buf, "Unknown error, error code: 0x%08x", err);
286 message += buf;
287 }
288 return message;
289 }
291 #define TS_DRAW 2
292 #define BP_CHECKBOX 3
294 typedef HANDLE (WINAPI*OpenThemeDataPtr)(HWND hwnd, LPCWSTR pszClassList);
295 typedef HRESULT (WINAPI*CloseThemeDataPtr)(HANDLE hTheme);
296 typedef HRESULT (WINAPI*GetThemePartSizePtr)(HANDLE hTheme, HDC hdc, int iPartId,
297 int iStateId, RECT* prc, int ts,
298 SIZE* psz);
299 typedef HRESULT (WINAPI*GetThemeContentRectPtr)(HANDLE hTheme, HDC hdc, int iPartId,
300 int iStateId, const RECT* pRect,
301 RECT* pContentRect);
304 static void GetThemeSizes(HWND hwnd)
305 {
306 HMODULE themeDLL = LoadLibrary(L"uxtheme.dll");
308 if (!themeDLL)
309 return;
311 OpenThemeDataPtr openTheme =
312 (OpenThemeDataPtr)GetProcAddress(themeDLL, "OpenThemeData");
313 CloseThemeDataPtr closeTheme =
314 (CloseThemeDataPtr)GetProcAddress(themeDLL, "CloseThemeData");
315 GetThemePartSizePtr getThemePartSize =
316 (GetThemePartSizePtr)GetProcAddress(themeDLL, "GetThemePartSize");
318 if (!openTheme || !closeTheme || !getThemePartSize) {
319 FreeLibrary(themeDLL);
320 return;
321 }
323 HANDLE buttonTheme = openTheme(hwnd, L"Button");
324 if (!buttonTheme) {
325 FreeLibrary(themeDLL);
326 return;
327 }
328 HDC hdc = GetDC(hwnd);
329 SIZE s;
330 getThemePartSize(buttonTheme, hdc, BP_CHECKBOX, 0, nullptr, TS_DRAW, &s);
331 gCheckboxPadding = s.cx;
332 closeTheme(buttonTheme);
333 FreeLibrary(themeDLL);
334 }
336 // Gets the position of a window relative to another window's client area
337 static void GetRelativeRect(HWND hwnd, HWND hwndParent, RECT* r)
338 {
339 GetWindowRect(hwnd, r);
340 MapWindowPoints(nullptr, hwndParent, (POINT*)r, 2);
341 }
343 static void SetDlgItemVisible(HWND hwndDlg, UINT item, bool visible)
344 {
345 HWND hwnd = GetDlgItem(hwndDlg, item);
347 ShowWindow(hwnd, visible ? SW_SHOW : SW_HIDE);
348 }
350 static void SetDlgItemDisabled(HWND hwndDlg, UINT item, bool disabled)
351 {
352 HWND hwnd = GetDlgItem(hwndDlg, item);
353 LONG style = GetWindowLong(hwnd, GWL_STYLE);
354 if (!disabled)
355 style |= WS_DISABLED;
356 else
357 style &= ~WS_DISABLED;
359 SetWindowLong(hwnd, GWL_STYLE, style);
360 }
362 /* === Crash Reporting Dialog === */
364 static void StretchDialog(HWND hwndDlg, int ydiff)
365 {
366 RECT r;
367 GetWindowRect(hwndDlg, &r);
368 r.bottom += ydiff;
369 MoveWindow(hwndDlg, r.left, r.top,
370 r.right - r.left, r.bottom - r.top, TRUE);
371 }
373 static void ReflowDialog(HWND hwndDlg, int ydiff)
374 {
375 // Move items attached to the bottom down/up by as much as
376 // the window resize
377 for (set<UINT>::const_iterator item = gAttachedBottom.begin();
378 item != gAttachedBottom.end();
379 item++) {
380 RECT r;
381 HWND hwnd = GetDlgItem(hwndDlg, *item);
382 GetRelativeRect(hwnd, hwndDlg, &r);
383 r.top += ydiff;
384 r.bottom += ydiff;
385 MoveWindow(hwnd, r.left, r.top,
386 r.right - r.left, r.bottom - r.top, TRUE);
387 }
388 }
390 static DWORD WINAPI SendThreadProc(LPVOID param)
391 {
392 bool finishedOk;
393 SendThreadData* td = (SendThreadData*)param;
395 if (td->sendURL.empty()) {
396 finishedOk = false;
397 LogMessage("No server URL, not sending report");
398 } else {
399 google_breakpad::CrashReportSender sender(L"");
400 finishedOk = (sender.SendCrashReport(td->sendURL,
401 td->queryParameters,
402 td->dumpFile,
403 &td->serverResponse)
404 == google_breakpad::RESULT_SUCCEEDED);
405 if (finishedOk) {
406 LogMessage("Crash report submitted successfully");
407 }
408 else {
409 // get an error string and print it to the log
410 //XXX: would be nice to get the HTTP status code here, filed:
411 // http://code.google.com/p/google-breakpad/issues/detail?id=220
412 LogMessage(FormatLastError());
413 }
414 }
416 PostMessage(td->hDlg, WM_UPLOADCOMPLETE, finishedOk ? 1 : 0, 0);
418 return 0;
419 }
421 static void EndCrashReporterDialog(HWND hwndDlg, int code)
422 {
423 // Save the current values to the registry
424 wchar_t email[MAX_EMAIL_LENGTH];
425 GetDlgItemTextW(hwndDlg, IDC_EMAILTEXT, email,
426 sizeof(email) / sizeof(email[0]));
427 SetStringKey(gCrashReporterKey.c_str(), EMAIL_VALUE, email);
429 SetBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE,
430 IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK) != 0);
431 SetBoolKey(gCrashReporterKey.c_str(), EMAIL_ME_VALUE,
432 IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK) != 0);
433 SetBoolKey(gCrashReporterKey.c_str(), SUBMIT_REPORT_VALUE,
434 IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0);
436 EndDialog(hwndDlg, code);
437 }
439 static void MaybeResizeProgressText(HWND hwndDlg)
440 {
441 HWND hwndProgress = GetDlgItem(hwndDlg, IDC_PROGRESSTEXT);
442 HDC hdc = GetDC(hwndProgress);
443 HFONT hfont = (HFONT)SendMessage(hwndProgress, WM_GETFONT, 0, 0);
444 if (hfont)
445 SelectObject(hdc, hfont);
446 SIZE size;
447 RECT rect;
448 GetRelativeRect(hwndProgress, hwndDlg, &rect);
450 wchar_t text[1024];
451 GetWindowText(hwndProgress, text, 1024);
453 if (!GetTextExtentPoint32(hdc, text, wcslen(text), &size))
454 return;
456 if (size.cx < (rect.right - rect.left))
457 return;
459 // Figure out how much we need to resize things vertically
460 // This is sort of a fudge, but it should be good enough.
461 int wantedHeight = size.cy *
462 (int)ceil((float)size.cx / (float)(rect.right - rect.left));
463 int diff = wantedHeight - (rect.bottom - rect.top);
464 if (diff <= 0)
465 return;
467 MoveWindow(hwndProgress, rect.left, rect.top,
468 rect.right - rect.left,
469 wantedHeight,
470 TRUE);
472 gAttachedBottom.clear();
473 gAttachedBottom.insert(IDC_CLOSEBUTTON);
474 gAttachedBottom.insert(IDC_RESTARTBUTTON);
476 StretchDialog(hwndDlg, diff);
478 for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) {
479 gAttachedBottom.insert(kDefaultAttachedBottom[i]);
480 }
481 }
483 static void MaybeSendReport(HWND hwndDlg)
484 {
485 if (!IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK)) {
486 EndCrashReporterDialog(hwndDlg, 0);
487 return;
488 }
490 // disable all the form controls
491 EnableWindow(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK), false);
492 EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), false);
493 EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), false);
494 EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), false);
495 EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), false);
496 EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), false);
497 EnableWindow(GetDlgItem(hwndDlg, IDC_CLOSEBUTTON), false);
498 EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTBUTTON), false);
500 SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, Str(ST_REPORTDURINGSUBMIT).c_str());
501 MaybeResizeProgressText(hwndDlg);
502 // start throbber
503 // play entire AVI, and loop
504 Animate_Play(GetDlgItem(hwndDlg, IDC_THROBBER), 0, -1, -1);
505 SetDlgItemVisible(hwndDlg, IDC_THROBBER, true);
506 gThreadHandle = nullptr;
507 gSendData.hDlg = hwndDlg;
508 gSendData.queryParameters = gQueryParameters;
510 gThreadHandle = CreateThread(nullptr, 0, SendThreadProc, &gSendData, 0,
511 nullptr);
512 }
514 static void RestartApplication()
515 {
516 wstring cmdLine;
518 for (unsigned int i = 0; i < gRestartArgs.size(); i++) {
519 cmdLine += L"\"" + UTF8ToWide(gRestartArgs[i]) + L"\" ";
520 }
522 STARTUPINFO si;
523 PROCESS_INFORMATION pi;
525 ZeroMemory(&si, sizeof(si));
526 si.cb = sizeof(si);
527 si.dwFlags = STARTF_USESHOWWINDOW;
528 si.wShowWindow = SW_SHOWNORMAL;
529 ZeroMemory(&pi, sizeof(pi));
531 if (CreateProcess(nullptr, (LPWSTR)cmdLine.c_str(), nullptr, nullptr, FALSE,
532 0, nullptr, nullptr, &si, &pi)) {
533 CloseHandle(pi.hProcess);
534 CloseHandle(pi.hThread);
535 }
536 }
538 static void ShowReportInfo(HWND hwndDlg)
539 {
540 wstring description;
542 for (map<wstring,wstring>::const_iterator i = gQueryParameters.begin();
543 i != gQueryParameters.end();
544 i++) {
545 description += i->first;
546 description += L": ";
547 description += i->second;
548 description += L"\n";
549 }
551 description += L"\n";
552 description += Str(ST_EXTRAREPORTINFO);
554 SetDlgItemText(hwndDlg, IDC_VIEWREPORTTEXT, description.c_str());
555 }
557 static void UpdateURL(HWND hwndDlg)
558 {
559 if (IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK)) {
560 gQueryParameters[L"URL"] = gURLParameter;
561 } else {
562 gQueryParameters.erase(L"URL");
563 }
564 }
566 static void UpdateEmail(HWND hwndDlg)
567 {
568 if (IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK)) {
569 wchar_t email[MAX_EMAIL_LENGTH];
570 GetDlgItemTextW(hwndDlg, IDC_EMAILTEXT, email,
571 sizeof(email) / sizeof(email[0]));
572 gQueryParameters[L"Email"] = email;
573 if (IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK))
574 EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), true);
575 } else {
576 gQueryParameters.erase(L"Email");
577 EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), false);
578 }
579 }
581 static void UpdateComment(HWND hwndDlg)
582 {
583 wchar_t comment[MAX_COMMENT_LENGTH + 1];
584 GetDlgItemTextW(hwndDlg, IDC_COMMENTTEXT, comment,
585 sizeof(comment) / sizeof(comment[0]));
586 if (wcslen(comment) > 0)
587 gQueryParameters[L"Comments"] = comment;
588 else
589 gQueryParameters.erase(L"Comments");
590 }
592 /*
593 * Dialog procedure for the "view report" dialog.
594 */
595 static BOOL CALLBACK ViewReportDialogProc(HWND hwndDlg, UINT message,
596 WPARAM wParam, LPARAM lParam)
597 {
598 switch (message) {
599 case WM_INITDIALOG: {
600 SetWindowText(hwndDlg, Str(ST_VIEWREPORTTITLE).c_str());
601 SetDlgItemText(hwndDlg, IDOK, Str(ST_OK).c_str());
602 SendDlgItemMessage(hwndDlg, IDC_VIEWREPORTTEXT,
603 EM_SETTARGETDEVICE, (WPARAM)nullptr, 0);
604 ShowReportInfo(hwndDlg);
605 SetFocus(GetDlgItem(hwndDlg, IDOK));
606 return FALSE;
607 }
609 case WM_COMMAND: {
610 if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDOK)
611 EndDialog(hwndDlg, 0);
612 return FALSE;
613 }
614 }
615 return FALSE;
616 }
618 // Return the number of bytes this string will take encoded
619 // in UTF-8
620 static inline int BytesInUTF8(wchar_t* str)
621 {
622 // Just count size of buffer for UTF-8, minus one
623 // (we don't need to count the null terminator)
624 return WideCharToMultiByte(CP_UTF8, 0, str, -1,
625 nullptr, 0, nullptr, nullptr) - 1;
626 }
628 // Calculate the length of the text in this edit control (in bytes,
629 // in the UTF-8 encoding) after replacing the current selection
630 // with |insert|.
631 static int NewTextLength(HWND hwndEdit, wchar_t* insert)
632 {
633 wchar_t current[MAX_COMMENT_LENGTH + 1];
635 GetWindowText(hwndEdit, current, MAX_COMMENT_LENGTH + 1);
636 DWORD selStart, selEnd;
637 SendMessage(hwndEdit, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd);
639 int selectionLength = 0;
640 if (selEnd - selStart > 0) {
641 wchar_t selection[MAX_COMMENT_LENGTH + 1];
642 google_breakpad::WindowsStringUtils::safe_wcsncpy(selection,
643 MAX_COMMENT_LENGTH + 1,
644 current + selStart,
645 selEnd - selStart);
646 selection[selEnd - selStart] = '\0';
647 selectionLength = BytesInUTF8(selection);
648 }
650 // current string length + replacement text length
651 // - replaced selection length
652 return BytesInUTF8(current) + BytesInUTF8(insert) - selectionLength;
653 }
655 // Window procedure for subclassing edit controls
656 static LRESULT CALLBACK EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
657 LPARAM lParam)
658 {
659 static WNDPROC super = nullptr;
661 if (super == nullptr)
662 super = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA);
664 switch (uMsg) {
665 case WM_PAINT: {
666 HDC hdc;
667 PAINTSTRUCT ps;
668 RECT r;
669 wchar_t windowText[1024];
671 GetWindowText(hwnd, windowText, 1024);
672 // if the control contains text or is focused, draw it normally
673 if (GetFocus() == hwnd || windowText[0] != '\0')
674 return CallWindowProc(super, hwnd, uMsg, wParam, lParam);
676 GetClientRect(hwnd, &r);
677 hdc = BeginPaint(hwnd, &ps);
678 FillRect(hdc, &r, GetSysColorBrush(IsWindowEnabled(hwnd)
679 ? COLOR_WINDOW : COLOR_BTNFACE));
680 SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT));
681 SelectObject(hdc, (HFONT)GetStockObject(DEFAULT_GUI_FONT));
682 SetBkMode(hdc, TRANSPARENT);
683 wchar_t* txt = (wchar_t*)GetProp(hwnd, L"PROP_GRAYTEXT");
684 // Get the actual edit control rect
685 CallWindowProc(super, hwnd, EM_GETRECT, 0, (LPARAM)&r);
686 UINT format = DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK | DT_INTERNAL;
687 if (gRTLlayout)
688 format |= DT_RIGHT;
689 if (txt)
690 DrawText(hdc, txt, wcslen(txt), &r, format);
691 EndPaint(hwnd, &ps);
692 return 0;
693 }
695 // We handle WM_CHAR and WM_PASTE to limit the comment box to 500
696 // bytes in UTF-8.
697 case WM_CHAR: {
698 // Leave accelerator keys and non-printing chars (except LF) alone
699 if (wParam & (1<<24) || wParam & (1<<29) ||
700 (wParam < ' ' && wParam != '\n'))
701 break;
703 wchar_t ch[2] = { (wchar_t)wParam, 0 };
704 if (NewTextLength(hwnd, ch) > MAX_COMMENT_LENGTH)
705 return 0;
707 break;
708 }
710 case WM_PASTE: {
711 if (IsClipboardFormatAvailable(CF_UNICODETEXT) &&
712 OpenClipboard(hwnd)) {
713 HGLOBAL hg = GetClipboardData(CF_UNICODETEXT);
714 wchar_t* pastedText = (wchar_t*)GlobalLock(hg);
715 int newSize = 0;
717 if (pastedText)
718 newSize = NewTextLength(hwnd, pastedText);
720 GlobalUnlock(hg);
721 CloseClipboard();
723 if (newSize > MAX_COMMENT_LENGTH)
724 return 0;
725 }
726 break;
727 }
729 case WM_SETFOCUS:
730 case WM_KILLFOCUS: {
731 RECT r;
732 GetClientRect(hwnd, &r);
733 InvalidateRect(hwnd, &r, TRUE);
734 break;
735 }
737 case WM_DESTROY: {
738 // cleanup our property
739 HGLOBAL hData = RemoveProp(hwnd, L"PROP_GRAYTEXT");
740 if (hData)
741 GlobalFree(hData);
742 }
743 }
745 return CallWindowProc(super, hwnd, uMsg, wParam, lParam);
746 }
748 // Resize a control to fit this text
749 static int ResizeControl(HWND hwndButton, RECT& rect, wstring text,
750 bool shiftLeft, int userDefinedPadding)
751 {
752 HDC hdc = GetDC(hwndButton);
753 HFONT hfont = (HFONT)SendMessage(hwndButton, WM_GETFONT, 0, 0);
754 if (hfont)
755 SelectObject(hdc, hfont);
756 SIZE size, oldSize;
757 int sizeDiff = 0;
759 wchar_t oldText[1024];
760 GetWindowText(hwndButton, oldText, 1024);
762 if (GetTextExtentPoint32(hdc, text.c_str(), text.length(), &size)
763 // default text on the button
764 && GetTextExtentPoint32(hdc, oldText, wcslen(oldText), &oldSize)) {
765 /*
766 Expand control widths to accomidate wider text strings. For most
767 controls (including buttons) the text padding is defined by the
768 dialog's rc file. Some controls (such as checkboxes) have padding
769 that extends to the end of the dialog, in which case we ignore the
770 rc padding and rely on a user defined value passed in through
771 userDefinedPadding.
772 */
773 int textIncrease = size.cx - oldSize.cx;
774 if (textIncrease < 0)
775 return 0;
776 int existingTextPadding;
777 if (userDefinedPadding == 0)
778 existingTextPadding = (rect.right - rect.left) - oldSize.cx;
779 else
780 existingTextPadding = userDefinedPadding;
781 sizeDiff = textIncrease + existingTextPadding;
783 if (shiftLeft) {
784 // shift left by the amount the button should grow
785 rect.left -= sizeDiff;
786 }
787 else {
788 // grow right instead
789 rect.right += sizeDiff;
790 }
791 MoveWindow(hwndButton, rect.left, rect.top,
792 rect.right - rect.left,
793 rect.bottom - rect.top,
794 TRUE);
795 }
796 return sizeDiff;
797 }
799 // The window was resized horizontally, so widen some of our
800 // controls to make use of the space
801 static void StretchControlsToFit(HWND hwndDlg)
802 {
803 int controls[] = {
804 IDC_DESCRIPTIONTEXT,
805 IDC_SUBMITREPORTCHECK,
806 IDC_COMMENTTEXT,
807 IDC_INCLUDEURLCHECK,
808 IDC_EMAILMECHECK,
809 IDC_EMAILTEXT,
810 IDC_PROGRESSTEXT
811 };
813 RECT dlgRect;
814 GetClientRect(hwndDlg, &dlgRect);
816 for (int i=0; i<sizeof(controls)/sizeof(controls[0]); i++) {
817 RECT r;
818 HWND hwndControl = GetDlgItem(hwndDlg, controls[i]);
819 GetRelativeRect(hwndControl, hwndDlg, &r);
820 // 6 pixel spacing on the right
821 if (r.right + 6 != dlgRect.right) {
822 r.right = dlgRect.right - 6;
823 MoveWindow(hwndControl, r.left, r.top,
824 r.right - r.left,
825 r.bottom - r.top,
826 TRUE);
827 }
828 }
829 }
831 static void SubmitReportChecked(HWND hwndDlg)
832 {
833 bool enabled = (IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0);
834 EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), enabled);
835 EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), enabled);
836 EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), enabled);
837 EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), enabled);
838 EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT),
839 enabled && (IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK)
840 != 0));
841 SetDlgItemVisible(hwndDlg, IDC_PROGRESSTEXT, enabled);
842 }
844 static INT_PTR DialogBoxParamMaybeRTL(UINT idd, HWND hwndParent,
845 DLGPROC dlgProc, LPARAM param)
846 {
847 INT_PTR rv = 0;
848 if (gRTLlayout) {
849 // We need to toggle the WS_EX_LAYOUTRTL style flag on the dialog
850 // template.
851 HRSRC hDialogRC = FindResource(nullptr, MAKEINTRESOURCE(idd),
852 RT_DIALOG);
853 HGLOBAL hDlgTemplate = LoadResource(nullptr, hDialogRC);
854 DLGTEMPLATEEX* pDlgTemplate = (DLGTEMPLATEEX*)LockResource(hDlgTemplate);
855 unsigned long sizeDlg = SizeofResource(nullptr, hDialogRC);
856 HGLOBAL hMyDlgTemplate = GlobalAlloc(GPTR, sizeDlg);
857 DLGTEMPLATEEX* pMyDlgTemplate =
858 (DLGTEMPLATEEX*)GlobalLock(hMyDlgTemplate);
859 memcpy(pMyDlgTemplate, pDlgTemplate, sizeDlg);
861 pMyDlgTemplate->exStyle |= WS_EX_LAYOUTRTL;
863 rv = DialogBoxIndirectParam(nullptr, (LPCDLGTEMPLATE)pMyDlgTemplate,
864 hwndParent, dlgProc, param);
865 GlobalUnlock(hMyDlgTemplate);
866 GlobalFree(hMyDlgTemplate);
867 }
868 else {
869 rv = DialogBoxParam(nullptr, MAKEINTRESOURCE(idd), hwndParent,
870 dlgProc, param);
871 }
873 return rv;
874 }
877 static BOOL CALLBACK CrashReporterDialogProc(HWND hwndDlg, UINT message,
878 WPARAM wParam, LPARAM lParam)
879 {
880 static int sHeight = 0;
882 bool success;
883 bool enabled;
885 switch (message) {
886 case WM_INITDIALOG: {
887 GetThemeSizes(hwndDlg);
888 RECT r;
889 GetClientRect(hwndDlg, &r);
890 sHeight = r.bottom - r.top;
892 SetWindowText(hwndDlg, Str(ST_CRASHREPORTERTITLE).c_str());
893 HICON hIcon = LoadIcon(GetModuleHandle(nullptr),
894 MAKEINTRESOURCE(IDI_MAINICON));
895 SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
896 SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
898 // resize the "View Report" button based on the string length
899 RECT rect;
900 HWND hwnd = GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON);
901 GetRelativeRect(hwnd, hwndDlg, &rect);
902 ResizeControl(hwnd, rect, Str(ST_VIEWREPORT), false, 0);
903 SetDlgItemText(hwndDlg, IDC_VIEWREPORTBUTTON, Str(ST_VIEWREPORT).c_str());
905 hwnd = GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK);
906 GetRelativeRect(hwnd, hwndDlg, &rect);
907 long maxdiff = ResizeControl(hwnd, rect, Str(ST_CHECKSUBMIT), false,
908 gCheckboxPadding);
909 SetDlgItemText(hwndDlg, IDC_SUBMITREPORTCHECK,
910 Str(ST_CHECKSUBMIT).c_str());
912 if (!CheckBoolKey(gCrashReporterKey.c_str(),
913 SUBMIT_REPORT_VALUE, &enabled))
914 enabled = ShouldEnableSending();
916 CheckDlgButton(hwndDlg, IDC_SUBMITREPORTCHECK, enabled ? BST_CHECKED
917 : BST_UNCHECKED);
918 SubmitReportChecked(hwndDlg);
920 HWND hwndComment = GetDlgItem(hwndDlg, IDC_COMMENTTEXT);
921 WNDPROC OldWndProc = (WNDPROC)SetWindowLongPtr(hwndComment,
922 GWLP_WNDPROC,
923 (LONG_PTR)EditSubclassProc);
925 // Subclass comment edit control to get placeholder text
926 SetWindowLongPtr(hwndComment, GWLP_USERDATA, (LONG_PTR)OldWndProc);
927 wstring commentGrayText = Str(ST_COMMENTGRAYTEXT);
928 wchar_t* hMem = (wchar_t*)GlobalAlloc(GPTR, (commentGrayText.length() + 1)*sizeof(wchar_t));
929 wcscpy(hMem, commentGrayText.c_str());
930 SetProp(hwndComment, L"PROP_GRAYTEXT", hMem);
932 hwnd = GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK);
933 GetRelativeRect(hwnd, hwndDlg, &rect);
934 long diff = ResizeControl(hwnd, rect, Str(ST_CHECKURL), false,
935 gCheckboxPadding);
936 maxdiff = std::max(diff, maxdiff);
937 SetDlgItemText(hwndDlg, IDC_INCLUDEURLCHECK, Str(ST_CHECKURL).c_str());
939 // want this on by default
940 if (CheckBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE, &enabled) &&
941 !enabled) {
942 CheckDlgButton(hwndDlg, IDC_INCLUDEURLCHECK, BST_UNCHECKED);
943 } else {
944 CheckDlgButton(hwndDlg, IDC_INCLUDEURLCHECK, BST_CHECKED);
945 }
947 hwnd = GetDlgItem(hwndDlg, IDC_EMAILMECHECK);
948 GetRelativeRect(hwnd, hwndDlg, &rect);
949 diff = ResizeControl(hwnd, rect, Str(ST_CHECKEMAIL), false,
950 gCheckboxPadding);
951 maxdiff = std::max(diff, maxdiff);
952 SetDlgItemText(hwndDlg, IDC_EMAILMECHECK, Str(ST_CHECKEMAIL).c_str());
954 if (CheckBoolKey(gCrashReporterKey.c_str(), EMAIL_ME_VALUE, &enabled) &&
955 enabled) {
956 CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_CHECKED);
957 } else {
958 CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_UNCHECKED);
959 }
961 wstring email;
962 if (GetStringKey(gCrashReporterKey.c_str(), EMAIL_VALUE, email)) {
963 SetDlgItemText(hwndDlg, IDC_EMAILTEXT, email.c_str());
964 }
966 // Subclass email edit control to get placeholder text
967 HWND hwndEmail = GetDlgItem(hwndDlg, IDC_EMAILTEXT);
968 OldWndProc = (WNDPROC)SetWindowLongPtr(hwndEmail,
969 GWLP_WNDPROC,
970 (LONG_PTR)EditSubclassProc);
971 SetWindowLongPtr(hwndEmail, GWLP_USERDATA, (LONG_PTR)OldWndProc);
972 wstring emailGrayText = Str(ST_EMAILGRAYTEXT);
973 hMem = (wchar_t*)GlobalAlloc(GPTR, (emailGrayText.length() + 1)*sizeof(wchar_t));
974 wcscpy(hMem, emailGrayText.c_str());
975 SetProp(hwndEmail, L"PROP_GRAYTEXT", hMem);
977 SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, Str(ST_REPORTPRESUBMIT).c_str());
979 RECT closeRect;
980 HWND hwndClose = GetDlgItem(hwndDlg, IDC_CLOSEBUTTON);
981 GetRelativeRect(hwndClose, hwndDlg, &closeRect);
983 RECT restartRect;
984 HWND hwndRestart = GetDlgItem(hwndDlg, IDC_RESTARTBUTTON);
985 GetRelativeRect(hwndRestart, hwndDlg, &restartRect);
987 // set the close button text and shift the buttons around
988 // since the size may need to change
989 int sizeDiff = ResizeControl(hwndClose, closeRect, Str(ST_QUIT),
990 true, 0);
991 restartRect.left -= sizeDiff;
992 restartRect.right -= sizeDiff;
993 SetDlgItemText(hwndDlg, IDC_CLOSEBUTTON, Str(ST_QUIT).c_str());
995 if (gRestartArgs.size() > 0) {
996 // Resize restart button to fit text
997 ResizeControl(hwndRestart, restartRect, Str(ST_RESTART), true, 0);
998 SetDlgItemText(hwndDlg, IDC_RESTARTBUTTON, Str(ST_RESTART).c_str());
999 } else {
1000 // No restart arguments, so just hide the restart button
1001 SetDlgItemVisible(hwndDlg, IDC_RESTARTBUTTON, false);
1002 }
1003 // See if we need to widen the window
1004 // Leave 6 pixels on either side + 6 pixels between the buttons
1005 int neededSize = closeRect.right - closeRect.left +
1006 restartRect.right - restartRect.left + 6 * 3;
1007 GetClientRect(hwndDlg, &r);
1008 // We may already have resized one of the checkboxes above
1009 maxdiff = std::max(maxdiff, neededSize - (r.right - r.left));
1011 if (maxdiff > 0) {
1012 // widen window
1013 GetWindowRect(hwndDlg, &r);
1014 r.right += maxdiff;
1015 MoveWindow(hwndDlg, r.left, r.top,
1016 r.right - r.left, r.bottom - r.top, TRUE);
1017 // shift both buttons right
1018 if (restartRect.left + maxdiff < 6)
1019 maxdiff += 6;
1020 closeRect.left += maxdiff;
1021 closeRect.right += maxdiff;
1022 restartRect.left += maxdiff;
1023 restartRect.right += maxdiff;
1024 MoveWindow(hwndClose, closeRect.left, closeRect.top,
1025 closeRect.right - closeRect.left,
1026 closeRect.bottom - closeRect.top,
1027 TRUE);
1028 StretchControlsToFit(hwndDlg);
1029 }
1030 // need to move the restart button regardless
1031 MoveWindow(hwndRestart, restartRect.left, restartRect.top,
1032 restartRect.right - restartRect.left,
1033 restartRect.bottom - restartRect.top,
1034 TRUE);
1036 // Resize the description text last, in case the window was resized
1037 // before this.
1038 SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT,
1039 EM_SETEVENTMASK, (WPARAM)nullptr,
1040 ENM_REQUESTRESIZE);
1042 wstring description = Str(ST_CRASHREPORTERHEADER);
1043 description += L"\n\n";
1044 description += Str(ST_CRASHREPORTERDESCRIPTION);
1045 SetDlgItemText(hwndDlg, IDC_DESCRIPTIONTEXT, description.c_str());
1048 // Make the title bold.
1049 CHARFORMAT fmt = { 0, };
1050 fmt.cbSize = sizeof(fmt);
1051 fmt.dwMask = CFM_BOLD;
1052 fmt.dwEffects = CFE_BOLD;
1053 SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL,
1054 0, Str(ST_CRASHREPORTERHEADER).length());
1055 SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETCHARFORMAT,
1056 SCF_SELECTION, (LPARAM)&fmt);
1057 SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL, 0, 0);
1058 // Force redraw.
1059 SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT,
1060 EM_SETTARGETDEVICE, (WPARAM)nullptr, 0);
1061 // Force resize.
1062 SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT,
1063 EM_REQUESTRESIZE, 0, 0);
1065 // if no URL was given, hide the URL checkbox
1066 if (gQueryParameters.find(L"URL") == gQueryParameters.end()) {
1067 RECT urlCheckRect, emailCheckRect;
1068 GetWindowRect(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), &urlCheckRect);
1069 GetWindowRect(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), &emailCheckRect);
1071 SetDlgItemVisible(hwndDlg, IDC_INCLUDEURLCHECK, false);
1073 gAttachedBottom.erase(IDC_VIEWREPORTBUTTON);
1074 gAttachedBottom.erase(IDC_SUBMITREPORTCHECK);
1075 gAttachedBottom.erase(IDC_COMMENTTEXT);
1077 StretchDialog(hwndDlg, urlCheckRect.top - emailCheckRect.top);
1079 gAttachedBottom.insert(IDC_VIEWREPORTBUTTON);
1080 gAttachedBottom.insert(IDC_SUBMITREPORTCHECK);
1081 gAttachedBottom.insert(IDC_COMMENTTEXT);
1082 }
1084 MaybeResizeProgressText(hwndDlg);
1086 // Open the AVI resource for the throbber
1087 Animate_Open(GetDlgItem(hwndDlg, IDC_THROBBER),
1088 MAKEINTRESOURCE(IDR_THROBBER));
1090 UpdateURL(hwndDlg);
1091 UpdateEmail(hwndDlg);
1093 SetFocus(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK));
1094 return FALSE;
1095 }
1096 case WM_SIZE: {
1097 ReflowDialog(hwndDlg, HIWORD(lParam) - sHeight);
1098 sHeight = HIWORD(lParam);
1099 InvalidateRect(hwndDlg, nullptr, TRUE);
1100 return FALSE;
1101 }
1102 case WM_NOTIFY: {
1103 NMHDR* notification = reinterpret_cast<NMHDR*>(lParam);
1104 if (notification->code == EN_REQUESTRESIZE) {
1105 // Resizing the rich edit control to fit the description text.
1106 REQRESIZE* reqresize = reinterpret_cast<REQRESIZE*>(lParam);
1107 RECT newSize = reqresize->rc;
1108 RECT oldSize;
1109 GetRelativeRect(notification->hwndFrom, hwndDlg, &oldSize);
1111 // resize the text box as requested
1112 MoveWindow(notification->hwndFrom, newSize.left, newSize.top,
1113 newSize.right - newSize.left, newSize.bottom - newSize.top,
1114 TRUE);
1116 // Resize the dialog to fit (the WM_SIZE handler will move the controls)
1117 StretchDialog(hwndDlg, newSize.bottom - oldSize.bottom);
1118 }
1119 return FALSE;
1120 }
1121 case WM_COMMAND: {
1122 if (HIWORD(wParam) == BN_CLICKED) {
1123 switch(LOWORD(wParam)) {
1124 case IDC_VIEWREPORTBUTTON:
1125 DialogBoxParamMaybeRTL(IDD_VIEWREPORTDIALOG, hwndDlg,
1126 (DLGPROC)ViewReportDialogProc, 0);
1127 break;
1128 case IDC_SUBMITREPORTCHECK:
1129 SubmitReportChecked(hwndDlg);
1130 break;
1131 case IDC_INCLUDEURLCHECK:
1132 UpdateURL(hwndDlg);
1133 break;
1134 case IDC_EMAILMECHECK:
1135 UpdateEmail(hwndDlg);
1136 break;
1137 case IDC_CLOSEBUTTON:
1138 MaybeSendReport(hwndDlg);
1139 break;
1140 case IDC_RESTARTBUTTON:
1141 RestartApplication();
1142 MaybeSendReport(hwndDlg);
1143 break;
1144 }
1145 } else if (HIWORD(wParam) == EN_CHANGE) {
1146 switch(LOWORD(wParam)) {
1147 case IDC_EMAILTEXT:
1148 UpdateEmail(hwndDlg);
1149 break;
1150 case IDC_COMMENTTEXT:
1151 UpdateComment(hwndDlg);
1152 }
1153 }
1155 return FALSE;
1156 }
1157 case WM_UPLOADCOMPLETE: {
1158 WaitForSingleObject(gThreadHandle, INFINITE);
1159 success = (wParam == 1);
1160 SendCompleted(success, WideToUTF8(gSendData.serverResponse));
1161 // hide throbber
1162 Animate_Stop(GetDlgItem(hwndDlg, IDC_THROBBER));
1163 SetDlgItemVisible(hwndDlg, IDC_THROBBER, false);
1165 SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT,
1166 success ?
1167 Str(ST_REPORTSUBMITSUCCESS).c_str() :
1168 Str(ST_SUBMITFAILED).c_str());
1169 MaybeResizeProgressText(hwndDlg);
1170 // close dialog after 5 seconds
1171 SetTimer(hwndDlg, 0, 5000, nullptr);
1172 //
1173 return TRUE;
1174 }
1176 case WM_LBUTTONDOWN: {
1177 HWND hwndEmail = GetDlgItem(hwndDlg, IDC_EMAILTEXT);
1178 POINT p = { LOWORD(lParam), HIWORD(lParam) };
1179 // if the email edit control is clicked, enable it,
1180 // check the email checkbox, and focus the email edit control
1181 if (ChildWindowFromPoint(hwndDlg, p) == hwndEmail &&
1182 IsWindowEnabled(GetDlgItem(hwndDlg, IDC_RESTARTBUTTON)) &&
1183 !IsWindowEnabled(hwndEmail) &&
1184 IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0) {
1185 CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_CHECKED);
1186 UpdateEmail(hwndDlg);
1187 SetFocus(hwndEmail);
1188 }
1189 break;
1190 }
1192 case WM_TIMER: {
1193 // The "1" gets used down in UIShowCrashUI to indicate that we at least
1194 // tried to send the report.
1195 EndCrashReporterDialog(hwndDlg, 1);
1196 return FALSE;
1197 }
1199 case WM_CLOSE: {
1200 EndCrashReporterDialog(hwndDlg, 0);
1201 return FALSE;
1202 }
1203 }
1204 return FALSE;
1205 }
1207 static wstring UTF8ToWide(const string& utf8, bool *success)
1208 {
1209 wchar_t* buffer = nullptr;
1210 int buffer_size = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(),
1211 -1, nullptr, 0);
1212 if(buffer_size == 0) {
1213 if (success)
1214 *success = false;
1215 return L"";
1216 }
1218 buffer = new wchar_t[buffer_size];
1219 if(buffer == nullptr) {
1220 if (success)
1221 *success = false;
1222 return L"";
1223 }
1225 MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(),
1226 -1, buffer, buffer_size);
1227 wstring str = buffer;
1228 delete [] buffer;
1230 if (success)
1231 *success = true;
1233 return str;
1234 }
1236 static string WideToMBCP(const wstring& wide,
1237 unsigned int cp,
1238 bool* success = nullptr)
1239 {
1240 char* buffer = nullptr;
1241 int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(),
1242 -1, nullptr, 0, nullptr, nullptr);
1243 if(buffer_size == 0) {
1244 if (success)
1245 *success = false;
1246 return "";
1247 }
1249 buffer = new char[buffer_size];
1250 if(buffer == nullptr) {
1251 if (success)
1252 *success = false;
1253 return "";
1254 }
1256 WideCharToMultiByte(cp, 0, wide.c_str(),
1257 -1, buffer, buffer_size, nullptr, nullptr);
1258 string mb = buffer;
1259 delete [] buffer;
1261 if (success)
1262 *success = true;
1264 return mb;
1265 }
1267 string WideToUTF8(const wstring& wide, bool* success)
1268 {
1269 return WideToMBCP(wide, CP_UTF8, success);
1270 }
1272 /* === Crashreporter UI Functions === */
1274 bool UIInit()
1275 {
1276 for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) {
1277 gAttachedBottom.insert(kDefaultAttachedBottom[i]);
1278 }
1280 DoInitCommonControls();
1282 return true;
1283 }
1285 void UIShutdown()
1286 {
1287 }
1289 void UIShowDefaultUI()
1290 {
1291 MessageBox(nullptr, Str(ST_CRASHREPORTERDEFAULT).c_str(),
1292 L"Crash Reporter",
1293 MB_OK | MB_ICONSTOP);
1294 }
1296 bool UIShowCrashUI(const string& dumpFile,
1297 const StringTable& queryParameters,
1298 const string& sendURL,
1299 const vector<string>& restartArgs)
1300 {
1301 gSendData.hDlg = nullptr;
1302 gSendData.dumpFile = UTF8ToWide(dumpFile);
1303 gSendData.sendURL = UTF8ToWide(sendURL);
1305 for (StringTable::const_iterator i = queryParameters.begin();
1306 i != queryParameters.end();
1307 i++) {
1308 gQueryParameters[UTF8ToWide(i->first)] = UTF8ToWide(i->second);
1309 }
1311 if (gQueryParameters.find(L"Vendor") != gQueryParameters.end()) {
1312 gCrashReporterKey = L"Software\\";
1313 if (!gQueryParameters[L"Vendor"].empty()) {
1314 gCrashReporterKey += gQueryParameters[L"Vendor"] + L"\\";
1315 }
1316 gCrashReporterKey += gQueryParameters[L"ProductName"] + L"\\Crash Reporter";
1317 }
1319 if (gQueryParameters.find(L"URL") != gQueryParameters.end())
1320 gURLParameter = gQueryParameters[L"URL"];
1322 gRestartArgs = restartArgs;
1324 if (gStrings.find("isRTL") != gStrings.end() &&
1325 gStrings["isRTL"] == "yes")
1326 gRTLlayout = true;
1328 return 1 == DialogBoxParamMaybeRTL(IDD_SENDDIALOG, nullptr,
1329 (DLGPROC)CrashReporterDialogProc, 0);
1330 }
1332 void UIError_impl(const string& message)
1333 {
1334 wstring title = Str(ST_CRASHREPORTERTITLE);
1335 if (title.empty())
1336 title = L"Crash Reporter Error";
1338 MessageBox(nullptr, UTF8ToWide(message).c_str(), title.c_str(),
1339 MB_OK | MB_ICONSTOP);
1340 }
1342 bool UIGetIniPath(string& path)
1343 {
1344 wchar_t fileName[MAX_PATH];
1345 if (GetModuleFileName(nullptr, fileName, MAX_PATH)) {
1346 // get crashreporter ini
1347 wchar_t* s = wcsrchr(fileName, '.');
1348 if (s) {
1349 wcscpy(s, L".ini");
1350 path = WideToUTF8(fileName);
1351 return true;
1352 }
1353 }
1355 return false;
1356 }
1358 bool UIGetSettingsPath(const string& vendor,
1359 const string& product,
1360 string& settings_path)
1361 {
1362 wchar_t path[MAX_PATH];
1363 HRESULT hRes = SHGetFolderPath(nullptr,
1364 CSIDL_APPDATA,
1365 nullptr,
1366 0,
1367 path);
1368 if (FAILED(hRes)) {
1369 // This provides a fallback for getting the path to APPDATA by querying the
1370 // registry when the call to SHGetFolderPath is unable to provide this path
1371 // (Bug 513958).
1372 HKEY key;
1373 DWORD type, size, dwRes;
1374 dwRes = ::RegOpenKeyExW(HKEY_CURRENT_USER,
1375 L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders",
1376 0,
1377 KEY_READ,
1378 &key);
1379 if (dwRes != ERROR_SUCCESS)
1380 return false;
1382 dwRes = RegQueryValueExW(key,
1383 L"AppData",
1384 nullptr,
1385 &type,
1386 (LPBYTE)&path,
1387 &size);
1388 ::RegCloseKey(key);
1389 // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the
1390 // buffer size must not equal 0, and the buffer size be a multiple of 2.
1391 if (dwRes != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0)
1392 return false;
1393 }
1395 if (!vendor.empty()) {
1396 PathAppend(path, UTF8ToWide(vendor).c_str());
1397 }
1398 PathAppend(path, UTF8ToWide(product).c_str());
1399 PathAppend(path, L"Crash Reports");
1400 settings_path = WideToUTF8(path);
1401 return true;
1402 }
1404 bool UIEnsurePathExists(const string& path)
1405 {
1406 if (CreateDirectory(UTF8ToWide(path).c_str(), nullptr) == 0) {
1407 if (GetLastError() != ERROR_ALREADY_EXISTS)
1408 return false;
1409 }
1411 return true;
1412 }
1414 bool UIFileExists(const string& path)
1415 {
1416 DWORD attrs = GetFileAttributes(UTF8ToWide(path).c_str());
1417 return (attrs != INVALID_FILE_ATTRIBUTES);
1418 }
1420 bool UIMoveFile(const string& oldfile, const string& newfile)
1421 {
1422 if (oldfile == newfile)
1423 return true;
1425 return MoveFile(UTF8ToWide(oldfile).c_str(), UTF8ToWide(newfile).c_str())
1426 == TRUE;
1427 }
1429 bool UIDeleteFile(const string& oldfile)
1430 {
1431 return DeleteFile(UTF8ToWide(oldfile).c_str()) == TRUE;
1432 }
1434 ifstream* UIOpenRead(const string& filename)
1435 {
1436 // adapted from breakpad's src/common/windows/http_upload.cc
1438 // The "open" method on pre-MSVC8 ifstream implementations doesn't accept a
1439 // wchar_t* filename, so use _wfopen directly in that case. For VC8 and
1440 // later, _wfopen has been deprecated in favor of _wfopen_s, which does
1441 // not exist in earlier versions, so let the ifstream open the file itself.
1442 #if _MSC_VER >= 1400 // MSVC 2005/8
1443 ifstream* file = new ifstream();
1444 file->open(UTF8ToWide(filename).c_str(), ios::in);
1445 #elif defined(_MSC_VER)
1446 ifstream* file = new ifstream(_wfopen(UTF8ToWide(filename).c_str(), L"r"));
1447 #else // GCC
1448 ifstream* file = new ifstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(),
1449 ios::in);
1450 #endif // _MSC_VER >= 1400
1452 return file;
1453 }
1455 ofstream* UIOpenWrite(const string& filename, bool append) // append=false
1456 {
1457 // adapted from breakpad's src/common/windows/http_upload.cc
1459 // The "open" method on pre-MSVC8 ifstream implementations doesn't accept a
1460 // wchar_t* filename, so use _wfopen directly in that case. For VC8 and
1461 // later, _wfopen has been deprecated in favor of _wfopen_s, which does
1462 // not exist in earlier versions, so let the ifstream open the file itself.
1463 #if _MSC_VER >= 1400 // MSVC 2005/8
1464 ofstream* file = new ofstream();
1465 file->open(UTF8ToWide(filename).c_str(), append ? ios::out | ios::app
1466 : ios::out);
1467 #elif defined(_MSC_VER)
1468 ofstream* file = new ofstream(_wfopen(UTF8ToWide(filename).c_str(),
1469 append ? L"a" : L"w"));
1470 #else // GCC
1471 ofstream* file = new ofstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(),
1472 append ? ios::out | ios::app : ios::out);
1473 #endif // _MSC_VER >= 1400
1475 return file;
1476 }
1478 struct FileData
1479 {
1480 FILETIME timestamp;
1481 wstring path;
1482 };
1484 static bool CompareFDTime(const FileData& fd1, const FileData& fd2)
1485 {
1486 return CompareFileTime(&fd1.timestamp, &fd2.timestamp) > 0;
1487 }
1489 void UIPruneSavedDumps(const std::string& directory)
1490 {
1491 wstring wdirectory = UTF8ToWide(directory);
1493 WIN32_FIND_DATA fdata;
1494 wstring findpath = wdirectory + L"\\*.dmp";
1495 HANDLE dirlist = FindFirstFile(findpath.c_str(), &fdata);
1496 if (dirlist == INVALID_HANDLE_VALUE)
1497 return;
1499 vector<FileData> dumpfiles;
1501 for (BOOL ok = true; ok; ok = FindNextFile(dirlist, &fdata)) {
1502 FileData fd = {fdata.ftLastWriteTime, wdirectory + L"\\" + fdata.cFileName};
1503 dumpfiles.push_back(fd);
1504 }
1506 sort(dumpfiles.begin(), dumpfiles.end(), CompareFDTime);
1508 while (dumpfiles.size() > kSaveCount) {
1509 // get the path of the oldest file
1510 wstring path = (--dumpfiles.end())->path;
1511 DeleteFile(path.c_str());
1513 // s/.dmp/.extra/
1514 path.replace(path.size() - 4, 4, L".extra");
1515 DeleteFile(path.c_str());
1517 dumpfiles.pop_back();
1518 }
1519 }