1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/crashreporter/client/crashreporter_osx.mm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,895 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#import <Cocoa/Cocoa.h> 1.10 +#import <CoreFoundation/CoreFoundation.h> 1.11 +#include "crashreporter.h" 1.12 +#include "crashreporter_osx.h" 1.13 +#include <crt_externs.h> 1.14 +#include <spawn.h> 1.15 +#include <sys/stat.h> 1.16 +#include <sys/types.h> 1.17 +#include <fcntl.h> 1.18 +#include <sstream> 1.19 + 1.20 +using std::string; 1.21 +using std::vector; 1.22 +using std::ostringstream; 1.23 + 1.24 +using namespace CrashReporter; 1.25 + 1.26 +static NSAutoreleasePool* gMainPool; 1.27 +static CrashReporterUI* gUI = 0; 1.28 +static string gDumpFile; 1.29 +static StringTable gQueryParameters; 1.30 +static string gURLParameter; 1.31 +static string gSendURL; 1.32 +static vector<string> gRestartArgs; 1.33 +static bool gDidTrySend = false; 1.34 +static bool gRTLlayout = false; 1.35 + 1.36 +static cpu_type_t pref_cpu_types[2] = { 1.37 +#if defined(__i386__) 1.38 + CPU_TYPE_X86, 1.39 +#elif defined(__x86_64__) 1.40 + CPU_TYPE_X86_64, 1.41 +#elif defined(__ppc__) 1.42 + CPU_TYPE_POWERPC, 1.43 +#endif 1.44 + CPU_TYPE_ANY }; 1.45 + 1.46 +#define NSSTR(s) [NSString stringWithUTF8String:(s).c_str()] 1.47 + 1.48 +static NSString* Str(const char* aName) 1.49 +{ 1.50 + string str = gStrings[aName]; 1.51 + if (str.empty()) str = "?"; 1.52 + return NSSTR(str); 1.53 +} 1.54 + 1.55 +static bool RestartApplication() 1.56 +{ 1.57 + vector<char*> argv(gRestartArgs.size() + 1); 1.58 + 1.59 + posix_spawnattr_t spawnattr; 1.60 + if (posix_spawnattr_init(&spawnattr) != 0) { 1.61 + return false; 1.62 + } 1.63 + 1.64 + // Set spawn attributes. 1.65 + size_t attr_count = sizeof(pref_cpu_types) / sizeof(pref_cpu_types[0]); 1.66 + size_t attr_ocount = 0; 1.67 + if (posix_spawnattr_setbinpref_np(&spawnattr, 1.68 + attr_count, 1.69 + pref_cpu_types, 1.70 + &attr_ocount) != 0 || 1.71 + attr_ocount != attr_count) { 1.72 + posix_spawnattr_destroy(&spawnattr); 1.73 + return false; 1.74 + } 1.75 + 1.76 + unsigned int i; 1.77 + for (i = 0; i < gRestartArgs.size(); i++) { 1.78 + argv[i] = (char*)gRestartArgs[i].c_str(); 1.79 + } 1.80 + argv[i] = 0; 1.81 + 1.82 + char **env = NULL; 1.83 + char ***nsEnv = _NSGetEnviron(); 1.84 + if (nsEnv) 1.85 + env = *nsEnv; 1.86 + int result = posix_spawnp(NULL, 1.87 + argv[0], 1.88 + NULL, 1.89 + &spawnattr, 1.90 + &argv[0], 1.91 + env); 1.92 + 1.93 + posix_spawnattr_destroy(&spawnattr); 1.94 + 1.95 + return result == 0; 1.96 +} 1.97 + 1.98 +@implementation CrashReporterUI 1.99 + 1.100 +-(void)awakeFromNib 1.101 +{ 1.102 + gUI = self; 1.103 + [mWindow center]; 1.104 + 1.105 + [mWindow setTitle:[[NSBundle mainBundle] 1.106 + objectForInfoDictionaryKey:@"CFBundleName"]]; 1.107 +} 1.108 + 1.109 +-(void)showCrashUI:(const string&)dumpfile 1.110 + queryParameters:(const StringTable&)queryParameters 1.111 + sendURL:(const string&)sendURL 1.112 +{ 1.113 + gDumpFile = dumpfile; 1.114 + gQueryParameters = queryParameters; 1.115 + gSendURL = sendURL; 1.116 + 1.117 + [mWindow setTitle:Str(ST_CRASHREPORTERTITLE)]; 1.118 + [mHeaderLabel setStringValue:Str(ST_CRASHREPORTERHEADER)]; 1.119 + 1.120 + NSRect viewReportFrame = [mViewReportButton frame]; 1.121 + [mViewReportButton setTitle:Str(ST_VIEWREPORT)]; 1.122 + [mViewReportButton sizeToFit]; 1.123 + if (gRTLlayout) { 1.124 + // sizeToFit will keep the left side fixed, so realign 1.125 + float oldWidth = viewReportFrame.size.width; 1.126 + viewReportFrame = [mViewReportButton frame]; 1.127 + viewReportFrame.origin.x += oldWidth - viewReportFrame.size.width; 1.128 + [mViewReportButton setFrame: viewReportFrame]; 1.129 + } 1.130 + 1.131 + [mSubmitReportButton setTitle:Str(ST_CHECKSUBMIT)]; 1.132 + [mIncludeURLButton setTitle:Str(ST_CHECKURL)]; 1.133 + [mEmailMeButton setTitle:Str(ST_CHECKEMAIL)]; 1.134 + [mViewReportOkButton setTitle:Str(ST_OK)]; 1.135 + 1.136 + [mCommentText setPlaceholder:Str(ST_COMMENTGRAYTEXT)]; 1.137 + if (gRTLlayout) 1.138 + [mCommentText toggleBaseWritingDirection:self]; 1.139 + [[mEmailText cell] setPlaceholderString:Str(ST_EMAILGRAYTEXT)]; 1.140 + 1.141 + if (gQueryParameters.find("URL") != gQueryParameters.end()) { 1.142 + // save the URL value in case the checkbox gets unchecked 1.143 + gURLParameter = gQueryParameters["URL"]; 1.144 + } 1.145 + else { 1.146 + // no URL specified, hide checkbox 1.147 + [mIncludeURLButton removeFromSuperview]; 1.148 + // shrink window to fit 1.149 + NSRect frame = [mWindow frame]; 1.150 + NSRect includeURLFrame = [mIncludeURLButton frame]; 1.151 + NSRect emailFrame = [mEmailMeButton frame]; 1.152 + int buttonMask = [mViewReportButton autoresizingMask]; 1.153 + int checkMask = [mSubmitReportButton autoresizingMask]; 1.154 + int commentScrollMask = [mCommentScrollView autoresizingMask]; 1.155 + 1.156 + [mViewReportButton setAutoresizingMask:NSViewMinYMargin]; 1.157 + [mSubmitReportButton setAutoresizingMask:NSViewMinYMargin]; 1.158 + [mCommentScrollView setAutoresizingMask:NSViewMinYMargin]; 1.159 + 1.160 + // remove all the space in between 1.161 + frame.size.height -= includeURLFrame.origin.y - emailFrame.origin.y; 1.162 + [mWindow setFrame:frame display: true animate:NO]; 1.163 + 1.164 + [mViewReportButton setAutoresizingMask:buttonMask]; 1.165 + [mSubmitReportButton setAutoresizingMask:checkMask]; 1.166 + [mCommentScrollView setAutoresizingMask:commentScrollMask]; 1.167 + } 1.168 + 1.169 + // resize some buttons horizontally and possibly some controls vertically 1.170 + [self doInitialResizing]; 1.171 + 1.172 + // load default state of submit checkbox 1.173 + // we don't just do this via IB because we want the default to be 1.174 + // off a certain percentage of the time 1.175 + BOOL submitChecked = NO; 1.176 + NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; 1.177 + if (nil != [userDefaults objectForKey:@"submitReport"]) { 1.178 + submitChecked = [userDefaults boolForKey:@"submitReport"]; 1.179 + } 1.180 + else { 1.181 + // use compile-time specified enable percentage 1.182 + submitChecked = ShouldEnableSending(); 1.183 + [userDefaults setBool:submitChecked forKey:@"submitReport"]; 1.184 + } 1.185 + [mSubmitReportButton setState:(submitChecked ? NSOnState : NSOffState)]; 1.186 + 1.187 + [self updateSubmit]; 1.188 + [self updateURL]; 1.189 + [self updateEmail]; 1.190 + 1.191 + [mWindow makeKeyAndOrderFront:nil]; 1.192 +} 1.193 + 1.194 +-(void)showErrorUI:(const string&)message 1.195 +{ 1.196 + [self setView: mErrorView animate: NO]; 1.197 + 1.198 + [mErrorHeaderLabel setStringValue:Str(ST_CRASHREPORTERHEADER)]; 1.199 + [self setStringFitVertically:mErrorLabel 1.200 + string:NSSTR(message) 1.201 + resizeWindow:YES]; 1.202 + [mErrorCloseButton setTitle:Str(ST_OK)]; 1.203 + 1.204 + [mErrorCloseButton setKeyEquivalent:@"\r"]; 1.205 + [mWindow makeFirstResponder:mErrorCloseButton]; 1.206 + [mWindow makeKeyAndOrderFront:nil]; 1.207 +} 1.208 + 1.209 +-(void)showReportInfo 1.210 +{ 1.211 + NSDictionary* boldAttr = [NSDictionary 1.212 + dictionaryWithObject: 1.213 + [NSFont boldSystemFontOfSize: 1.214 + [NSFont smallSystemFontSize]] 1.215 + forKey:NSFontAttributeName]; 1.216 + NSDictionary* normalAttr = [NSDictionary 1.217 + dictionaryWithObject: 1.218 + [NSFont systemFontOfSize: 1.219 + [NSFont smallSystemFontSize]] 1.220 + forKey:NSFontAttributeName]; 1.221 + 1.222 + [mViewReportTextView setString:@""]; 1.223 + for (StringTable::iterator iter = gQueryParameters.begin(); 1.224 + iter != gQueryParameters.end(); 1.225 + iter++) { 1.226 + NSAttributedString* key = [[NSAttributedString alloc] 1.227 + initWithString:NSSTR(iter->first + ": ") 1.228 + attributes:boldAttr]; 1.229 + NSAttributedString* value = [[NSAttributedString alloc] 1.230 + initWithString:NSSTR(iter->second + "\n") 1.231 + attributes:normalAttr]; 1.232 + [[mViewReportTextView textStorage] appendAttributedString: key]; 1.233 + [[mViewReportTextView textStorage] appendAttributedString: value]; 1.234 + [key release]; 1.235 + [value release]; 1.236 + } 1.237 + 1.238 + NSAttributedString* extra = [[NSAttributedString alloc] 1.239 + initWithString:NSSTR("\n" + gStrings[ST_EXTRAREPORTINFO]) 1.240 + attributes:normalAttr]; 1.241 + [[mViewReportTextView textStorage] appendAttributedString: extra]; 1.242 + [extra release]; 1.243 +} 1.244 + 1.245 +- (void)maybeSubmitReport 1.246 +{ 1.247 + if ([mSubmitReportButton state] == NSOnState) { 1.248 + [self setStringFitVertically:mProgressText 1.249 + string:Str(ST_REPORTDURINGSUBMIT) 1.250 + resizeWindow:YES]; 1.251 + // disable all the controls 1.252 + [self enableControls:NO]; 1.253 + [mSubmitReportButton setEnabled:NO]; 1.254 + [mRestartButton setEnabled:NO]; 1.255 + [mCloseButton setEnabled:NO]; 1.256 + [mProgressIndicator startAnimation:self]; 1.257 + gDidTrySend = true; 1.258 + [self sendReport]; 1.259 + } else { 1.260 + [NSApp terminate:self]; 1.261 + } 1.262 +} 1.263 + 1.264 +- (void)closeMeDown:(id)unused 1.265 +{ 1.266 + [NSApp terminate:self]; 1.267 +} 1.268 + 1.269 +-(IBAction)submitReportClicked:(id)sender 1.270 +{ 1.271 + [self updateSubmit]; 1.272 + NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; 1.273 + [userDefaults setBool:([mSubmitReportButton state] == NSOnState) 1.274 + forKey:@"submitReport"]; 1.275 + [userDefaults synchronize]; 1.276 +} 1.277 + 1.278 +-(IBAction)viewReportClicked:(id)sender 1.279 +{ 1.280 + [self showReportInfo]; 1.281 + [NSApp beginSheet:mViewReportWindow modalForWindow:mWindow 1.282 + modalDelegate:nil didEndSelector:nil contextInfo:nil]; 1.283 +} 1.284 + 1.285 +- (IBAction)viewReportOkClicked:(id)sender; 1.286 +{ 1.287 + [mViewReportWindow orderOut:nil]; 1.288 + [NSApp endSheet:mViewReportWindow]; 1.289 +} 1.290 + 1.291 +-(IBAction)closeClicked:(id)sender 1.292 +{ 1.293 + [self maybeSubmitReport]; 1.294 +} 1.295 + 1.296 +-(IBAction)restartClicked:(id)sender 1.297 +{ 1.298 + RestartApplication(); 1.299 + [self maybeSubmitReport]; 1.300 +} 1.301 + 1.302 +- (IBAction)includeURLClicked:(id)sender 1.303 +{ 1.304 + [self updateURL]; 1.305 +} 1.306 + 1.307 +-(IBAction)emailMeClicked:(id)sender 1.308 +{ 1.309 + [self updateEmail]; 1.310 +} 1.311 + 1.312 +-(void)controlTextDidChange:(NSNotification *)note 1.313 +{ 1.314 + [self updateEmail]; 1.315 +} 1.316 + 1.317 +- (void)textDidChange:(NSNotification *)aNotification 1.318 +{ 1.319 + // update comment parameter 1.320 + if ([[[mCommentText textStorage] mutableString] length] > 0) 1.321 + gQueryParameters["Comments"] = [[[mCommentText textStorage] mutableString] 1.322 + UTF8String]; 1.323 + else 1.324 + gQueryParameters.erase("Comments"); 1.325 +} 1.326 + 1.327 +// Limit the comment field to 500 bytes in UTF-8 1.328 +- (BOOL)textView:(NSTextView *)aTextView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString 1.329 +{ 1.330 + // current string length + replacement text length - replaced range length 1.331 + if (([[aTextView string] 1.332 + lengthOfBytesUsingEncoding:NSUTF8StringEncoding] 1.333 + + [replacementString lengthOfBytesUsingEncoding:NSUTF8StringEncoding] 1.334 + - [[[aTextView string] substringWithRange:affectedCharRange] 1.335 + lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) 1.336 + > MAX_COMMENT_LENGTH) { 1.337 + return NO; 1.338 + } 1.339 + return YES; 1.340 +} 1.341 + 1.342 +- (void)doInitialResizing 1.343 +{ 1.344 + NSRect windowFrame = [mWindow frame]; 1.345 + NSRect restartFrame = [mRestartButton frame]; 1.346 + NSRect closeFrame = [mCloseButton frame]; 1.347 + // resize close button to fit text 1.348 + float oldCloseWidth = closeFrame.size.width; 1.349 + [mCloseButton setTitle:Str(ST_QUIT)]; 1.350 + [mCloseButton sizeToFit]; 1.351 + closeFrame = [mCloseButton frame]; 1.352 + // move close button left if it grew 1.353 + if (!gRTLlayout) { 1.354 + closeFrame.origin.x -= closeFrame.size.width - oldCloseWidth; 1.355 + } 1.356 + 1.357 + if (gRestartArgs.size() == 0) { 1.358 + [mRestartButton removeFromSuperview]; 1.359 + if (!gRTLlayout) { 1.360 + closeFrame.origin.x = restartFrame.origin.x + 1.361 + (restartFrame.size.width - closeFrame.size.width); 1.362 + } 1.363 + else { 1.364 + closeFrame.origin.x = restartFrame.origin.x; 1.365 + } 1.366 + [mCloseButton setFrame: closeFrame]; 1.367 + [mCloseButton setKeyEquivalent:@"\r"]; 1.368 + } else { 1.369 + [mRestartButton setTitle:Str(ST_RESTART)]; 1.370 + // resize "restart" button 1.371 + float oldRestartWidth = restartFrame.size.width; 1.372 + [mRestartButton sizeToFit]; 1.373 + restartFrame = [mRestartButton frame]; 1.374 + if (!gRTLlayout) { 1.375 + // move left by the amount that the button grew 1.376 + restartFrame.origin.x -= restartFrame.size.width - oldRestartWidth; 1.377 + closeFrame.origin.x -= restartFrame.size.width - oldRestartWidth; 1.378 + } 1.379 + else { 1.380 + // shift the close button right in RTL 1.381 + closeFrame.origin.x += restartFrame.size.width - oldRestartWidth; 1.382 + } 1.383 + [mRestartButton setFrame: restartFrame]; 1.384 + [mCloseButton setFrame: closeFrame]; 1.385 + // possibly resize window if both buttons no longer fit 1.386 + // leave 20 px from either side of the window, and 12 px 1.387 + // between the buttons 1.388 + float neededWidth = closeFrame.size.width + restartFrame.size.width + 1.389 + 2*20 + 12; 1.390 + 1.391 + if (neededWidth > windowFrame.size.width) { 1.392 + windowFrame.size.width = neededWidth; 1.393 + [mWindow setFrame:windowFrame display: true animate: NO]; 1.394 + } 1.395 + [mRestartButton setKeyEquivalent:@"\r"]; 1.396 + } 1.397 + 1.398 + NSButton *checkboxes[] = { 1.399 + mSubmitReportButton, 1.400 + mIncludeURLButton, 1.401 + mEmailMeButton 1.402 + }; 1.403 + 1.404 + for (int i=0; i<3; i++) { 1.405 + NSRect frame = [checkboxes[i] frame]; 1.406 + [checkboxes[i] sizeToFit]; 1.407 + if (gRTLlayout) { 1.408 + // sizeToFit will keep the left side fixed, so realign 1.409 + float oldWidth = frame.size.width; 1.410 + frame = [checkboxes[i] frame]; 1.411 + frame.origin.x += oldWidth - frame.size.width; 1.412 + [checkboxes[i] setFrame: frame]; 1.413 + } 1.414 + // keep existing spacing on left side, + 20 px spare on right 1.415 + float neededWidth = frame.origin.x + frame.size.width + 20; 1.416 + if (neededWidth > windowFrame.size.width) { 1.417 + windowFrame.size.width = neededWidth; 1.418 + [mWindow setFrame:windowFrame display: true animate: NO]; 1.419 + } 1.420 + } 1.421 + 1.422 + // do this down here because we may have made the window wider 1.423 + // up above 1.424 + [self setStringFitVertically:mDescriptionLabel 1.425 + string:Str(ST_CRASHREPORTERDESCRIPTION) 1.426 + resizeWindow:YES]; 1.427 + 1.428 + // now pin all the controls (except quit/submit) in place, 1.429 + // if we lengthen the window after this, it's just to lengthen 1.430 + // the progress text, so nothing above that text should move. 1.431 + NSView* views[] = { 1.432 + mSubmitReportButton, 1.433 + mViewReportButton, 1.434 + mCommentScrollView, 1.435 + mIncludeURLButton, 1.436 + mEmailMeButton, 1.437 + mEmailText, 1.438 + mProgressIndicator, 1.439 + mProgressText 1.440 + }; 1.441 + for (unsigned int i=0; i<sizeof(views)/sizeof(views[0]); i++) { 1.442 + [views[i] setAutoresizingMask:NSViewMinYMargin]; 1.443 + } 1.444 +} 1.445 + 1.446 +-(float)setStringFitVertically:(NSControl*)control 1.447 + string:(NSString*)str 1.448 + resizeWindow:(BOOL)resizeWindow 1.449 +{ 1.450 + // hack to make the text field grow vertically 1.451 + NSRect frame = [control frame]; 1.452 + float oldHeight = frame.size.height; 1.453 + 1.454 + frame.size.height = 10000; 1.455 + NSSize oldCellSize = [[control cell] cellSizeForBounds: frame]; 1.456 + [control setStringValue: str]; 1.457 + NSSize newCellSize = [[control cell] cellSizeForBounds: frame]; 1.458 + 1.459 + float delta = newCellSize.height - oldCellSize.height; 1.460 + frame.origin.y -= delta; 1.461 + frame.size.height = oldHeight + delta; 1.462 + [control setFrame: frame]; 1.463 + 1.464 + if (resizeWindow) { 1.465 + NSRect frame = [mWindow frame]; 1.466 + frame.origin.y -= delta; 1.467 + frame.size.height += delta; 1.468 + [mWindow setFrame:frame display: true animate: NO]; 1.469 + } 1.470 + 1.471 + return delta; 1.472 +} 1.473 + 1.474 +-(void)setView: (NSView*)v animate: (BOOL)animate 1.475 +{ 1.476 + NSRect frame = [mWindow frame]; 1.477 + 1.478 + NSRect oldViewFrame = [[mWindow contentView] frame]; 1.479 + NSRect newViewFrame = [v frame]; 1.480 + 1.481 + frame.origin.y += oldViewFrame.size.height - newViewFrame.size.height; 1.482 + frame.size.height -= oldViewFrame.size.height - newViewFrame.size.height; 1.483 + 1.484 + frame.origin.x += oldViewFrame.size.width - newViewFrame.size.width; 1.485 + frame.size.width -= oldViewFrame.size.width - newViewFrame.size.width; 1.486 + 1.487 + [mWindow setContentView:v]; 1.488 + [mWindow setFrame:frame display:true animate:animate]; 1.489 +} 1.490 + 1.491 +- (void)enableControls:(BOOL)enabled 1.492 +{ 1.493 + [mViewReportButton setEnabled:enabled]; 1.494 + [mIncludeURLButton setEnabled:enabled]; 1.495 + [mEmailMeButton setEnabled:enabled]; 1.496 + [mCommentText setEnabled:enabled]; 1.497 + [mCommentScrollView setHasVerticalScroller:enabled]; 1.498 + [self updateEmail]; 1.499 +} 1.500 + 1.501 +-(void)updateSubmit 1.502 +{ 1.503 + if ([mSubmitReportButton state] == NSOnState) { 1.504 + [self setStringFitVertically:mProgressText 1.505 + string:Str(ST_REPORTPRESUBMIT) 1.506 + resizeWindow:YES]; 1.507 + [mProgressText setHidden:NO]; 1.508 + // enable all the controls 1.509 + [self enableControls:YES]; 1.510 + } 1.511 + else { 1.512 + // not submitting, disable all the controls under 1.513 + // the submit checkbox, and hide the status text 1.514 + [mProgressText setHidden:YES]; 1.515 + [self enableControls:NO]; 1.516 + } 1.517 +} 1.518 + 1.519 +-(void)updateURL 1.520 +{ 1.521 + if ([mIncludeURLButton state] == NSOnState && !gURLParameter.empty()) { 1.522 + gQueryParameters["URL"] = gURLParameter; 1.523 + } else { 1.524 + gQueryParameters.erase("URL"); 1.525 + } 1.526 +} 1.527 + 1.528 +-(void)updateEmail 1.529 +{ 1.530 + if ([mEmailMeButton state] == NSOnState && 1.531 + [mSubmitReportButton state] == NSOnState) { 1.532 + NSString* email = [mEmailText stringValue]; 1.533 + gQueryParameters["Email"] = [email UTF8String]; 1.534 + [mEmailText setEnabled:YES]; 1.535 + } else { 1.536 + gQueryParameters.erase("Email"); 1.537 + [mEmailText setEnabled:NO]; 1.538 + } 1.539 +} 1.540 + 1.541 +-(void)sendReport 1.542 +{ 1.543 + if (![self setupPost]) { 1.544 + LogMessage("Crash report submission failed: could not set up POST data"); 1.545 + [self setStringFitVertically:mProgressText 1.546 + string:Str(ST_SUBMITFAILED) 1.547 + resizeWindow:YES]; 1.548 + // quit after 5 seconds 1.549 + [self performSelector:@selector(closeMeDown:) withObject:nil 1.550 + afterDelay:5.0]; 1.551 + } 1.552 + 1.553 + [NSThread detachNewThreadSelector:@selector(uploadThread:) 1.554 + toTarget:self 1.555 + withObject:mPost]; 1.556 +} 1.557 + 1.558 +-(bool)setupPost 1.559 +{ 1.560 + NSURL* url = [NSURL URLWithString:[NSSTR(gSendURL) stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 1.561 + if (!url) return false; 1.562 + 1.563 + mPost = [[HTTPMultipartUpload alloc] initWithURL: url]; 1.564 + if (!mPost) return false; 1.565 + 1.566 + NSMutableDictionary* parameters = 1.567 + [[NSMutableDictionary alloc] initWithCapacity: gQueryParameters.size()]; 1.568 + if (!parameters) return false; 1.569 + 1.570 + StringTable::const_iterator end = gQueryParameters.end(); 1.571 + for (StringTable::const_iterator i = gQueryParameters.begin(); 1.572 + i != end; 1.573 + i++) { 1.574 + NSString* key = NSSTR(i->first); 1.575 + NSString* value = NSSTR(i->second); 1.576 + [parameters setObject: value forKey: key]; 1.577 + } 1.578 + 1.579 + [mPost addFileAtPath: NSSTR(gDumpFile) name: @"upload_file_minidump"]; 1.580 + [mPost setParameters: parameters]; 1.581 + [parameters release]; 1.582 + 1.583 + return true; 1.584 +} 1.585 + 1.586 +-(void)uploadComplete:(NSData*)data 1.587 +{ 1.588 + NSHTTPURLResponse* response = [mPost response]; 1.589 + [mPost release]; 1.590 + 1.591 + bool success; 1.592 + string reply; 1.593 + if (!data || !response || [response statusCode] != 200) { 1.594 + success = false; 1.595 + reply = ""; 1.596 + 1.597 + // if data is nil, we probably logged an error in uploadThread 1.598 + if (data != nil && response != nil) { 1.599 + ostringstream message; 1.600 + message << "Crash report submission failed: server returned status " 1.601 + << [response statusCode]; 1.602 + LogMessage(message.str()); 1.603 + } 1.604 + } else { 1.605 + success = true; 1.606 + LogMessage("Crash report submitted successfully"); 1.607 + 1.608 + NSString* encodingName = [response textEncodingName]; 1.609 + NSStringEncoding encoding; 1.610 + if (encodingName) { 1.611 + encoding = CFStringConvertEncodingToNSStringEncoding( 1.612 + CFStringConvertIANACharSetNameToEncoding((CFStringRef)encodingName)); 1.613 + } else { 1.614 + encoding = NSISOLatin1StringEncoding; 1.615 + } 1.616 + NSString* r = [[NSString alloc] initWithData: data encoding: encoding]; 1.617 + reply = [r UTF8String]; 1.618 + [r release]; 1.619 + } 1.620 + 1.621 + SendCompleted(success, reply); 1.622 + 1.623 + [mProgressIndicator stopAnimation:self]; 1.624 + if (success) { 1.625 + [self setStringFitVertically:mProgressText 1.626 + string:Str(ST_REPORTSUBMITSUCCESS) 1.627 + resizeWindow:YES]; 1.628 + } else { 1.629 + [self setStringFitVertically:mProgressText 1.630 + string:Str(ST_SUBMITFAILED) 1.631 + resizeWindow:YES]; 1.632 + } 1.633 + // quit after 5 seconds 1.634 + [self performSelector:@selector(closeMeDown:) withObject:nil 1.635 + afterDelay:5.0]; 1.636 +} 1.637 + 1.638 +-(void)uploadThread:(HTTPMultipartUpload*)post 1.639 +{ 1.640 + NSAutoreleasePool* autoreleasepool = [[NSAutoreleasePool alloc] init]; 1.641 + NSError* error = nil; 1.642 + NSData* data = [post send: &error]; 1.643 + if (error) { 1.644 + data = nil; 1.645 + NSString* errorDesc = [error localizedDescription]; 1.646 + string message = [errorDesc UTF8String]; 1.647 + LogMessage("Crash report submission failed: " + message); 1.648 + } 1.649 + 1.650 + [self performSelectorOnMainThread: @selector(uploadComplete:) 1.651 + withObject: data 1.652 + waitUntilDone: YES]; 1.653 + 1.654 + [autoreleasepool release]; 1.655 +} 1.656 + 1.657 +// to get auto-quit when we close the window 1.658 +-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication 1.659 +{ 1.660 + return YES; 1.661 +} 1.662 + 1.663 +-(void)applicationWillTerminate:(NSNotification *)aNotification 1.664 +{ 1.665 + // since we use [NSApp terminate:] we never return to main, 1.666 + // so do our cleanup here 1.667 + if (!gDidTrySend) 1.668 + DeleteDump(); 1.669 +} 1.670 + 1.671 +@end 1.672 + 1.673 +@implementation TextViewWithPlaceHolder 1.674 + 1.675 +- (BOOL)becomeFirstResponder 1.676 +{ 1.677 + [self setNeedsDisplay:YES]; 1.678 + return [super becomeFirstResponder]; 1.679 +} 1.680 + 1.681 +- (void)drawRect:(NSRect)rect 1.682 +{ 1.683 + [super drawRect:rect]; 1.684 + if (mPlaceHolderString && [[self string] isEqualToString:@""] && 1.685 + self != [[self window] firstResponder]) 1.686 + [mPlaceHolderString drawInRect:[self frame]]; 1.687 +} 1.688 + 1.689 +- (BOOL)resignFirstResponder 1.690 +{ 1.691 + [self setNeedsDisplay:YES]; 1.692 + return [super resignFirstResponder]; 1.693 +} 1.694 + 1.695 +- (void)setPlaceholder:(NSString*)placeholder 1.696 +{ 1.697 + NSColor* txtColor = [NSColor disabledControlTextColor]; 1.698 + NSDictionary* txtDict = [NSDictionary 1.699 + dictionaryWithObjectsAndKeys:txtColor, 1.700 + NSForegroundColorAttributeName, nil]; 1.701 + mPlaceHolderString = [[NSMutableAttributedString alloc] 1.702 + initWithString:placeholder attributes:txtDict]; 1.703 + if (gRTLlayout) 1.704 + [mPlaceHolderString setAlignment:NSRightTextAlignment 1.705 + range:NSMakeRange(0, [placeholder length])]; 1.706 + 1.707 +} 1.708 + 1.709 +- (void)insertTab:(id)sender 1.710 +{ 1.711 + // don't actually want to insert tabs, just tab to next control 1.712 + [[self window] selectNextKeyView:sender]; 1.713 +} 1.714 + 1.715 +- (void)insertBacktab:(id)sender 1.716 +{ 1.717 + [[self window] selectPreviousKeyView:sender]; 1.718 +} 1.719 + 1.720 +- (void)setEnabled:(BOOL)enabled 1.721 +{ 1.722 + [self setSelectable:enabled]; 1.723 + [self setEditable:enabled]; 1.724 + if (![[self string] isEqualToString:@""]) { 1.725 + NSAttributedString* colorString; 1.726 + NSColor* txtColor; 1.727 + if (enabled) 1.728 + txtColor = [NSColor textColor]; 1.729 + else 1.730 + txtColor = [NSColor disabledControlTextColor]; 1.731 + NSDictionary *txtDict = [NSDictionary 1.732 + dictionaryWithObjectsAndKeys:txtColor, 1.733 + NSForegroundColorAttributeName, nil]; 1.734 + colorString = [[NSAttributedString alloc] 1.735 + initWithString:[self string] 1.736 + attributes:txtDict]; 1.737 + [[self textStorage] setAttributedString: colorString]; 1.738 + [self setInsertionPointColor:txtColor]; 1.739 + [colorString release]; 1.740 + } 1.741 +} 1.742 + 1.743 +- (void)dealloc 1.744 +{ 1.745 + [mPlaceHolderString release]; 1.746 + [super dealloc]; 1.747 +} 1.748 + 1.749 +@end 1.750 + 1.751 +/* === Crashreporter UI Functions === */ 1.752 + 1.753 +bool UIInit() 1.754 +{ 1.755 + gMainPool = [[NSAutoreleasePool alloc] init]; 1.756 + [NSApplication sharedApplication]; 1.757 + 1.758 + if (gStrings.find("isRTL") != gStrings.end() && 1.759 + gStrings["isRTL"] == "yes") 1.760 + gRTLlayout = true; 1.761 + 1.762 + [NSBundle loadNibNamed:(gRTLlayout ? @"MainMenuRTL" : @"MainMenu") 1.763 + owner:NSApp]; 1.764 + 1.765 + return true; 1.766 +} 1.767 + 1.768 +void UIShutdown() 1.769 +{ 1.770 + [gMainPool release]; 1.771 +} 1.772 + 1.773 +void UIShowDefaultUI() 1.774 +{ 1.775 + [gUI showErrorUI: gStrings[ST_CRASHREPORTERDEFAULT]]; 1.776 + [NSApp run]; 1.777 +} 1.778 + 1.779 +bool UIShowCrashUI(const string& dumpfile, 1.780 + const StringTable& queryParameters, 1.781 + const string& sendURL, 1.782 + const vector<string>& restartArgs) 1.783 +{ 1.784 + gRestartArgs = restartArgs; 1.785 + 1.786 + [gUI showCrashUI: dumpfile 1.787 + queryParameters: queryParameters 1.788 + sendURL: sendURL]; 1.789 + [NSApp run]; 1.790 + 1.791 + return gDidTrySend; 1.792 +} 1.793 + 1.794 +void UIError_impl(const string& message) 1.795 +{ 1.796 + if (!gUI) { 1.797 + // UI failed to initialize, printing is the best we can do 1.798 + printf("Error: %s\n", message.c_str()); 1.799 + return; 1.800 + } 1.801 + 1.802 + [gUI showErrorUI: message]; 1.803 + [NSApp run]; 1.804 +} 1.805 + 1.806 +bool UIGetIniPath(string& path) 1.807 +{ 1.808 + path = gArgv[0]; 1.809 + path.append(".ini"); 1.810 + 1.811 + return true; 1.812 +} 1.813 + 1.814 +bool UIGetSettingsPath(const string& vendor, 1.815 + const string& product, 1.816 + string& settingsPath) 1.817 +{ 1.818 + FSRef foundRef; 1.819 + OSErr err = FSFindFolder(kUserDomain, kApplicationSupportFolderType, 1.820 + kCreateFolder, &foundRef); 1.821 + if (err != noErr) 1.822 + return false; 1.823 + 1.824 + unsigned char path[PATH_MAX]; 1.825 + FSRefMakePath(&foundRef, path, sizeof(path)); 1.826 + NSString* destPath = [NSString stringWithUTF8String:reinterpret_cast<char*>(path)]; 1.827 + 1.828 + // Note that MacOS ignores the vendor when creating the profile hierarchy - 1.829 + // all application preferences directories live alongside one another in 1.830 + // ~/Library/Application Support/ 1.831 + destPath = [destPath stringByAppendingPathComponent: NSSTR(product)]; 1.832 + // Thunderbird stores its profile in ~/Library/Thunderbird, 1.833 + // but we're going to put stuff in ~/Library/Application Support/Thunderbird 1.834 + // anyway, so we have to ensure that path exists. 1.835 + string tempPath = [destPath UTF8String]; 1.836 + if (!UIEnsurePathExists(tempPath)) 1.837 + return false; 1.838 + 1.839 + destPath = [destPath stringByAppendingPathComponent: @"Crash Reports"]; 1.840 + 1.841 + settingsPath = [destPath UTF8String]; 1.842 + 1.843 + return true; 1.844 +} 1.845 + 1.846 +bool UIEnsurePathExists(const string& path) 1.847 +{ 1.848 + int ret = mkdir(path.c_str(), S_IRWXU); 1.849 + int e = errno; 1.850 + if (ret == -1 && e != EEXIST) 1.851 + return false; 1.852 + 1.853 + return true; 1.854 +} 1.855 + 1.856 +bool UIFileExists(const string& path) 1.857 +{ 1.858 + struct stat sb; 1.859 + int ret = stat(path.c_str(), &sb); 1.860 + if (ret == -1 || !(sb.st_mode & S_IFREG)) 1.861 + return false; 1.862 + 1.863 + return true; 1.864 +} 1.865 + 1.866 +bool UIMoveFile(const string& file, const string& newfile) 1.867 +{ 1.868 + if (!rename(file.c_str(), newfile.c_str())) 1.869 + return true; 1.870 + if (errno != EXDEV) 1.871 + return false; 1.872 + 1.873 + NSFileManager *fileManager = [NSFileManager defaultManager]; 1.874 + NSString *source = [fileManager stringWithFileSystemRepresentation:file.c_str() length:file.length()]; 1.875 + NSString *dest = [fileManager stringWithFileSystemRepresentation:newfile.c_str() length:newfile.length()]; 1.876 + if (!source || !dest) 1.877 + return false; 1.878 + 1.879 + [fileManager moveItemAtPath:source toPath:dest error:NULL]; 1.880 + return UIFileExists(newfile); 1.881 +} 1.882 + 1.883 +bool UIDeleteFile(const string& file) 1.884 +{ 1.885 + return (unlink(file.c_str()) != -1); 1.886 +} 1.887 + 1.888 +std::ifstream* UIOpenRead(const string& filename) 1.889 +{ 1.890 + return new std::ifstream(filename.c_str(), std::ios::in); 1.891 +} 1.892 + 1.893 +std::ofstream* UIOpenWrite(const string& filename, bool append) // append=false 1.894 +{ 1.895 + return new std::ofstream(filename.c_str(), 1.896 + append ? std::ios::out | std::ios::app 1.897 + : std::ios::out); 1.898 +}