Port fixes from internal Google Breakpad to SVN.

A=preston, nealsid
R=Stuart, Preston



git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@360 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
nealsid 2009-07-21 00:10:57 +00:00
parent 23c364a2b4
commit 22734848ea
12 changed files with 287 additions and 52 deletions

View File

@ -88,6 +88,7 @@
F93DE33F0F82C66B00608B94 /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53820ECCE635009BE4BA /* string_utilities.cc */; }; F93DE33F0F82C66B00608B94 /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53820ECCE635009BE4BA /* string_utilities.cc */; };
F93DE3410F82C68300608B94 /* exception_handler_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F93DE3400F82C68300608B94 /* exception_handler_test.cc */; }; F93DE3410F82C68300608B94 /* exception_handler_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F93DE3400F82C68300608B94 /* exception_handler_test.cc */; };
F945849E0F280E3C009A47BF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F945849C0F280E3C009A47BF /* Localizable.strings */; }; F945849E0F280E3C009A47BF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F945849C0F280E3C009A47BF /* Localizable.strings */; };
F9B630A0100FF96B00D0F4AC /* goArrow.png in Resources */ = {isa = PBXBuildFile; fileRef = F9B6309F100FF96B00D0F4AC /* goArrow.png */; };
F9C44DB20EF07288003AEBAA /* Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C44DAC0EF07288003AEBAA /* Controller.m */; }; F9C44DB20EF07288003AEBAA /* Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C44DAC0EF07288003AEBAA /* Controller.m */; };
F9C44DB30EF07288003AEBAA /* crashduringload in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAD0EF07288003AEBAA /* crashduringload */; }; F9C44DB30EF07288003AEBAA /* crashduringload in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAD0EF07288003AEBAA /* crashduringload */; };
F9C44DB40EF07288003AEBAA /* crashInMain in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAE0EF07288003AEBAA /* crashInMain */; }; F9C44DB40EF07288003AEBAA /* crashInMain in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAE0EF07288003AEBAA /* crashInMain */; };
@ -288,6 +289,7 @@
F93DE3400F82C68300608B94 /* exception_handler_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = exception_handler_test.cc; path = handler/exception_handler_test.cc; sourceTree = "<group>"; }; F93DE3400F82C68300608B94 /* exception_handler_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = exception_handler_test.cc; path = handler/exception_handler_test.cc; sourceTree = "<group>"; };
F945849D0F280E3C009A47BF /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = sender/English.lproj/Localizable.strings; sourceTree = "<group>"; }; F945849D0F280E3C009A47BF /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = sender/English.lproj/Localizable.strings; sourceTree = "<group>"; };
F945859D0F78241E009A47BF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Framework/Info.plist; sourceTree = "<group>"; }; F945859D0F78241E009A47BF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Framework/Info.plist; sourceTree = "<group>"; };
F9B6309F100FF96B00D0F4AC /* goArrow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = goArrow.png; path = sender/goArrow.png; sourceTree = "<group>"; };
F9C44DA50EF060A8003AEBAA /* BreakpadTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BreakpadTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; F9C44DA50EF060A8003AEBAA /* BreakpadTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BreakpadTest.app; sourceTree = BUILT_PRODUCTS_DIR; };
F9C44DAC0EF07288003AEBAA /* Controller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Controller.m; path = testapp/Controller.m; sourceTree = "<group>"; }; F9C44DAC0EF07288003AEBAA /* Controller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Controller.m; path = testapp/Controller.m; sourceTree = "<group>"; };
F9C44DAD0EF07288003AEBAA /* crashduringload */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = crashduringload; path = testapp/crashduringload; sourceTree = "<group>"; }; F9C44DAD0EF07288003AEBAA /* crashduringload */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = crashduringload; path = testapp/crashduringload; sourceTree = "<group>"; };
@ -535,6 +537,7 @@
F92C56A60ECE04B6009BE4BA /* sender */ = { F92C56A60ECE04B6009BE4BA /* sender */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
F9B6309F100FF96B00D0F4AC /* goArrow.png */,
F92C56A70ECE04C5009BE4BA /* crash_report_sender.h */, F92C56A70ECE04C5009BE4BA /* crash_report_sender.h */,
F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */, F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */,
F945849C0F280E3C009A47BF /* Localizable.strings */, F945849C0F280E3C009A47BF /* Localizable.strings */,
@ -813,6 +816,7 @@
4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */, 4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */,
33880C800F9E097100817F82 /* InfoPlist.strings in Resources */, 33880C800F9E097100817F82 /* InfoPlist.strings in Resources */,
3329D4ED0FA16D820007BBC5 /* Breakpad.nib in Resources */, 3329D4ED0FA16D820007BBC5 /* Breakpad.nib in Resources */,
F9B630A0100FF96B00D0F4AC /* goArrow.png in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@ -89,6 +89,7 @@ extern "C" {
#define BREAKPAD_PROCESS_CRASH_TIME "BreakpadProcessCrashTime" #define BREAKPAD_PROCESS_CRASH_TIME "BreakpadProcessCrashTime"
#define BREAKPAD_LOGFILE_KEY_PREFIX "BreakpadAppLogFile" #define BREAKPAD_LOGFILE_KEY_PREFIX "BreakpadAppLogFile"
#define BREAKPAD_SERVER_PARAMETER_PREFIX "BreakpadServerParameterPrefix_" #define BREAKPAD_SERVER_PARAMETER_PREFIX "BreakpadServerParameterPrefix_"
#define BREAKPAD_ON_DEMAND "BreakpadOnDemand"
// Optional user-defined function to dec to decide if we should handle // Optional user-defined function to dec to decide if we should handle
// this crash or forward it along. // this crash or forward it along.
@ -215,7 +216,7 @@ typedef bool (*BreakpadFilterCallback)(int exception_type,
//============================================================================= //=============================================================================
// The following are NOT user-supplied but are documented here for // The following are NOT user-supplied but are documented here for
// completeness. They are calculated by Breakpad during initialization & // completeness. They are calculated by Breakpad during initialization &
// crash-dump generation. // crash-dump generation, or entered in by the user.
// //
// BREAKPAD_PROCESS_START_TIME The time the process started. // BREAKPAD_PROCESS_START_TIME The time the process started.
// //
@ -242,6 +243,12 @@ typedef bool (*BreakpadFilterCallback)(int exception_type,
// server without leaking Breakpad's // server without leaking Breakpad's
// internal values. // internal values.
// //
// BREAKPAD_ON_DEMAND Used internally to indicate to the
// Reporter that we're sending on-demand,
// not as result of a crash.
//
// BREAKPAD_COMMENTS The text the user provided as comments.
// Only used in crash_report_sender.
// Returns a new BreakpadRef object on success, NULL otherwise. // Returns a new BreakpadRef object on success, NULL otherwise.
BreakpadRef BreakpadCreate(NSDictionary *parameters); BreakpadRef BreakpadCreate(NSDictionary *parameters);
@ -286,7 +293,7 @@ void BreakpadRemoveKeyValue(BreakpadRef ref, NSString *key);
// necessary. Note that as mentioned above there are limits on both // necessary. Note that as mentioned above there are limits on both
// the number of keys and their length. // the number of keys and their length.
void BreakpadAddUploadParameter(BreakpadRef ref, NSString *key, void BreakpadAddUploadParameter(BreakpadRef ref, NSString *key,
NSString *value); NSString *value);
// This method will remove a previously-added parameter from the // This method will remove a previously-added parameter from the
// upload parameter set. // upload parameter set.

View File

@ -409,7 +409,6 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
NSString *timeout = [parameters objectForKey:@BREAKPAD_CONFIRM_TIMEOUT]; NSString *timeout = [parameters objectForKey:@BREAKPAD_CONFIRM_TIMEOUT];
NSArray *logFilePaths = [parameters objectForKey:@BREAKPAD_LOGFILES]; NSArray *logFilePaths = [parameters objectForKey:@BREAKPAD_LOGFILES];
NSString *logFileTailSize = [parameters objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE]; NSString *logFileTailSize = [parameters objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE];
NSString *reportEmail = [parameters objectForKey:@BREAKPAD_EMAIL];
NSString *requestUserText = NSString *requestUserText =
[parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS]; [parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS];
NSString *requestEmail = [parameters objectForKey:@BREAKPAD_REQUEST_EMAIL]; NSString *requestEmail = [parameters objectForKey:@BREAKPAD_REQUEST_EMAIL];
@ -451,7 +450,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
vendor = @"Vendor not specified"; vendor = @"Vendor not specified";
} }
// Normalize the values // Normalize the values.
if (skipConfirm) { if (skipConfirm) {
skipConfirm = [skipConfirm uppercaseString]; skipConfirm = [skipConfirm uppercaseString];
@ -504,7 +503,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
[resourcePath stringByAppendingPathComponent:@"Inspector"]; [resourcePath stringByAppendingPathComponent:@"Inspector"];
} }
// Verify that there is an Inspector tool // Verify that there is an Inspector tool.
if (![[NSFileManager defaultManager] fileExistsAtPath:inspectorPathString]) { if (![[NSFileManager defaultManager] fileExistsAtPath:inspectorPathString]) {
DEBUGLOG(stderr, "Cannot find Inspector tool\n"); DEBUGLOG(stderr, "Cannot find Inspector tool\n");
return false; return false;
@ -517,7 +516,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
reporterPathString = [[NSBundle bundleWithPath:reporterPathString] executablePath]; reporterPathString = [[NSBundle bundleWithPath:reporterPathString] executablePath];
} }
// Verify that there is a Reporter application // Verify that there is a Reporter application.
if (![[NSFileManager defaultManager] if (![[NSFileManager defaultManager]
fileExistsAtPath:reporterPathString]) { fileExistsAtPath:reporterPathString]) {
DEBUGLOG(stderr, "Cannot find Reporter tool\n"); DEBUGLOG(stderr, "Cannot find Reporter tool\n");
@ -588,11 +587,6 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
} }
} }
if (reportEmail) {
dictionary.SetKeyValue(BREAKPAD_EMAIL,
[reportEmail UTF8String]);
}
if (serverParameters) { if (serverParameters) {
// For each key-value pair, call BreakpadAddUploadParameter() // For each key-value pair, call BreakpadAddUploadParameter()
NSEnumerator *keyEnumerator = [serverParameters keyEnumerator]; NSEnumerator *keyEnumerator = [serverParameters keyEnumerator];
@ -633,7 +627,9 @@ void Breakpad::RemoveKeyValue(NSString *key) {
//============================================================================= //=============================================================================
void Breakpad::GenerateAndSendReport() { void Breakpad::GenerateAndSendReport() {
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "YES");
HandleException(0, 0, 0, mach_thread_self()); HandleException(0, 0, 0, mach_thread_self());
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "NO");
} }
//============================================================================= //=============================================================================

View File

@ -319,6 +319,12 @@ kern_return_t Inspector::ReadMessages() {
printf("parameter count = %d\n", info.parameter_count); printf("parameter count = %d\n", info.parameter_count);
#endif #endif
// In certain situations where multiple crash requests come
// through quickly, we can end up with the mach IPC messages not
// coming through correctly. Since we don't know what parameters
// we've missed, we can't do much besides abort the crash dump
// situation in this case.
unsigned int parameters_read = 0;
// The initial message contains the number of key value pairs that // The initial message contains the number of key value pairs that
// we are expected to read. // we are expected to read.
// Read each key/value pair, one mach message per key/value pair. // Read each key/value pair, one mach message per key/value pair.
@ -329,6 +335,14 @@ kern_return_t Inspector::ReadMessages() {
if(result == KERN_SUCCESS) { if(result == KERN_SUCCESS) {
KeyValueMessageData &key_value_data = KeyValueMessageData &key_value_data =
(KeyValueMessageData&)*message.GetData(); (KeyValueMessageData&)*message.GetData();
// If we get a blank key, make sure we don't increment the
// parameter count; in some cases (notably on-demand generation
// many times in a short period of time) caused the Mach IPC
// messages to not come through correctly.
if (strlen(key_value_data.key) == 0) {
continue;
}
parameters_read++;
config_params_.SetKeyValue(key_value_data.key, key_value_data.value); config_params_.SetKeyValue(key_value_data.key, key_value_data.value);
} else { } else {
@ -336,6 +350,11 @@ kern_return_t Inspector::ReadMessages() {
break; break;
} }
} }
if (parameters_read != info.parameter_count) {
DEBUGLOG(stderr, "Only read %d parameters instead of %d, aborting crash "
"dump generation.", parameters_read, info.parameter_count);
return KERN_FAILURE;
}
} }
return result; return result;
@ -379,7 +398,7 @@ bool Inspector::InspectTask() {
SetCrashTimeParameters(); SetCrashTimeParameters();
// If the client app has not specified a minidump directory, // If the client app has not specified a minidump directory,
// use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name> // use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name>
if (0 == strlen(minidumpDirectory)) { if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) {
NSArray *libraryDirectories = NSArray *libraryDirectories =
NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
NSUserDomainMask, NSUserDomainMask,

View File

@ -4,6 +4,14 @@
<dict> <dict>
<key>IBClasses</key> <key>IBClasses</key>
<array> <array>
<dict>
<key>CLASS</key>
<string>LengthLimitingTextField</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSTextField</string>
</dict>
<dict> <dict>
<key>ACTIONS</key> <key>ACTIONS</key>
<dict> <dict>
@ -26,10 +34,14 @@
<string>NSButton</string> <string>NSButton</string>
<key>commentMessage_</key> <key>commentMessage_</key>
<string>NSTextField</string> <string>NSTextField</string>
<key>commentsEntryField_</key>
<string>LengthLimitingTextField</string>
<key>countdownLabel_</key>
<string>NSTextField</string>
<key>dialogTitle_</key> <key>dialogTitle_</key>
<string>NSTextField</string> <string>NSTextField</string>
<key>emailEntryField_</key> <key>emailEntryField_</key>
<string>NSView</string> <string>LengthLimitingTextField</string>
<key>emailLabel_</key> <key>emailLabel_</key>
<string>NSTextField</string> <string>NSTextField</string>
<key>emailMessage_</key> <key>emailMessage_</key>

View File

@ -3,17 +3,17 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>IBFramework Version</key> <key>IBFramework Version</key>
<string>672</string> <string>676</string>
<key>IBLastKnownRelativeProjectPath</key> <key>IBLastKnownRelativeProjectPath</key>
<string>../Breakpad.xcodeproj</string> <string>../Breakpad.xcodeproj</string>
<key>IBOldestOS</key> <key>IBOldestOS</key>
<integer>5</integer> <integer>5</integer>
<key>IBOpenObjects</key> <key>IBOpenObjects</key>
<array> <array>
<integer>2</integer> <integer>132</integer>
</array> </array>
<key>IBSystem Version</key> <key>IBSystem Version</key>
<string>9G55</string> <string>9J61</string>
<key>targetFramework</key> <key>targetFramework</key>
<string>IBCocoaFramework</string> <string>IBCocoaFramework</string>
</dict> </dict>

Binary file not shown.

View File

@ -41,6 +41,24 @@
extern NSString *const kGoogleServerType; extern NSString *const kGoogleServerType;
extern NSString *const kSocorroServerType; extern NSString *const kSocorroServerType;
extern NSString *const kDefaultServerType; extern NSString *const kDefaultServerType;
// We're sublcassing NSTextField in order to override a particular
// method (see the implementation) that lets us reject changes if they
// are longer than a particular length. Bindings would normally solve
// this problem, but when we implemented a validation method, and
// returned NO for strings that were too long, the UI was not updated
// right away, which was a poor user experience. The UI would be
// updated as soon as the text field lost first responder status,
// which isn't soon enough. It is a known bug that the UI KVO didn't
// work in the middle of a validation.
@interface LengthLimitingTextField : NSTextField {
@private
unsigned int maximumLength_;
}
- (void) setMaximumLength:(unsigned int)maxLength;
@end
@interface Reporter : NSObject { @interface Reporter : NSObject {
@public @public
IBOutlet NSWindow *alertWindow_; // The alert window IBOutlet NSWindow *alertWindow_; // The alert window
@ -50,26 +68,34 @@ extern NSString *const kDefaultServerType;
IBOutlet NSBox *preEmailBox_; IBOutlet NSBox *preEmailBox_;
IBOutlet NSBox *emailSectionBox_; IBOutlet NSBox *emailSectionBox_;
// Localized elements (or things that need to be moved during localization). // Localized elements (or things that need to be moved during localization).
IBOutlet NSTextField *dialogTitle_; IBOutlet NSTextField *dialogTitle_;
IBOutlet NSTextField *commentMessage_; IBOutlet NSTextField *commentMessage_;
IBOutlet NSTextField *emailMessage_; IBOutlet NSTextField *emailMessage_;
IBOutlet NSTextField *emailLabel_; IBOutlet NSTextField *emailLabel_;
IBOutlet NSTextField *privacyLinkLabel_; IBOutlet NSTextField *privacyLinkLabel_;
IBOutlet NSButton *sendButton_; IBOutlet NSButton *sendButton_;
IBOutlet NSButton *cancelButton_; IBOutlet NSButton *cancelButton_;
IBOutlet NSView *emailEntryField_; IBOutlet LengthLimitingTextField *emailEntryField_;
IBOutlet NSView *privacyLinkArrow_; IBOutlet LengthLimitingTextField *commentsEntryField_;
IBOutlet NSTextField *countdownLabel_;
IBOutlet NSView *privacyLinkArrow_;
// Text field bindings, for user input. // Text field bindings, for user input.
NSString *commentsValue_; // Comments from the user NSString *commentsValue_; // Comments from the user
NSString *emailValue_; // Email from the user NSString *emailValue_; // Email from the user
NSString *countdownMessage_; // Message indicating time
// left for input.
@private @private
int configFile_; // File descriptor for config file int configFile_; // File descriptor for config file
NSMutableDictionary *parameters_; // Key value pairs of data (STRONG) NSMutableDictionary *parameters_; // Key value pairs of data (STRONG)
NSData *minidumpContents_; // The data in the minidump (STRONG) NSData *minidumpContents_; // The data in the minidump (STRONG)
NSData *logFileData_; // An NSdata for the tar, NSData *logFileData_; // An NSdata for the tar,
// bz2'd log file. // bz2'd log file.
NSTimeInterval remainingDialogTime_; // Keeps track of how long
// we have until we cancel
// the dialog
NSTimer *messageTimer_; // Timer we use to update
// the dialog
NSMutableDictionary *serverDictionary_; // The dictionary mapping a NSMutableDictionary *serverDictionary_; // The dictionary mapping a
// server type name to a // server type name to a
// dictionary of server // dictionary of server
@ -107,4 +133,7 @@ extern NSString *const kDefaultServerType;
- (NSString *)emailValue; - (NSString *)emailValue;
- (void)setEmailValue:(NSString *)value; - (void)setEmailValue:(NSString *)value;
- (NSString *)countdownMessage;
- (void)setCountdownMessage:(NSString *)value;
@end @end

View File

@ -42,8 +42,11 @@
#define kLastSubmission @"LastSubmission" #define kLastSubmission @"LastSubmission"
const int kMinidumpFileLengthLimit = 800000; const int kMinidumpFileLengthLimit = 800000;
const int kUserCommentsMaxLength = 1500;
const int kEmailMaxLength = 64;
#define kApplePrefsSyncExcludeAllKey @"com.apple.PreferenceSync.ExcludeAllSyncKeys" #define kApplePrefsSyncExcludeAllKey \
@"com.apple.PreferenceSync.ExcludeAllSyncKeys"
NSString *const kGoogleServerType = @"google"; NSString *const kGoogleServerType = @"google";
NSString *const kSocorroServerType = @"socorro"; NSString *const kSocorroServerType = @"socorro";
@ -174,6 +177,9 @@ NSString *const kDefaultServerType = @"google";
// Returns YES if we should send the report without asking the user first. // Returns YES if we should send the report without asking the user first.
- (BOOL)shouldSubmitSilently; - (BOOL)shouldSubmitSilently;
// Returns YES if the minidump was generated on demand.
- (BOOL)isOnDemand;
// Returns YES if we should ask the user to provide comments. // Returns YES if we should ask the user to provide comments.
- (BOOL)shouldRequestComments; - (BOOL)shouldRequestComments;
@ -187,11 +193,11 @@ NSString *const kDefaultServerType = @"google";
// Returns the short description of the crash, suitable for use as a dialog // Returns the short description of the crash, suitable for use as a dialog
// title (e.g., "The application Foo has quit unexpectedly"). // title (e.g., "The application Foo has quit unexpectedly").
- (NSString*)shortCrashDialogMessage; - (NSString*)shortDialogMessage;
// Return explanatory text about the crash and the reporter, suitable for the // Return explanatory text about the crash and the reporter, suitable for the
// body text of a dialog. // body text of a dialog.
- (NSString*)explanatoryCrashDialogText; - (NSString*)explanatoryDialogText;
// Returns the amount of time the UI should be shown before timing out. // Returns the amount of time the UI should be shown before timing out.
- (NSTimeInterval)messageTimeout; - (NSTimeInterval)messageTimeout;
@ -207,7 +213,7 @@ NSString *const kDefaultServerType = @"google";
- (void)removeEmailPrompt; - (void)removeEmailPrompt;
// Run an alert window with the given timeout. Returns // Run an alert window with the given timeout. Returns
// NSAlertButtonDefault if the timeout is exceeded. A timeout of 0 // NSRunStoppedResponse if the timeout is exceeded. A timeout of 0
// queues the message immediately in the modal run loop. // queues the message immediately in the modal run loop.
- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout; - (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout;
@ -235,6 +241,16 @@ NSString *const kDefaultServerType = @"google";
// will be uploaded to the crash server. // will be uploaded to the crash server.
- (void)addServerParameter:(id)value forKey:(NSString *)key; - (void)addServerParameter:(id)value forKey:(NSString *)key;
// This method is used to periodically update the UI with how many
// seconds are left in the dialog display.
- (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer;
// When we receive this notification, it means that the user has
// begun editing the email address or comments field, and we disable
// the timers so that the user has as long as they want to type
// in their comments/email.
- (void)controlTextDidBeginEditing:(NSNotification *)aNotification;
@end @end
@implementation Reporter @implementation Reporter
@ -261,6 +277,7 @@ NSString *const kDefaultServerType = @"google";
- (id)initWithConfigurationFD:(int)fd { - (id)initWithConfigurationFD:(int)fd {
if ((self = [super init])) { if ((self = [super init])) {
configFile_ = fd; configFile_ = fd;
remainingDialogTime_ = 0;
} }
// Because the reporter is embedded in the framework (and many copies // Because the reporter is embedded in the framework (and many copies
@ -545,8 +562,8 @@ NSString *const kDefaultServerType = @"google";
} }
} else { } else {
// Create an alert panel to tell the user something happened // Create an alert panel to tell the user something happened
NSPanel* alert = NSGetAlertPanel([self shortCrashDialogMessage], NSPanel* alert = NSGetAlertPanel([self shortDialogMessage],
[self explanatoryCrashDialogText], [self explanatoryDialogText],
NSLocalizedString(@"sendReportButton", @""), NSLocalizedString(@"sendReportButton", @""),
NSLocalizedString(@"cancelButton", @""), NSLocalizedString(@"cancelButton", @""),
nil); nil);
@ -566,11 +583,11 @@ NSString *const kDefaultServerType = @"google";
// "fall" as text areas are shrunk from their overly-large IB sizes. // "fall" as text areas are shrunk from their overly-large IB sizes.
// Localize the header. No resizing needed, as it has plenty of room. // Localize the header. No resizing needed, as it has plenty of room.
[dialogTitle_ setStringValue:[self shortCrashDialogMessage]]; [dialogTitle_ setStringValue:[self shortDialogMessage]];
// Localize the explanatory text field. // Localize the explanatory text field.
[commentMessage_ setStringValue:[NSString stringWithFormat:@"%@\n\n%@", [commentMessage_ setStringValue:[NSString stringWithFormat:@"%@\n\n%@",
[self explanatoryCrashDialogText], [self explanatoryDialogText],
NSLocalizedString(@"commentsMsg", @"")]]; NSLocalizedString(@"commentsMsg", @"")]];
float commentHeightDelta = [commentMessage_ breakpad_adjustHeightToFit]; float commentHeightDelta = [commentMessage_ breakpad_adjustHeightToFit];
[headerBox_ breakpad_shiftVertically:commentHeightDelta]; [headerBox_ breakpad_shiftVertically:commentHeightDelta];
@ -615,9 +632,13 @@ NSString *const kDefaultServerType = @"google";
- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout { - (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout {
// Queue a |stopModal| message to be performed in |timeout| seconds. // Queue a |stopModal| message to be performed in |timeout| seconds.
if (timeout > 0.001) { if (timeout > 0.001) {
[NSApp performSelector:@selector(stopModal) remainingDialogTime_ = timeout;
withObject:nil SEL updateSelector = @selector(updateSecondsLeftInDialogDisplay:);
afterDelay:timeout]; messageTimer_ = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:updateSelector
userInfo:nil
repeats:YES];
} }
// Run the window modally and wait for either a |stopModal| message or a // Run the window modally and wait for either a |stopModal| message or a
@ -625,12 +646,6 @@ NSString *const kDefaultServerType = @"google";
[NSApp activateIgnoringOtherApps:YES]; [NSApp activateIgnoringOtherApps:YES];
int returnMethod = [NSApp runModalForWindow:window]; int returnMethod = [NSApp runModalForWindow:window];
// Cancel the pending |stopModal| message.
if (returnMethod != NSRunStoppedResponse) {
[NSObject cancelPreviousPerformRequestsWithTarget:NSApp
selector:@selector(stopModal)
object:nil];
}
return returnMethod; return returnMethod;
} }
@ -667,8 +682,10 @@ NSString *const kDefaultServerType = @"google";
textView:(NSTextView*)textView textView:(NSTextView*)textView
doCommandBySelector:(SEL)commandSelector { doCommandBySelector:(SEL)commandSelector {
BOOL result = NO; BOOL result = NO;
// If the user has entered text, don't end editing on "return" // If the user has entered text on the comment field, don't end
if (commandSelector == @selector(insertNewline:) // editing on "return".
if (control == commentsEntryField_ &&
commandSelector == @selector(insertNewline:)
&& [[textView string] length] > 0) { && [[textView string] length] > 0) {
[textView insertNewlineIgnoringFieldEditor:self]; [textView insertNewlineIgnoringFieldEditor:self];
result = YES; result = YES;
@ -676,6 +693,50 @@ doCommandBySelector:(SEL)commandSelector {
return result; return result;
} }
- (void)controlTextDidBeginEditing:(NSNotification *)aNotification {
[messageTimer_ invalidate];
[self setCountdownMessage:@""];
}
- (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer {
remainingDialogTime_ -= 1;
NSString *countdownMessage;
NSString *formatString;
int displayedTimeLeft; // This can be either minutes or seconds.
if (remainingDialogTime_ > 59) {
// calculate minutes remaining for UI purposes
displayedTimeLeft = (remainingDialogTime_ / 60);
if (displayedTimeLeft == 1) {
formatString = NSLocalizedString(@"countdownMsgMinuteSingular", @"");
} else {
formatString = NSLocalizedString(@"countdownMsgMinutesPlural", @"");
}
} else {
displayedTimeLeft = remainingDialogTime_;
if (remainingDialogTime_ == 1) {
formatString = NSLocalizedString(@"countdownMsgSecondSingular", @"");
} else {
formatString = NSLocalizedString(@"countdownMsgSecondsPlural", @"");
}
}
countdownMessage = [NSString stringWithFormat:formatString,
displayedTimeLeft];
if (remainingDialogTime_ <= 30) {
[countdownLabel_ setTextColor:[NSColor redColor]];
}
[self setCountdownMessage:countdownMessage];
if (remainingDialogTime_ <= 0) {
[messageTimer_ invalidate];
[NSApp stopModal];
}
}
#pragma mark Accessors #pragma mark Accessors
#pragma mark - #pragma mark -
//============================================================================= //=============================================================================
@ -702,6 +763,17 @@ doCommandBySelector:(SEL)commandSelector {
} }
} }
- (NSString *)countdownMessage {
return [[countdownMessage_ retain] autorelease];
}
- (void)setCountdownMessage:(NSString *)value {
if (countdownMessage_ != value) {
[countdownMessage_ release];
countdownMessage_ = [value copy];
}
}
#pragma mark - #pragma mark -
//============================================================================= //=============================================================================
- (BOOL)reportIntervalElapsed { - (BOOL)reportIntervalElapsed {
@ -730,6 +802,11 @@ doCommandBySelector:(SEL)commandSelector {
return YES; return YES;
} }
- (BOOL)isOnDemand {
return [[parameters_ objectForKey:@BREAKPAD_ON_DEMAND]
isEqualToString:@"YES"];
}
- (BOOL)shouldSubmitSilently { - (BOOL)shouldSubmitSilently {
return [[parameters_ objectForKey:@BREAKPAD_SKIP_CONFIRM] return [[parameters_ objectForKey:@BREAKPAD_SKIP_CONFIRM]
isEqualToString:@"YES"]; isEqualToString:@"YES"];
@ -745,21 +822,40 @@ doCommandBySelector:(SEL)commandSelector {
isEqualToString:@"YES"]; isEqualToString:@"YES"];
} }
- (NSString*)shortCrashDialogMessage { - (NSString*)shortDialogMessage {
NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY]; NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
if (![displayName length]) if (![displayName length])
displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT]; displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
return [NSString stringWithFormat:NSLocalizedString(@"headerFmt", @""), if ([self isOnDemand]) {
displayName]; return [NSString
stringWithFormat:NSLocalizedString(@"noCrashDialogHeader", @""),
displayName];
} else {
return [NSString
stringWithFormat:NSLocalizedString(@"crashDialogHeader", @""),
displayName];
}
} }
- (NSString*)explanatoryCrashDialogText { - (NSString*)explanatoryDialogText {
NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
if (![displayName length])
displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
NSString *vendor = [parameters_ objectForKey:@BREAKPAD_VENDOR]; NSString *vendor = [parameters_ objectForKey:@BREAKPAD_VENDOR];
if (![vendor length]) if (![vendor length])
vendor = @"unknown vendor"; vendor = @"unknown vendor";
return [NSString stringWithFormat:NSLocalizedString(@"msgFmt", @""), vendor]; if ([self isOnDemand]) {
return [NSString
stringWithFormat:NSLocalizedString(@"noCrashDialogMsg", @""),
vendor, displayName];
} else {
return [NSString
stringWithFormat:NSLocalizedString(@"crashDialogMsg", @""),
vendor];
}
} }
- (NSTimeInterval)messageTimeout { - (NSTimeInterval)messageTimeout {
@ -940,6 +1036,77 @@ doCommandBySelector:(SEL)commandSelector {
[super dealloc]; [super dealloc];
} }
- (void)awakeFromNib {
[emailEntryField_ setMaximumLength:kEmailMaxLength];
[commentsEntryField_ setMaximumLength:kUserCommentsMaxLength];
}
@end
//=============================================================================
@implementation LengthLimitingTextField
- (void) setMaximumLength:(unsigned int)maxLength {
maximumLength_ = maxLength;
}
// This is the method we're overriding in NSTextField, which lets us
// limit the user's input if it makes the string too long.
- (BOOL) textView:(NSTextView *)textView
shouldChangeTextInRange:(NSRange)affectedCharRange
replacementString:(NSString *)replacementString {
// Sometimes the range comes in invalid, so reject if we can't
// figure out if the replacement text is too long.
if (affectedCharRange.location == NSNotFound) {
return NO;
}
// Figure out what the new string length would be, taking into
// account user selections.
int newStringLength =
[[textView string] length] - affectedCharRange.length +
[replacementString length];
if (newStringLength > maximumLength_) {
return NO;
} else {
return YES;
}
}
// Cut, copy, and paste have to be caught specifically since there is no menu.
- (BOOL)performKeyEquivalent:(NSEvent*)event {
// Only handle the key equivalent if |self| is the text field with focus.
NSText* fieldEditor = [self currentEditor];
if (fieldEditor != nil) {
// Check for a single "Command" modifier
unsigned int modifiers = [event modifierFlags];
modifiers &= NSDeviceIndependentModifierFlagsMask;
if (modifiers == NSCommandKeyMask) {
// Now, check for Select All, Cut, Copy, or Paste key equivalents.
NSString* characters = [event characters];
// Select All is Command-A.
if ([characters isEqualToString:@"a"]) {
[fieldEditor selectAll:self];
return YES;
// Cut is Command-X.
} else if ([characters isEqualToString:@"x"]) {
[fieldEditor cut:self];
return YES;
// Copy is Command-C.
} else if ([characters isEqualToString:@"c"]) {
[fieldEditor copy:self];
return YES;
// Paste is Command-V.
} else if ([characters isEqualToString:@"v"]) {
[fieldEditor paste:self];
return YES;
}
}
}
// Let the super class handle the rest (e.g. Command-Period will cancel).
return [super performKeyEquivalent:event];
}
@end @end
//============================================================================= //=============================================================================

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -209,8 +209,9 @@ const mach_port_t kNoLastExceptionThread = MACH_PORT_NULL;
@"Last exception type is not 0 for on demand"); @"Last exception type is not 0 for on demand");
STAssertEquals(last_exception_code_, 0, STAssertEquals(last_exception_code_, 0,
@"Last exception code is not 0 for on demand"); @"Last exception code is not 0 for on demand");
STAssertEquals(last_exception_thread_, (mach_port_t)0, STAssertEquals(last_exception_thread_, mach_thread_self(),
@"Last exception thread is not 0 for on demand"); @"Last exception thread is not mach_thread_self() "
"for on demand");
} }
@end @end