|
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 #import <Cocoa/Cocoa.h> |
|
7 #import <CoreFoundation/CoreFoundation.h> |
|
8 #include "crashreporter.h" |
|
9 #include "crashreporter_osx.h" |
|
10 #include <crt_externs.h> |
|
11 #include <spawn.h> |
|
12 #include <sys/stat.h> |
|
13 #include <sys/types.h> |
|
14 #include <fcntl.h> |
|
15 #include <sstream> |
|
16 |
|
17 using std::string; |
|
18 using std::vector; |
|
19 using std::ostringstream; |
|
20 |
|
21 using namespace CrashReporter; |
|
22 |
|
23 static NSAutoreleasePool* gMainPool; |
|
24 static CrashReporterUI* gUI = 0; |
|
25 static string gDumpFile; |
|
26 static StringTable gQueryParameters; |
|
27 static string gURLParameter; |
|
28 static string gSendURL; |
|
29 static vector<string> gRestartArgs; |
|
30 static bool gDidTrySend = false; |
|
31 static bool gRTLlayout = false; |
|
32 |
|
33 static cpu_type_t pref_cpu_types[2] = { |
|
34 #if defined(__i386__) |
|
35 CPU_TYPE_X86, |
|
36 #elif defined(__x86_64__) |
|
37 CPU_TYPE_X86_64, |
|
38 #elif defined(__ppc__) |
|
39 CPU_TYPE_POWERPC, |
|
40 #endif |
|
41 CPU_TYPE_ANY }; |
|
42 |
|
43 #define NSSTR(s) [NSString stringWithUTF8String:(s).c_str()] |
|
44 |
|
45 static NSString* Str(const char* aName) |
|
46 { |
|
47 string str = gStrings[aName]; |
|
48 if (str.empty()) str = "?"; |
|
49 return NSSTR(str); |
|
50 } |
|
51 |
|
52 static bool RestartApplication() |
|
53 { |
|
54 vector<char*> argv(gRestartArgs.size() + 1); |
|
55 |
|
56 posix_spawnattr_t spawnattr; |
|
57 if (posix_spawnattr_init(&spawnattr) != 0) { |
|
58 return false; |
|
59 } |
|
60 |
|
61 // Set spawn attributes. |
|
62 size_t attr_count = sizeof(pref_cpu_types) / sizeof(pref_cpu_types[0]); |
|
63 size_t attr_ocount = 0; |
|
64 if (posix_spawnattr_setbinpref_np(&spawnattr, |
|
65 attr_count, |
|
66 pref_cpu_types, |
|
67 &attr_ocount) != 0 || |
|
68 attr_ocount != attr_count) { |
|
69 posix_spawnattr_destroy(&spawnattr); |
|
70 return false; |
|
71 } |
|
72 |
|
73 unsigned int i; |
|
74 for (i = 0; i < gRestartArgs.size(); i++) { |
|
75 argv[i] = (char*)gRestartArgs[i].c_str(); |
|
76 } |
|
77 argv[i] = 0; |
|
78 |
|
79 char **env = NULL; |
|
80 char ***nsEnv = _NSGetEnviron(); |
|
81 if (nsEnv) |
|
82 env = *nsEnv; |
|
83 int result = posix_spawnp(NULL, |
|
84 argv[0], |
|
85 NULL, |
|
86 &spawnattr, |
|
87 &argv[0], |
|
88 env); |
|
89 |
|
90 posix_spawnattr_destroy(&spawnattr); |
|
91 |
|
92 return result == 0; |
|
93 } |
|
94 |
|
95 @implementation CrashReporterUI |
|
96 |
|
97 -(void)awakeFromNib |
|
98 { |
|
99 gUI = self; |
|
100 [mWindow center]; |
|
101 |
|
102 [mWindow setTitle:[[NSBundle mainBundle] |
|
103 objectForInfoDictionaryKey:@"CFBundleName"]]; |
|
104 } |
|
105 |
|
106 -(void)showCrashUI:(const string&)dumpfile |
|
107 queryParameters:(const StringTable&)queryParameters |
|
108 sendURL:(const string&)sendURL |
|
109 { |
|
110 gDumpFile = dumpfile; |
|
111 gQueryParameters = queryParameters; |
|
112 gSendURL = sendURL; |
|
113 |
|
114 [mWindow setTitle:Str(ST_CRASHREPORTERTITLE)]; |
|
115 [mHeaderLabel setStringValue:Str(ST_CRASHREPORTERHEADER)]; |
|
116 |
|
117 NSRect viewReportFrame = [mViewReportButton frame]; |
|
118 [mViewReportButton setTitle:Str(ST_VIEWREPORT)]; |
|
119 [mViewReportButton sizeToFit]; |
|
120 if (gRTLlayout) { |
|
121 // sizeToFit will keep the left side fixed, so realign |
|
122 float oldWidth = viewReportFrame.size.width; |
|
123 viewReportFrame = [mViewReportButton frame]; |
|
124 viewReportFrame.origin.x += oldWidth - viewReportFrame.size.width; |
|
125 [mViewReportButton setFrame: viewReportFrame]; |
|
126 } |
|
127 |
|
128 [mSubmitReportButton setTitle:Str(ST_CHECKSUBMIT)]; |
|
129 [mIncludeURLButton setTitle:Str(ST_CHECKURL)]; |
|
130 [mEmailMeButton setTitle:Str(ST_CHECKEMAIL)]; |
|
131 [mViewReportOkButton setTitle:Str(ST_OK)]; |
|
132 |
|
133 [mCommentText setPlaceholder:Str(ST_COMMENTGRAYTEXT)]; |
|
134 if (gRTLlayout) |
|
135 [mCommentText toggleBaseWritingDirection:self]; |
|
136 [[mEmailText cell] setPlaceholderString:Str(ST_EMAILGRAYTEXT)]; |
|
137 |
|
138 if (gQueryParameters.find("URL") != gQueryParameters.end()) { |
|
139 // save the URL value in case the checkbox gets unchecked |
|
140 gURLParameter = gQueryParameters["URL"]; |
|
141 } |
|
142 else { |
|
143 // no URL specified, hide checkbox |
|
144 [mIncludeURLButton removeFromSuperview]; |
|
145 // shrink window to fit |
|
146 NSRect frame = [mWindow frame]; |
|
147 NSRect includeURLFrame = [mIncludeURLButton frame]; |
|
148 NSRect emailFrame = [mEmailMeButton frame]; |
|
149 int buttonMask = [mViewReportButton autoresizingMask]; |
|
150 int checkMask = [mSubmitReportButton autoresizingMask]; |
|
151 int commentScrollMask = [mCommentScrollView autoresizingMask]; |
|
152 |
|
153 [mViewReportButton setAutoresizingMask:NSViewMinYMargin]; |
|
154 [mSubmitReportButton setAutoresizingMask:NSViewMinYMargin]; |
|
155 [mCommentScrollView setAutoresizingMask:NSViewMinYMargin]; |
|
156 |
|
157 // remove all the space in between |
|
158 frame.size.height -= includeURLFrame.origin.y - emailFrame.origin.y; |
|
159 [mWindow setFrame:frame display: true animate:NO]; |
|
160 |
|
161 [mViewReportButton setAutoresizingMask:buttonMask]; |
|
162 [mSubmitReportButton setAutoresizingMask:checkMask]; |
|
163 [mCommentScrollView setAutoresizingMask:commentScrollMask]; |
|
164 } |
|
165 |
|
166 // resize some buttons horizontally and possibly some controls vertically |
|
167 [self doInitialResizing]; |
|
168 |
|
169 // load default state of submit checkbox |
|
170 // we don't just do this via IB because we want the default to be |
|
171 // off a certain percentage of the time |
|
172 BOOL submitChecked = NO; |
|
173 NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; |
|
174 if (nil != [userDefaults objectForKey:@"submitReport"]) { |
|
175 submitChecked = [userDefaults boolForKey:@"submitReport"]; |
|
176 } |
|
177 else { |
|
178 // use compile-time specified enable percentage |
|
179 submitChecked = ShouldEnableSending(); |
|
180 [userDefaults setBool:submitChecked forKey:@"submitReport"]; |
|
181 } |
|
182 [mSubmitReportButton setState:(submitChecked ? NSOnState : NSOffState)]; |
|
183 |
|
184 [self updateSubmit]; |
|
185 [self updateURL]; |
|
186 [self updateEmail]; |
|
187 |
|
188 [mWindow makeKeyAndOrderFront:nil]; |
|
189 } |
|
190 |
|
191 -(void)showErrorUI:(const string&)message |
|
192 { |
|
193 [self setView: mErrorView animate: NO]; |
|
194 |
|
195 [mErrorHeaderLabel setStringValue:Str(ST_CRASHREPORTERHEADER)]; |
|
196 [self setStringFitVertically:mErrorLabel |
|
197 string:NSSTR(message) |
|
198 resizeWindow:YES]; |
|
199 [mErrorCloseButton setTitle:Str(ST_OK)]; |
|
200 |
|
201 [mErrorCloseButton setKeyEquivalent:@"\r"]; |
|
202 [mWindow makeFirstResponder:mErrorCloseButton]; |
|
203 [mWindow makeKeyAndOrderFront:nil]; |
|
204 } |
|
205 |
|
206 -(void)showReportInfo |
|
207 { |
|
208 NSDictionary* boldAttr = [NSDictionary |
|
209 dictionaryWithObject: |
|
210 [NSFont boldSystemFontOfSize: |
|
211 [NSFont smallSystemFontSize]] |
|
212 forKey:NSFontAttributeName]; |
|
213 NSDictionary* normalAttr = [NSDictionary |
|
214 dictionaryWithObject: |
|
215 [NSFont systemFontOfSize: |
|
216 [NSFont smallSystemFontSize]] |
|
217 forKey:NSFontAttributeName]; |
|
218 |
|
219 [mViewReportTextView setString:@""]; |
|
220 for (StringTable::iterator iter = gQueryParameters.begin(); |
|
221 iter != gQueryParameters.end(); |
|
222 iter++) { |
|
223 NSAttributedString* key = [[NSAttributedString alloc] |
|
224 initWithString:NSSTR(iter->first + ": ") |
|
225 attributes:boldAttr]; |
|
226 NSAttributedString* value = [[NSAttributedString alloc] |
|
227 initWithString:NSSTR(iter->second + "\n") |
|
228 attributes:normalAttr]; |
|
229 [[mViewReportTextView textStorage] appendAttributedString: key]; |
|
230 [[mViewReportTextView textStorage] appendAttributedString: value]; |
|
231 [key release]; |
|
232 [value release]; |
|
233 } |
|
234 |
|
235 NSAttributedString* extra = [[NSAttributedString alloc] |
|
236 initWithString:NSSTR("\n" + gStrings[ST_EXTRAREPORTINFO]) |
|
237 attributes:normalAttr]; |
|
238 [[mViewReportTextView textStorage] appendAttributedString: extra]; |
|
239 [extra release]; |
|
240 } |
|
241 |
|
242 - (void)maybeSubmitReport |
|
243 { |
|
244 if ([mSubmitReportButton state] == NSOnState) { |
|
245 [self setStringFitVertically:mProgressText |
|
246 string:Str(ST_REPORTDURINGSUBMIT) |
|
247 resizeWindow:YES]; |
|
248 // disable all the controls |
|
249 [self enableControls:NO]; |
|
250 [mSubmitReportButton setEnabled:NO]; |
|
251 [mRestartButton setEnabled:NO]; |
|
252 [mCloseButton setEnabled:NO]; |
|
253 [mProgressIndicator startAnimation:self]; |
|
254 gDidTrySend = true; |
|
255 [self sendReport]; |
|
256 } else { |
|
257 [NSApp terminate:self]; |
|
258 } |
|
259 } |
|
260 |
|
261 - (void)closeMeDown:(id)unused |
|
262 { |
|
263 [NSApp terminate:self]; |
|
264 } |
|
265 |
|
266 -(IBAction)submitReportClicked:(id)sender |
|
267 { |
|
268 [self updateSubmit]; |
|
269 NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; |
|
270 [userDefaults setBool:([mSubmitReportButton state] == NSOnState) |
|
271 forKey:@"submitReport"]; |
|
272 [userDefaults synchronize]; |
|
273 } |
|
274 |
|
275 -(IBAction)viewReportClicked:(id)sender |
|
276 { |
|
277 [self showReportInfo]; |
|
278 [NSApp beginSheet:mViewReportWindow modalForWindow:mWindow |
|
279 modalDelegate:nil didEndSelector:nil contextInfo:nil]; |
|
280 } |
|
281 |
|
282 - (IBAction)viewReportOkClicked:(id)sender; |
|
283 { |
|
284 [mViewReportWindow orderOut:nil]; |
|
285 [NSApp endSheet:mViewReportWindow]; |
|
286 } |
|
287 |
|
288 -(IBAction)closeClicked:(id)sender |
|
289 { |
|
290 [self maybeSubmitReport]; |
|
291 } |
|
292 |
|
293 -(IBAction)restartClicked:(id)sender |
|
294 { |
|
295 RestartApplication(); |
|
296 [self maybeSubmitReport]; |
|
297 } |
|
298 |
|
299 - (IBAction)includeURLClicked:(id)sender |
|
300 { |
|
301 [self updateURL]; |
|
302 } |
|
303 |
|
304 -(IBAction)emailMeClicked:(id)sender |
|
305 { |
|
306 [self updateEmail]; |
|
307 } |
|
308 |
|
309 -(void)controlTextDidChange:(NSNotification *)note |
|
310 { |
|
311 [self updateEmail]; |
|
312 } |
|
313 |
|
314 - (void)textDidChange:(NSNotification *)aNotification |
|
315 { |
|
316 // update comment parameter |
|
317 if ([[[mCommentText textStorage] mutableString] length] > 0) |
|
318 gQueryParameters["Comments"] = [[[mCommentText textStorage] mutableString] |
|
319 UTF8String]; |
|
320 else |
|
321 gQueryParameters.erase("Comments"); |
|
322 } |
|
323 |
|
324 // Limit the comment field to 500 bytes in UTF-8 |
|
325 - (BOOL)textView:(NSTextView *)aTextView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString |
|
326 { |
|
327 // current string length + replacement text length - replaced range length |
|
328 if (([[aTextView string] |
|
329 lengthOfBytesUsingEncoding:NSUTF8StringEncoding] |
|
330 + [replacementString lengthOfBytesUsingEncoding:NSUTF8StringEncoding] |
|
331 - [[[aTextView string] substringWithRange:affectedCharRange] |
|
332 lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) |
|
333 > MAX_COMMENT_LENGTH) { |
|
334 return NO; |
|
335 } |
|
336 return YES; |
|
337 } |
|
338 |
|
339 - (void)doInitialResizing |
|
340 { |
|
341 NSRect windowFrame = [mWindow frame]; |
|
342 NSRect restartFrame = [mRestartButton frame]; |
|
343 NSRect closeFrame = [mCloseButton frame]; |
|
344 // resize close button to fit text |
|
345 float oldCloseWidth = closeFrame.size.width; |
|
346 [mCloseButton setTitle:Str(ST_QUIT)]; |
|
347 [mCloseButton sizeToFit]; |
|
348 closeFrame = [mCloseButton frame]; |
|
349 // move close button left if it grew |
|
350 if (!gRTLlayout) { |
|
351 closeFrame.origin.x -= closeFrame.size.width - oldCloseWidth; |
|
352 } |
|
353 |
|
354 if (gRestartArgs.size() == 0) { |
|
355 [mRestartButton removeFromSuperview]; |
|
356 if (!gRTLlayout) { |
|
357 closeFrame.origin.x = restartFrame.origin.x + |
|
358 (restartFrame.size.width - closeFrame.size.width); |
|
359 } |
|
360 else { |
|
361 closeFrame.origin.x = restartFrame.origin.x; |
|
362 } |
|
363 [mCloseButton setFrame: closeFrame]; |
|
364 [mCloseButton setKeyEquivalent:@"\r"]; |
|
365 } else { |
|
366 [mRestartButton setTitle:Str(ST_RESTART)]; |
|
367 // resize "restart" button |
|
368 float oldRestartWidth = restartFrame.size.width; |
|
369 [mRestartButton sizeToFit]; |
|
370 restartFrame = [mRestartButton frame]; |
|
371 if (!gRTLlayout) { |
|
372 // move left by the amount that the button grew |
|
373 restartFrame.origin.x -= restartFrame.size.width - oldRestartWidth; |
|
374 closeFrame.origin.x -= restartFrame.size.width - oldRestartWidth; |
|
375 } |
|
376 else { |
|
377 // shift the close button right in RTL |
|
378 closeFrame.origin.x += restartFrame.size.width - oldRestartWidth; |
|
379 } |
|
380 [mRestartButton setFrame: restartFrame]; |
|
381 [mCloseButton setFrame: closeFrame]; |
|
382 // possibly resize window if both buttons no longer fit |
|
383 // leave 20 px from either side of the window, and 12 px |
|
384 // between the buttons |
|
385 float neededWidth = closeFrame.size.width + restartFrame.size.width + |
|
386 2*20 + 12; |
|
387 |
|
388 if (neededWidth > windowFrame.size.width) { |
|
389 windowFrame.size.width = neededWidth; |
|
390 [mWindow setFrame:windowFrame display: true animate: NO]; |
|
391 } |
|
392 [mRestartButton setKeyEquivalent:@"\r"]; |
|
393 } |
|
394 |
|
395 NSButton *checkboxes[] = { |
|
396 mSubmitReportButton, |
|
397 mIncludeURLButton, |
|
398 mEmailMeButton |
|
399 }; |
|
400 |
|
401 for (int i=0; i<3; i++) { |
|
402 NSRect frame = [checkboxes[i] frame]; |
|
403 [checkboxes[i] sizeToFit]; |
|
404 if (gRTLlayout) { |
|
405 // sizeToFit will keep the left side fixed, so realign |
|
406 float oldWidth = frame.size.width; |
|
407 frame = [checkboxes[i] frame]; |
|
408 frame.origin.x += oldWidth - frame.size.width; |
|
409 [checkboxes[i] setFrame: frame]; |
|
410 } |
|
411 // keep existing spacing on left side, + 20 px spare on right |
|
412 float neededWidth = frame.origin.x + frame.size.width + 20; |
|
413 if (neededWidth > windowFrame.size.width) { |
|
414 windowFrame.size.width = neededWidth; |
|
415 [mWindow setFrame:windowFrame display: true animate: NO]; |
|
416 } |
|
417 } |
|
418 |
|
419 // do this down here because we may have made the window wider |
|
420 // up above |
|
421 [self setStringFitVertically:mDescriptionLabel |
|
422 string:Str(ST_CRASHREPORTERDESCRIPTION) |
|
423 resizeWindow:YES]; |
|
424 |
|
425 // now pin all the controls (except quit/submit) in place, |
|
426 // if we lengthen the window after this, it's just to lengthen |
|
427 // the progress text, so nothing above that text should move. |
|
428 NSView* views[] = { |
|
429 mSubmitReportButton, |
|
430 mViewReportButton, |
|
431 mCommentScrollView, |
|
432 mIncludeURLButton, |
|
433 mEmailMeButton, |
|
434 mEmailText, |
|
435 mProgressIndicator, |
|
436 mProgressText |
|
437 }; |
|
438 for (unsigned int i=0; i<sizeof(views)/sizeof(views[0]); i++) { |
|
439 [views[i] setAutoresizingMask:NSViewMinYMargin]; |
|
440 } |
|
441 } |
|
442 |
|
443 -(float)setStringFitVertically:(NSControl*)control |
|
444 string:(NSString*)str |
|
445 resizeWindow:(BOOL)resizeWindow |
|
446 { |
|
447 // hack to make the text field grow vertically |
|
448 NSRect frame = [control frame]; |
|
449 float oldHeight = frame.size.height; |
|
450 |
|
451 frame.size.height = 10000; |
|
452 NSSize oldCellSize = [[control cell] cellSizeForBounds: frame]; |
|
453 [control setStringValue: str]; |
|
454 NSSize newCellSize = [[control cell] cellSizeForBounds: frame]; |
|
455 |
|
456 float delta = newCellSize.height - oldCellSize.height; |
|
457 frame.origin.y -= delta; |
|
458 frame.size.height = oldHeight + delta; |
|
459 [control setFrame: frame]; |
|
460 |
|
461 if (resizeWindow) { |
|
462 NSRect frame = [mWindow frame]; |
|
463 frame.origin.y -= delta; |
|
464 frame.size.height += delta; |
|
465 [mWindow setFrame:frame display: true animate: NO]; |
|
466 } |
|
467 |
|
468 return delta; |
|
469 } |
|
470 |
|
471 -(void)setView: (NSView*)v animate: (BOOL)animate |
|
472 { |
|
473 NSRect frame = [mWindow frame]; |
|
474 |
|
475 NSRect oldViewFrame = [[mWindow contentView] frame]; |
|
476 NSRect newViewFrame = [v frame]; |
|
477 |
|
478 frame.origin.y += oldViewFrame.size.height - newViewFrame.size.height; |
|
479 frame.size.height -= oldViewFrame.size.height - newViewFrame.size.height; |
|
480 |
|
481 frame.origin.x += oldViewFrame.size.width - newViewFrame.size.width; |
|
482 frame.size.width -= oldViewFrame.size.width - newViewFrame.size.width; |
|
483 |
|
484 [mWindow setContentView:v]; |
|
485 [mWindow setFrame:frame display:true animate:animate]; |
|
486 } |
|
487 |
|
488 - (void)enableControls:(BOOL)enabled |
|
489 { |
|
490 [mViewReportButton setEnabled:enabled]; |
|
491 [mIncludeURLButton setEnabled:enabled]; |
|
492 [mEmailMeButton setEnabled:enabled]; |
|
493 [mCommentText setEnabled:enabled]; |
|
494 [mCommentScrollView setHasVerticalScroller:enabled]; |
|
495 [self updateEmail]; |
|
496 } |
|
497 |
|
498 -(void)updateSubmit |
|
499 { |
|
500 if ([mSubmitReportButton state] == NSOnState) { |
|
501 [self setStringFitVertically:mProgressText |
|
502 string:Str(ST_REPORTPRESUBMIT) |
|
503 resizeWindow:YES]; |
|
504 [mProgressText setHidden:NO]; |
|
505 // enable all the controls |
|
506 [self enableControls:YES]; |
|
507 } |
|
508 else { |
|
509 // not submitting, disable all the controls under |
|
510 // the submit checkbox, and hide the status text |
|
511 [mProgressText setHidden:YES]; |
|
512 [self enableControls:NO]; |
|
513 } |
|
514 } |
|
515 |
|
516 -(void)updateURL |
|
517 { |
|
518 if ([mIncludeURLButton state] == NSOnState && !gURLParameter.empty()) { |
|
519 gQueryParameters["URL"] = gURLParameter; |
|
520 } else { |
|
521 gQueryParameters.erase("URL"); |
|
522 } |
|
523 } |
|
524 |
|
525 -(void)updateEmail |
|
526 { |
|
527 if ([mEmailMeButton state] == NSOnState && |
|
528 [mSubmitReportButton state] == NSOnState) { |
|
529 NSString* email = [mEmailText stringValue]; |
|
530 gQueryParameters["Email"] = [email UTF8String]; |
|
531 [mEmailText setEnabled:YES]; |
|
532 } else { |
|
533 gQueryParameters.erase("Email"); |
|
534 [mEmailText setEnabled:NO]; |
|
535 } |
|
536 } |
|
537 |
|
538 -(void)sendReport |
|
539 { |
|
540 if (![self setupPost]) { |
|
541 LogMessage("Crash report submission failed: could not set up POST data"); |
|
542 [self setStringFitVertically:mProgressText |
|
543 string:Str(ST_SUBMITFAILED) |
|
544 resizeWindow:YES]; |
|
545 // quit after 5 seconds |
|
546 [self performSelector:@selector(closeMeDown:) withObject:nil |
|
547 afterDelay:5.0]; |
|
548 } |
|
549 |
|
550 [NSThread detachNewThreadSelector:@selector(uploadThread:) |
|
551 toTarget:self |
|
552 withObject:mPost]; |
|
553 } |
|
554 |
|
555 -(bool)setupPost |
|
556 { |
|
557 NSURL* url = [NSURL URLWithString:[NSSTR(gSendURL) stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; |
|
558 if (!url) return false; |
|
559 |
|
560 mPost = [[HTTPMultipartUpload alloc] initWithURL: url]; |
|
561 if (!mPost) return false; |
|
562 |
|
563 NSMutableDictionary* parameters = |
|
564 [[NSMutableDictionary alloc] initWithCapacity: gQueryParameters.size()]; |
|
565 if (!parameters) return false; |
|
566 |
|
567 StringTable::const_iterator end = gQueryParameters.end(); |
|
568 for (StringTable::const_iterator i = gQueryParameters.begin(); |
|
569 i != end; |
|
570 i++) { |
|
571 NSString* key = NSSTR(i->first); |
|
572 NSString* value = NSSTR(i->second); |
|
573 [parameters setObject: value forKey: key]; |
|
574 } |
|
575 |
|
576 [mPost addFileAtPath: NSSTR(gDumpFile) name: @"upload_file_minidump"]; |
|
577 [mPost setParameters: parameters]; |
|
578 [parameters release]; |
|
579 |
|
580 return true; |
|
581 } |
|
582 |
|
583 -(void)uploadComplete:(NSData*)data |
|
584 { |
|
585 NSHTTPURLResponse* response = [mPost response]; |
|
586 [mPost release]; |
|
587 |
|
588 bool success; |
|
589 string reply; |
|
590 if (!data || !response || [response statusCode] != 200) { |
|
591 success = false; |
|
592 reply = ""; |
|
593 |
|
594 // if data is nil, we probably logged an error in uploadThread |
|
595 if (data != nil && response != nil) { |
|
596 ostringstream message; |
|
597 message << "Crash report submission failed: server returned status " |
|
598 << [response statusCode]; |
|
599 LogMessage(message.str()); |
|
600 } |
|
601 } else { |
|
602 success = true; |
|
603 LogMessage("Crash report submitted successfully"); |
|
604 |
|
605 NSString* encodingName = [response textEncodingName]; |
|
606 NSStringEncoding encoding; |
|
607 if (encodingName) { |
|
608 encoding = CFStringConvertEncodingToNSStringEncoding( |
|
609 CFStringConvertIANACharSetNameToEncoding((CFStringRef)encodingName)); |
|
610 } else { |
|
611 encoding = NSISOLatin1StringEncoding; |
|
612 } |
|
613 NSString* r = [[NSString alloc] initWithData: data encoding: encoding]; |
|
614 reply = [r UTF8String]; |
|
615 [r release]; |
|
616 } |
|
617 |
|
618 SendCompleted(success, reply); |
|
619 |
|
620 [mProgressIndicator stopAnimation:self]; |
|
621 if (success) { |
|
622 [self setStringFitVertically:mProgressText |
|
623 string:Str(ST_REPORTSUBMITSUCCESS) |
|
624 resizeWindow:YES]; |
|
625 } else { |
|
626 [self setStringFitVertically:mProgressText |
|
627 string:Str(ST_SUBMITFAILED) |
|
628 resizeWindow:YES]; |
|
629 } |
|
630 // quit after 5 seconds |
|
631 [self performSelector:@selector(closeMeDown:) withObject:nil |
|
632 afterDelay:5.0]; |
|
633 } |
|
634 |
|
635 -(void)uploadThread:(HTTPMultipartUpload*)post |
|
636 { |
|
637 NSAutoreleasePool* autoreleasepool = [[NSAutoreleasePool alloc] init]; |
|
638 NSError* error = nil; |
|
639 NSData* data = [post send: &error]; |
|
640 if (error) { |
|
641 data = nil; |
|
642 NSString* errorDesc = [error localizedDescription]; |
|
643 string message = [errorDesc UTF8String]; |
|
644 LogMessage("Crash report submission failed: " + message); |
|
645 } |
|
646 |
|
647 [self performSelectorOnMainThread: @selector(uploadComplete:) |
|
648 withObject: data |
|
649 waitUntilDone: YES]; |
|
650 |
|
651 [autoreleasepool release]; |
|
652 } |
|
653 |
|
654 // to get auto-quit when we close the window |
|
655 -(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication |
|
656 { |
|
657 return YES; |
|
658 } |
|
659 |
|
660 -(void)applicationWillTerminate:(NSNotification *)aNotification |
|
661 { |
|
662 // since we use [NSApp terminate:] we never return to main, |
|
663 // so do our cleanup here |
|
664 if (!gDidTrySend) |
|
665 DeleteDump(); |
|
666 } |
|
667 |
|
668 @end |
|
669 |
|
670 @implementation TextViewWithPlaceHolder |
|
671 |
|
672 - (BOOL)becomeFirstResponder |
|
673 { |
|
674 [self setNeedsDisplay:YES]; |
|
675 return [super becomeFirstResponder]; |
|
676 } |
|
677 |
|
678 - (void)drawRect:(NSRect)rect |
|
679 { |
|
680 [super drawRect:rect]; |
|
681 if (mPlaceHolderString && [[self string] isEqualToString:@""] && |
|
682 self != [[self window] firstResponder]) |
|
683 [mPlaceHolderString drawInRect:[self frame]]; |
|
684 } |
|
685 |
|
686 - (BOOL)resignFirstResponder |
|
687 { |
|
688 [self setNeedsDisplay:YES]; |
|
689 return [super resignFirstResponder]; |
|
690 } |
|
691 |
|
692 - (void)setPlaceholder:(NSString*)placeholder |
|
693 { |
|
694 NSColor* txtColor = [NSColor disabledControlTextColor]; |
|
695 NSDictionary* txtDict = [NSDictionary |
|
696 dictionaryWithObjectsAndKeys:txtColor, |
|
697 NSForegroundColorAttributeName, nil]; |
|
698 mPlaceHolderString = [[NSMutableAttributedString alloc] |
|
699 initWithString:placeholder attributes:txtDict]; |
|
700 if (gRTLlayout) |
|
701 [mPlaceHolderString setAlignment:NSRightTextAlignment |
|
702 range:NSMakeRange(0, [placeholder length])]; |
|
703 |
|
704 } |
|
705 |
|
706 - (void)insertTab:(id)sender |
|
707 { |
|
708 // don't actually want to insert tabs, just tab to next control |
|
709 [[self window] selectNextKeyView:sender]; |
|
710 } |
|
711 |
|
712 - (void)insertBacktab:(id)sender |
|
713 { |
|
714 [[self window] selectPreviousKeyView:sender]; |
|
715 } |
|
716 |
|
717 - (void)setEnabled:(BOOL)enabled |
|
718 { |
|
719 [self setSelectable:enabled]; |
|
720 [self setEditable:enabled]; |
|
721 if (![[self string] isEqualToString:@""]) { |
|
722 NSAttributedString* colorString; |
|
723 NSColor* txtColor; |
|
724 if (enabled) |
|
725 txtColor = [NSColor textColor]; |
|
726 else |
|
727 txtColor = [NSColor disabledControlTextColor]; |
|
728 NSDictionary *txtDict = [NSDictionary |
|
729 dictionaryWithObjectsAndKeys:txtColor, |
|
730 NSForegroundColorAttributeName, nil]; |
|
731 colorString = [[NSAttributedString alloc] |
|
732 initWithString:[self string] |
|
733 attributes:txtDict]; |
|
734 [[self textStorage] setAttributedString: colorString]; |
|
735 [self setInsertionPointColor:txtColor]; |
|
736 [colorString release]; |
|
737 } |
|
738 } |
|
739 |
|
740 - (void)dealloc |
|
741 { |
|
742 [mPlaceHolderString release]; |
|
743 [super dealloc]; |
|
744 } |
|
745 |
|
746 @end |
|
747 |
|
748 /* === Crashreporter UI Functions === */ |
|
749 |
|
750 bool UIInit() |
|
751 { |
|
752 gMainPool = [[NSAutoreleasePool alloc] init]; |
|
753 [NSApplication sharedApplication]; |
|
754 |
|
755 if (gStrings.find("isRTL") != gStrings.end() && |
|
756 gStrings["isRTL"] == "yes") |
|
757 gRTLlayout = true; |
|
758 |
|
759 [NSBundle loadNibNamed:(gRTLlayout ? @"MainMenuRTL" : @"MainMenu") |
|
760 owner:NSApp]; |
|
761 |
|
762 return true; |
|
763 } |
|
764 |
|
765 void UIShutdown() |
|
766 { |
|
767 [gMainPool release]; |
|
768 } |
|
769 |
|
770 void UIShowDefaultUI() |
|
771 { |
|
772 [gUI showErrorUI: gStrings[ST_CRASHREPORTERDEFAULT]]; |
|
773 [NSApp run]; |
|
774 } |
|
775 |
|
776 bool UIShowCrashUI(const string& dumpfile, |
|
777 const StringTable& queryParameters, |
|
778 const string& sendURL, |
|
779 const vector<string>& restartArgs) |
|
780 { |
|
781 gRestartArgs = restartArgs; |
|
782 |
|
783 [gUI showCrashUI: dumpfile |
|
784 queryParameters: queryParameters |
|
785 sendURL: sendURL]; |
|
786 [NSApp run]; |
|
787 |
|
788 return gDidTrySend; |
|
789 } |
|
790 |
|
791 void UIError_impl(const string& message) |
|
792 { |
|
793 if (!gUI) { |
|
794 // UI failed to initialize, printing is the best we can do |
|
795 printf("Error: %s\n", message.c_str()); |
|
796 return; |
|
797 } |
|
798 |
|
799 [gUI showErrorUI: message]; |
|
800 [NSApp run]; |
|
801 } |
|
802 |
|
803 bool UIGetIniPath(string& path) |
|
804 { |
|
805 path = gArgv[0]; |
|
806 path.append(".ini"); |
|
807 |
|
808 return true; |
|
809 } |
|
810 |
|
811 bool UIGetSettingsPath(const string& vendor, |
|
812 const string& product, |
|
813 string& settingsPath) |
|
814 { |
|
815 FSRef foundRef; |
|
816 OSErr err = FSFindFolder(kUserDomain, kApplicationSupportFolderType, |
|
817 kCreateFolder, &foundRef); |
|
818 if (err != noErr) |
|
819 return false; |
|
820 |
|
821 unsigned char path[PATH_MAX]; |
|
822 FSRefMakePath(&foundRef, path, sizeof(path)); |
|
823 NSString* destPath = [NSString stringWithUTF8String:reinterpret_cast<char*>(path)]; |
|
824 |
|
825 // Note that MacOS ignores the vendor when creating the profile hierarchy - |
|
826 // all application preferences directories live alongside one another in |
|
827 // ~/Library/Application Support/ |
|
828 destPath = [destPath stringByAppendingPathComponent: NSSTR(product)]; |
|
829 // Thunderbird stores its profile in ~/Library/Thunderbird, |
|
830 // but we're going to put stuff in ~/Library/Application Support/Thunderbird |
|
831 // anyway, so we have to ensure that path exists. |
|
832 string tempPath = [destPath UTF8String]; |
|
833 if (!UIEnsurePathExists(tempPath)) |
|
834 return false; |
|
835 |
|
836 destPath = [destPath stringByAppendingPathComponent: @"Crash Reports"]; |
|
837 |
|
838 settingsPath = [destPath UTF8String]; |
|
839 |
|
840 return true; |
|
841 } |
|
842 |
|
843 bool UIEnsurePathExists(const string& path) |
|
844 { |
|
845 int ret = mkdir(path.c_str(), S_IRWXU); |
|
846 int e = errno; |
|
847 if (ret == -1 && e != EEXIST) |
|
848 return false; |
|
849 |
|
850 return true; |
|
851 } |
|
852 |
|
853 bool UIFileExists(const string& path) |
|
854 { |
|
855 struct stat sb; |
|
856 int ret = stat(path.c_str(), &sb); |
|
857 if (ret == -1 || !(sb.st_mode & S_IFREG)) |
|
858 return false; |
|
859 |
|
860 return true; |
|
861 } |
|
862 |
|
863 bool UIMoveFile(const string& file, const string& newfile) |
|
864 { |
|
865 if (!rename(file.c_str(), newfile.c_str())) |
|
866 return true; |
|
867 if (errno != EXDEV) |
|
868 return false; |
|
869 |
|
870 NSFileManager *fileManager = [NSFileManager defaultManager]; |
|
871 NSString *source = [fileManager stringWithFileSystemRepresentation:file.c_str() length:file.length()]; |
|
872 NSString *dest = [fileManager stringWithFileSystemRepresentation:newfile.c_str() length:newfile.length()]; |
|
873 if (!source || !dest) |
|
874 return false; |
|
875 |
|
876 [fileManager moveItemAtPath:source toPath:dest error:NULL]; |
|
877 return UIFileExists(newfile); |
|
878 } |
|
879 |
|
880 bool UIDeleteFile(const string& file) |
|
881 { |
|
882 return (unlink(file.c_str()) != -1); |
|
883 } |
|
884 |
|
885 std::ifstream* UIOpenRead(const string& filename) |
|
886 { |
|
887 return new std::ifstream(filename.c_str(), std::ios::in); |
|
888 } |
|
889 |
|
890 std::ofstream* UIOpenWrite(const string& filename, bool append) // append=false |
|
891 { |
|
892 return new std::ofstream(filename.c_str(), |
|
893 append ? std::ios::out | std::ios::app |
|
894 : std::ios::out); |
|
895 } |