toolkit/crashreporter/client/crashreporter_win.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:64b53ab9f531
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/. */
5
6 #ifdef WIN32_LEAN_AND_MEAN
7 #undef WIN32_LEAN_AND_MEAN
8 #endif
9
10 #define NOMINMAX
11
12 #include "crashreporter.h"
13
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"
27
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
35
36 #define WM_UPLOADCOMPLETE WM_APP
37
38 // Thanks, Windows.h :(
39 #undef min
40 #undef max
41
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;
50
51 using namespace CrashReporter;
52
53 typedef struct {
54 HWND hDlg;
55 wstring dumpFile;
56 map<wstring,wstring> queryParameters;
57 wstring sendURL;
58
59 wstring serverResponse;
60 } SendThreadData;
61
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;
77
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;
86
87 // When vertically resizing the dialog, these items should move down
88 static set<UINT> gAttachedBottom;
89
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 };
103
104 static wstring UTF8ToWide(const string& utf8, bool *success = 0);
105 static DWORD WINAPI SendThreadProc(LPVOID param);
106
107 static wstring Str(const char* key)
108 {
109 return UTF8ToWide(gStrings[key]);
110 }
111
112 /* === win32 helper functions === */
113
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 }
123
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;
132
133 return false;
134 }
135
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;
140
141 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_SET_VALUE, &hRegKey)
142 == ERROR_SUCCESS) {
143 RegDeleteValue(hRegKey, valueName);
144 RegCloseKey(hRegKey);
145 }
146
147 if (RegOpenKeyEx(HKEY_CURRENT_USER, key, 0, KEY_SET_VALUE, &hRegKey)
148 == ERROR_SUCCESS) {
149 RegDeleteValue(hRegKey, valueName);
150 RegCloseKey(hRegKey);
151 }
152 }
153
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 }
183
184 return found;
185 }
186
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;
194
195 // remove the old value from the registry if it exists
196 RemoveUnusedValues(key, SUBMIT_REPORT_OLD);
197
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 }
204
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 }
216
217 return false;
218 }
219
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 }
242
243 return found;
244 }
245
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 }
258
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 }
290
291 #define TS_DRAW 2
292 #define BP_CHECKBOX 3
293
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);
302
303
304 static void GetThemeSizes(HWND hwnd)
305 {
306 HMODULE themeDLL = LoadLibrary(L"uxtheme.dll");
307
308 if (!themeDLL)
309 return;
310
311 OpenThemeDataPtr openTheme =
312 (OpenThemeDataPtr)GetProcAddress(themeDLL, "OpenThemeData");
313 CloseThemeDataPtr closeTheme =
314 (CloseThemeDataPtr)GetProcAddress(themeDLL, "CloseThemeData");
315 GetThemePartSizePtr getThemePartSize =
316 (GetThemePartSizePtr)GetProcAddress(themeDLL, "GetThemePartSize");
317
318 if (!openTheme || !closeTheme || !getThemePartSize) {
319 FreeLibrary(themeDLL);
320 return;
321 }
322
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 }
335
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 }
342
343 static void SetDlgItemVisible(HWND hwndDlg, UINT item, bool visible)
344 {
345 HWND hwnd = GetDlgItem(hwndDlg, item);
346
347 ShowWindow(hwnd, visible ? SW_SHOW : SW_HIDE);
348 }
349
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;
358
359 SetWindowLong(hwnd, GWL_STYLE, style);
360 }
361
362 /* === Crash Reporting Dialog === */
363
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 }
372
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 }
389
390 static DWORD WINAPI SendThreadProc(LPVOID param)
391 {
392 bool finishedOk;
393 SendThreadData* td = (SendThreadData*)param;
394
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 }
415
416 PostMessage(td->hDlg, WM_UPLOADCOMPLETE, finishedOk ? 1 : 0, 0);
417
418 return 0;
419 }
420
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);
428
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);
435
436 EndDialog(hwndDlg, code);
437 }
438
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);
449
450 wchar_t text[1024];
451 GetWindowText(hwndProgress, text, 1024);
452
453 if (!GetTextExtentPoint32(hdc, text, wcslen(text), &size))
454 return;
455
456 if (size.cx < (rect.right - rect.left))
457 return;
458
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;
466
467 MoveWindow(hwndProgress, rect.left, rect.top,
468 rect.right - rect.left,
469 wantedHeight,
470 TRUE);
471
472 gAttachedBottom.clear();
473 gAttachedBottom.insert(IDC_CLOSEBUTTON);
474 gAttachedBottom.insert(IDC_RESTARTBUTTON);
475
476 StretchDialog(hwndDlg, diff);
477
478 for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) {
479 gAttachedBottom.insert(kDefaultAttachedBottom[i]);
480 }
481 }
482
483 static void MaybeSendReport(HWND hwndDlg)
484 {
485 if (!IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK)) {
486 EndCrashReporterDialog(hwndDlg, 0);
487 return;
488 }
489
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);
499
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;
509
510 gThreadHandle = CreateThread(nullptr, 0, SendThreadProc, &gSendData, 0,
511 nullptr);
512 }
513
514 static void RestartApplication()
515 {
516 wstring cmdLine;
517
518 for (unsigned int i = 0; i < gRestartArgs.size(); i++) {
519 cmdLine += L"\"" + UTF8ToWide(gRestartArgs[i]) + L"\" ";
520 }
521
522 STARTUPINFO si;
523 PROCESS_INFORMATION pi;
524
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));
530
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 }
537
538 static void ShowReportInfo(HWND hwndDlg)
539 {
540 wstring description;
541
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 }
550
551 description += L"\n";
552 description += Str(ST_EXTRAREPORTINFO);
553
554 SetDlgItemText(hwndDlg, IDC_VIEWREPORTTEXT, description.c_str());
555 }
556
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 }
565
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 }
580
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 }
591
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 }
608
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 }
617
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 }
627
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];
634
635 GetWindowText(hwndEdit, current, MAX_COMMENT_LENGTH + 1);
636 DWORD selStart, selEnd;
637 SendMessage(hwndEdit, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd);
638
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 }
649
650 // current string length + replacement text length
651 // - replaced selection length
652 return BytesInUTF8(current) + BytesInUTF8(insert) - selectionLength;
653 }
654
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;
660
661 if (super == nullptr)
662 super = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA);
663
664 switch (uMsg) {
665 case WM_PAINT: {
666 HDC hdc;
667 PAINTSTRUCT ps;
668 RECT r;
669 wchar_t windowText[1024];
670
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);
675
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 }
694
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;
702
703 wchar_t ch[2] = { (wchar_t)wParam, 0 };
704 if (NewTextLength(hwnd, ch) > MAX_COMMENT_LENGTH)
705 return 0;
706
707 break;
708 }
709
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;
716
717 if (pastedText)
718 newSize = NewTextLength(hwnd, pastedText);
719
720 GlobalUnlock(hg);
721 CloseClipboard();
722
723 if (newSize > MAX_COMMENT_LENGTH)
724 return 0;
725 }
726 break;
727 }
728
729 case WM_SETFOCUS:
730 case WM_KILLFOCUS: {
731 RECT r;
732 GetClientRect(hwnd, &r);
733 InvalidateRect(hwnd, &r, TRUE);
734 break;
735 }
736
737 case WM_DESTROY: {
738 // cleanup our property
739 HGLOBAL hData = RemoveProp(hwnd, L"PROP_GRAYTEXT");
740 if (hData)
741 GlobalFree(hData);
742 }
743 }
744
745 return CallWindowProc(super, hwnd, uMsg, wParam, lParam);
746 }
747
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;
758
759 wchar_t oldText[1024];
760 GetWindowText(hwndButton, oldText, 1024);
761
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;
782
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 }
798
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 };
812
813 RECT dlgRect;
814 GetClientRect(hwndDlg, &dlgRect);
815
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 }
830
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 }
843
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);
860
861 pMyDlgTemplate->exStyle |= WS_EX_LAYOUTRTL;
862
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 }
872
873 return rv;
874 }
875
876
877 static BOOL CALLBACK CrashReporterDialogProc(HWND hwndDlg, UINT message,
878 WPARAM wParam, LPARAM lParam)
879 {
880 static int sHeight = 0;
881
882 bool success;
883 bool enabled;
884
885 switch (message) {
886 case WM_INITDIALOG: {
887 GetThemeSizes(hwndDlg);
888 RECT r;
889 GetClientRect(hwndDlg, &r);
890 sHeight = r.bottom - r.top;
891
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);
897
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());
904
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());
911
912 if (!CheckBoolKey(gCrashReporterKey.c_str(),
913 SUBMIT_REPORT_VALUE, &enabled))
914 enabled = ShouldEnableSending();
915
916 CheckDlgButton(hwndDlg, IDC_SUBMITREPORTCHECK, enabled ? BST_CHECKED
917 : BST_UNCHECKED);
918 SubmitReportChecked(hwndDlg);
919
920 HWND hwndComment = GetDlgItem(hwndDlg, IDC_COMMENTTEXT);
921 WNDPROC OldWndProc = (WNDPROC)SetWindowLongPtr(hwndComment,
922 GWLP_WNDPROC,
923 (LONG_PTR)EditSubclassProc);
924
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);
931
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());
938
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 }
946
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());
953
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 }
960
961 wstring email;
962 if (GetStringKey(gCrashReporterKey.c_str(), EMAIL_VALUE, email)) {
963 SetDlgItemText(hwndDlg, IDC_EMAILTEXT, email.c_str());
964 }
965
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);
976
977 SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, Str(ST_REPORTPRESUBMIT).c_str());
978
979 RECT closeRect;
980 HWND hwndClose = GetDlgItem(hwndDlg, IDC_CLOSEBUTTON);
981 GetRelativeRect(hwndClose, hwndDlg, &closeRect);
982
983 RECT restartRect;
984 HWND hwndRestart = GetDlgItem(hwndDlg, IDC_RESTARTBUTTON);
985 GetRelativeRect(hwndRestart, hwndDlg, &restartRect);
986
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());
994
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));
1010
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);
1035
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);
1041
1042 wstring description = Str(ST_CRASHREPORTERHEADER);
1043 description += L"\n\n";
1044 description += Str(ST_CRASHREPORTERDESCRIPTION);
1045 SetDlgItemText(hwndDlg, IDC_DESCRIPTIONTEXT, description.c_str());
1046
1047
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);
1064
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);
1070
1071 SetDlgItemVisible(hwndDlg, IDC_INCLUDEURLCHECK, false);
1072
1073 gAttachedBottom.erase(IDC_VIEWREPORTBUTTON);
1074 gAttachedBottom.erase(IDC_SUBMITREPORTCHECK);
1075 gAttachedBottom.erase(IDC_COMMENTTEXT);
1076
1077 StretchDialog(hwndDlg, urlCheckRect.top - emailCheckRect.top);
1078
1079 gAttachedBottom.insert(IDC_VIEWREPORTBUTTON);
1080 gAttachedBottom.insert(IDC_SUBMITREPORTCHECK);
1081 gAttachedBottom.insert(IDC_COMMENTTEXT);
1082 }
1083
1084 MaybeResizeProgressText(hwndDlg);
1085
1086 // Open the AVI resource for the throbber
1087 Animate_Open(GetDlgItem(hwndDlg, IDC_THROBBER),
1088 MAKEINTRESOURCE(IDR_THROBBER));
1089
1090 UpdateURL(hwndDlg);
1091 UpdateEmail(hwndDlg);
1092
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);
1110
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);
1115
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 }
1154
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);
1164
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 }
1175
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 }
1191
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 }
1198
1199 case WM_CLOSE: {
1200 EndCrashReporterDialog(hwndDlg, 0);
1201 return FALSE;
1202 }
1203 }
1204 return FALSE;
1205 }
1206
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 }
1217
1218 buffer = new wchar_t[buffer_size];
1219 if(buffer == nullptr) {
1220 if (success)
1221 *success = false;
1222 return L"";
1223 }
1224
1225 MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(),
1226 -1, buffer, buffer_size);
1227 wstring str = buffer;
1228 delete [] buffer;
1229
1230 if (success)
1231 *success = true;
1232
1233 return str;
1234 }
1235
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 }
1248
1249 buffer = new char[buffer_size];
1250 if(buffer == nullptr) {
1251 if (success)
1252 *success = false;
1253 return "";
1254 }
1255
1256 WideCharToMultiByte(cp, 0, wide.c_str(),
1257 -1, buffer, buffer_size, nullptr, nullptr);
1258 string mb = buffer;
1259 delete [] buffer;
1260
1261 if (success)
1262 *success = true;
1263
1264 return mb;
1265 }
1266
1267 string WideToUTF8(const wstring& wide, bool* success)
1268 {
1269 return WideToMBCP(wide, CP_UTF8, success);
1270 }
1271
1272 /* === Crashreporter UI Functions === */
1273
1274 bool UIInit()
1275 {
1276 for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) {
1277 gAttachedBottom.insert(kDefaultAttachedBottom[i]);
1278 }
1279
1280 DoInitCommonControls();
1281
1282 return true;
1283 }
1284
1285 void UIShutdown()
1286 {
1287 }
1288
1289 void UIShowDefaultUI()
1290 {
1291 MessageBox(nullptr, Str(ST_CRASHREPORTERDEFAULT).c_str(),
1292 L"Crash Reporter",
1293 MB_OK | MB_ICONSTOP);
1294 }
1295
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);
1304
1305 for (StringTable::const_iterator i = queryParameters.begin();
1306 i != queryParameters.end();
1307 i++) {
1308 gQueryParameters[UTF8ToWide(i->first)] = UTF8ToWide(i->second);
1309 }
1310
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 }
1318
1319 if (gQueryParameters.find(L"URL") != gQueryParameters.end())
1320 gURLParameter = gQueryParameters[L"URL"];
1321
1322 gRestartArgs = restartArgs;
1323
1324 if (gStrings.find("isRTL") != gStrings.end() &&
1325 gStrings["isRTL"] == "yes")
1326 gRTLlayout = true;
1327
1328 return 1 == DialogBoxParamMaybeRTL(IDD_SENDDIALOG, nullptr,
1329 (DLGPROC)CrashReporterDialogProc, 0);
1330 }
1331
1332 void UIError_impl(const string& message)
1333 {
1334 wstring title = Str(ST_CRASHREPORTERTITLE);
1335 if (title.empty())
1336 title = L"Crash Reporter Error";
1337
1338 MessageBox(nullptr, UTF8ToWide(message).c_str(), title.c_str(),
1339 MB_OK | MB_ICONSTOP);
1340 }
1341
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 }
1354
1355 return false;
1356 }
1357
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;
1381
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 }
1394
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 }
1403
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 }
1410
1411 return true;
1412 }
1413
1414 bool UIFileExists(const string& path)
1415 {
1416 DWORD attrs = GetFileAttributes(UTF8ToWide(path).c_str());
1417 return (attrs != INVALID_FILE_ATTRIBUTES);
1418 }
1419
1420 bool UIMoveFile(const string& oldfile, const string& newfile)
1421 {
1422 if (oldfile == newfile)
1423 return true;
1424
1425 return MoveFile(UTF8ToWide(oldfile).c_str(), UTF8ToWide(newfile).c_str())
1426 == TRUE;
1427 }
1428
1429 bool UIDeleteFile(const string& oldfile)
1430 {
1431 return DeleteFile(UTF8ToWide(oldfile).c_str()) == TRUE;
1432 }
1433
1434 ifstream* UIOpenRead(const string& filename)
1435 {
1436 // adapted from breakpad's src/common/windows/http_upload.cc
1437
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
1451
1452 return file;
1453 }
1454
1455 ofstream* UIOpenWrite(const string& filename, bool append) // append=false
1456 {
1457 // adapted from breakpad's src/common/windows/http_upload.cc
1458
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
1474
1475 return file;
1476 }
1477
1478 struct FileData
1479 {
1480 FILETIME timestamp;
1481 wstring path;
1482 };
1483
1484 static bool CompareFDTime(const FileData& fd1, const FileData& fd2)
1485 {
1486 return CompareFileTime(&fd1.timestamp, &fd2.timestamp) > 0;
1487 }
1488
1489 void UIPruneSavedDumps(const std::string& directory)
1490 {
1491 wstring wdirectory = UTF8ToWide(directory);
1492
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;
1498
1499 vector<FileData> dumpfiles;
1500
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 }
1505
1506 sort(dumpfiles.begin(), dumpfiles.end(), CompareFDTime);
1507
1508 while (dumpfiles.size() > kSaveCount) {
1509 // get the path of the oldest file
1510 wstring path = (--dumpfiles.end())->path;
1511 DeleteFile(path.c_str());
1512
1513 // s/.dmp/.extra/
1514 path.replace(path.size() - 4, 4, L".extra");
1515 DeleteFile(path.c_str());
1516
1517 dumpfiles.pop_back();
1518 }
1519 }

mercurial