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.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #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>
    17 using std::string;
    18 using std::vector;
    19 using std::ostringstream;
    21 using namespace CrashReporter;
    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;
    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 };
    43 #define NSSTR(s) [NSString stringWithUTF8String:(s).c_str()]
    45 static NSString* Str(const char* aName)
    46 {
    47   string str = gStrings[aName];
    48   if (str.empty()) str = "?";
    49   return NSSTR(str);
    50 }
    52 static bool RestartApplication()
    53 {
    54   vector<char*> argv(gRestartArgs.size() + 1);
    56   posix_spawnattr_t spawnattr;
    57   if (posix_spawnattr_init(&spawnattr) != 0) {
    58     return false;
    59   }
    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   }
    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;
    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);
    90   posix_spawnattr_destroy(&spawnattr);
    92   return result == 0;
    93 }
    95 @implementation CrashReporterUI
    97 -(void)awakeFromNib
    98 {
    99   gUI = self;
   100   [mWindow center];
   102   [mWindow setTitle:[[NSBundle mainBundle]
   103                       objectForInfoDictionaryKey:@"CFBundleName"]];
   104 }
   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;
   114   [mWindow setTitle:Str(ST_CRASHREPORTERTITLE)];
   115   [mHeaderLabel setStringValue:Str(ST_CRASHREPORTERHEADER)];
   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   }
   128   [mSubmitReportButton setTitle:Str(ST_CHECKSUBMIT)];
   129   [mIncludeURLButton setTitle:Str(ST_CHECKURL)];
   130   [mEmailMeButton setTitle:Str(ST_CHECKEMAIL)];
   131   [mViewReportOkButton setTitle:Str(ST_OK)];
   133   [mCommentText setPlaceholder:Str(ST_COMMENTGRAYTEXT)];
   134   if (gRTLlayout)
   135     [mCommentText toggleBaseWritingDirection:self];
   136   [[mEmailText cell] setPlaceholderString:Str(ST_EMAILGRAYTEXT)];
   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];
   153     [mViewReportButton setAutoresizingMask:NSViewMinYMargin];
   154     [mSubmitReportButton setAutoresizingMask:NSViewMinYMargin];
   155     [mCommentScrollView setAutoresizingMask:NSViewMinYMargin];
   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];
   161     [mViewReportButton setAutoresizingMask:buttonMask];
   162     [mSubmitReportButton setAutoresizingMask:checkMask];
   163     [mCommentScrollView setAutoresizingMask:commentScrollMask];
   164   }
   166   // resize some buttons horizontally and possibly some controls vertically
   167   [self doInitialResizing];
   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)];
   184   [self updateSubmit];
   185   [self updateURL];
   186   [self updateEmail];
   188   [mWindow makeKeyAndOrderFront:nil];
   189 }
   191 -(void)showErrorUI:(const string&)message
   192 {
   193   [self setView: mErrorView animate: NO];
   195   [mErrorHeaderLabel setStringValue:Str(ST_CRASHREPORTERHEADER)];
   196   [self setStringFitVertically:mErrorLabel
   197                         string:NSSTR(message)
   198                   resizeWindow:YES];
   199   [mErrorCloseButton setTitle:Str(ST_OK)];
   201   [mErrorCloseButton setKeyEquivalent:@"\r"];
   202   [mWindow makeFirstResponder:mErrorCloseButton];
   203   [mWindow makeKeyAndOrderFront:nil];
   204 }
   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];
   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   }
   235   NSAttributedString* extra = [[NSAttributedString alloc]
   236                                initWithString:NSSTR("\n" + gStrings[ST_EXTRAREPORTINFO])
   237                                attributes:normalAttr];
   238   [[mViewReportTextView textStorage] appendAttributedString: extra];
   239   [extra release];
   240 }
   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 }
   261 - (void)closeMeDown:(id)unused
   262 {
   263   [NSApp terminate:self];
   264 }
   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 }
   275 -(IBAction)viewReportClicked:(id)sender
   276 {
   277   [self showReportInfo];
   278   [NSApp beginSheet:mViewReportWindow modalForWindow:mWindow
   279    modalDelegate:nil didEndSelector:nil contextInfo:nil];
   280 }
   282 - (IBAction)viewReportOkClicked:(id)sender;
   283 {
   284   [mViewReportWindow orderOut:nil];
   285   [NSApp endSheet:mViewReportWindow];
   286 }
   288 -(IBAction)closeClicked:(id)sender
   289 {
   290   [self maybeSubmitReport];
   291 }
   293 -(IBAction)restartClicked:(id)sender
   294 {
   295   RestartApplication();
   296   [self maybeSubmitReport];
   297 }
   299 - (IBAction)includeURLClicked:(id)sender
   300 {
   301   [self updateURL];
   302 }
   304 -(IBAction)emailMeClicked:(id)sender
   305 {
   306   [self updateEmail];
   307 }
   309 -(void)controlTextDidChange:(NSNotification *)note
   310 {
   311   [self updateEmail];
   312 }
   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 }
   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 }
   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   }
   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;
   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   }
   395   NSButton *checkboxes[] = {
   396     mSubmitReportButton,
   397     mIncludeURLButton,
   398     mEmailMeButton
   399   };
   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   }
   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];
   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 }
   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;
   451   frame.size.height = 10000;
   452   NSSize oldCellSize = [[control cell] cellSizeForBounds: frame];
   453   [control setStringValue: str];
   454   NSSize newCellSize = [[control cell] cellSizeForBounds: frame];
   456   float delta = newCellSize.height - oldCellSize.height;
   457   frame.origin.y -= delta;
   458   frame.size.height = oldHeight + delta;
   459   [control setFrame: frame];
   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   }
   468   return delta;
   469 }
   471 -(void)setView: (NSView*)v animate: (BOOL)animate
   472 {
   473   NSRect frame = [mWindow frame];
   475   NSRect oldViewFrame = [[mWindow contentView] frame];
   476   NSRect newViewFrame = [v frame];
   478   frame.origin.y += oldViewFrame.size.height - newViewFrame.size.height;
   479   frame.size.height -= oldViewFrame.size.height - newViewFrame.size.height;
   481   frame.origin.x += oldViewFrame.size.width - newViewFrame.size.width;
   482   frame.size.width -= oldViewFrame.size.width - newViewFrame.size.width;
   484   [mWindow setContentView:v];
   485   [mWindow setFrame:frame display:true animate:animate];
   486 }
   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 }
   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 }
   516 -(void)updateURL
   517 {
   518   if ([mIncludeURLButton state] == NSOnState && !gURLParameter.empty()) {
   519     gQueryParameters["URL"] = gURLParameter;
   520   } else {
   521     gQueryParameters.erase("URL");
   522   }
   523 }
   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 }
   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   }
   550   [NSThread detachNewThreadSelector:@selector(uploadThread:)
   551             toTarget:self
   552             withObject:mPost];
   553 }
   555 -(bool)setupPost
   556 {
   557   NSURL* url = [NSURL URLWithString:[NSSTR(gSendURL) stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
   558   if (!url) return false;
   560   mPost = [[HTTPMultipartUpload alloc] initWithURL: url];
   561   if (!mPost) return false;
   563   NSMutableDictionary* parameters =
   564     [[NSMutableDictionary alloc] initWithCapacity: gQueryParameters.size()];
   565   if (!parameters) return false;
   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   }
   576   [mPost addFileAtPath: NSSTR(gDumpFile) name: @"upload_file_minidump"];
   577   [mPost setParameters: parameters];
   578   [parameters release];
   580   return true;
   581 }
   583 -(void)uploadComplete:(NSData*)data
   584 {
   585   NSHTTPURLResponse* response = [mPost response];
   586   [mPost release];
   588   bool success;
   589   string reply;
   590   if (!data || !response || [response statusCode] != 200) {
   591     success = false;
   592     reply = "";
   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");
   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   }
   618   SendCompleted(success, reply);
   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 }
   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   }
   647   [self performSelectorOnMainThread: @selector(uploadComplete:)
   648         withObject: data
   649         waitUntilDone: YES];
   651   [autoreleasepool release];
   652 }
   654 // to get auto-quit when we close the window
   655 -(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication
   656 {
   657   return YES;
   658 }
   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 }
   668 @end
   670 @implementation TextViewWithPlaceHolder
   672 - (BOOL)becomeFirstResponder
   673 {
   674   [self setNeedsDisplay:YES];
   675   return [super becomeFirstResponder];
   676 }
   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 }
   686 - (BOOL)resignFirstResponder
   687 {
   688   [self setNeedsDisplay:YES];
   689   return [super resignFirstResponder];
   690 }
   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])];
   704 }
   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 }
   712 - (void)insertBacktab:(id)sender
   713 {
   714   [[self window] selectPreviousKeyView:sender];
   715 }
   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 }
   740 - (void)dealloc
   741 {
   742   [mPlaceHolderString release];
   743   [super dealloc];
   744 }
   746 @end
   748 /* === Crashreporter UI Functions === */
   750 bool UIInit()
   751 {
   752   gMainPool = [[NSAutoreleasePool alloc] init];
   753   [NSApplication sharedApplication];
   755   if (gStrings.find("isRTL") != gStrings.end() &&
   756       gStrings["isRTL"] == "yes")
   757     gRTLlayout = true;
   759   [NSBundle loadNibNamed:(gRTLlayout ? @"MainMenuRTL" : @"MainMenu")
   760                    owner:NSApp];
   762   return true;
   763 }
   765 void UIShutdown()
   766 {
   767   [gMainPool release];
   768 }
   770 void UIShowDefaultUI()
   771 {
   772   [gUI showErrorUI: gStrings[ST_CRASHREPORTERDEFAULT]];
   773   [NSApp run];
   774 }
   776 bool UIShowCrashUI(const string& dumpfile,
   777                    const StringTable& queryParameters,
   778                    const string& sendURL,
   779                    const vector<string>& restartArgs)
   780 {
   781   gRestartArgs = restartArgs;
   783   [gUI showCrashUI: dumpfile
   784        queryParameters: queryParameters
   785        sendURL: sendURL];
   786   [NSApp run];
   788   return gDidTrySend;
   789 }
   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   }
   799   [gUI showErrorUI: message];
   800   [NSApp run];
   801 }
   803 bool UIGetIniPath(string& path)
   804 {
   805   path = gArgv[0];
   806   path.append(".ini");
   808   return true;
   809 }
   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;
   821   unsigned char path[PATH_MAX];
   822   FSRefMakePath(&foundRef, path, sizeof(path));
   823   NSString* destPath = [NSString stringWithUTF8String:reinterpret_cast<char*>(path)];
   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;
   836   destPath = [destPath stringByAppendingPathComponent: @"Crash Reports"];
   838   settingsPath = [destPath UTF8String];
   840   return true;
   841 }
   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;
   850   return true;
   851 }
   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;
   860   return true;
   861 }
   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;
   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;
   876   [fileManager moveItemAtPath:source toPath:dest error:NULL];
   877   return UIFileExists(newfile);
   878 }
   880 bool UIDeleteFile(const string& file)
   881 {
   882   return (unlink(file.c_str()) != -1);
   883 }
   885 std::ifstream* UIOpenRead(const string& filename)
   886 {
   887   return new std::ifstream(filename.c_str(), std::ios::in);
   888 }
   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 }

mercurial