toolkit/crashreporter/client/crashreporter_osx.mm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial