Wed, 31 Dec 2014 06:09:35 +0100
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 }