diff options
Diffstat (limited to 'src/client/mac/sender/crash_report_sender.m')
-rw-r--r-- | src/client/mac/sender/crash_report_sender.m | 215 |
1 files changed, 191 insertions, 24 deletions
diff --git a/src/client/mac/sender/crash_report_sender.m b/src/client/mac/sender/crash_report_sender.m index 5d76290c..31c35fe5 100644 --- a/src/client/mac/sender/crash_report_sender.m +++ b/src/client/mac/sender/crash_report_sender.m @@ -42,8 +42,11 @@ #define kLastSubmission @"LastSubmission" 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 kSocorroServerType = @"socorro"; @@ -174,6 +177,9 @@ NSString *const kDefaultServerType = @"google"; // Returns YES if we should send the report without asking the user first. - (BOOL)shouldSubmitSilently; +// Returns YES if the minidump was generated on demand. +- (BOOL)isOnDemand; + // Returns YES if we should ask the user to provide comments. - (BOOL)shouldRequestComments; @@ -187,11 +193,11 @@ NSString *const kDefaultServerType = @"google"; // Returns the short description of the crash, suitable for use as a dialog // 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 // body text of a dialog. -- (NSString*)explanatoryCrashDialogText; +- (NSString*)explanatoryDialogText; // Returns the amount of time the UI should be shown before timing out. - (NSTimeInterval)messageTimeout; @@ -207,7 +213,7 @@ NSString *const kDefaultServerType = @"google"; - (void)removeEmailPrompt; // 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. - (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout; @@ -235,6 +241,16 @@ NSString *const kDefaultServerType = @"google"; // will be uploaded to the crash server. - (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 @implementation Reporter @@ -261,6 +277,7 @@ NSString *const kDefaultServerType = @"google"; - (id)initWithConfigurationFD:(int)fd { if ((self = [super init])) { configFile_ = fd; + remainingDialogTime_ = 0; } // Because the reporter is embedded in the framework (and many copies @@ -545,8 +562,8 @@ NSString *const kDefaultServerType = @"google"; } } else { // Create an alert panel to tell the user something happened - NSPanel* alert = NSGetAlertPanel([self shortCrashDialogMessage], - [self explanatoryCrashDialogText], + NSPanel* alert = NSGetAlertPanel([self shortDialogMessage], + [self explanatoryDialogText], NSLocalizedString(@"sendReportButton", @""), NSLocalizedString(@"cancelButton", @""), nil); @@ -566,11 +583,11 @@ NSString *const kDefaultServerType = @"google"; // "fall" as text areas are shrunk from their overly-large IB sizes. // 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. [commentMessage_ setStringValue:[NSString stringWithFormat:@"%@\n\n%@", - [self explanatoryCrashDialogText], + [self explanatoryDialogText], NSLocalizedString(@"commentsMsg", @"")]]; float commentHeightDelta = [commentMessage_ breakpad_adjustHeightToFit]; [headerBox_ breakpad_shiftVertically:commentHeightDelta]; @@ -615,9 +632,13 @@ NSString *const kDefaultServerType = @"google"; - (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout { // Queue a |stopModal| message to be performed in |timeout| seconds. if (timeout > 0.001) { - [NSApp performSelector:@selector(stopModal) - withObject:nil - afterDelay:timeout]; + remainingDialogTime_ = timeout; + SEL updateSelector = @selector(updateSecondsLeftInDialogDisplay:); + 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 @@ -625,12 +646,6 @@ NSString *const kDefaultServerType = @"google"; [NSApp activateIgnoringOtherApps:YES]; int returnMethod = [NSApp runModalForWindow:window]; - // Cancel the pending |stopModal| message. - if (returnMethod != NSRunStoppedResponse) { - [NSObject cancelPreviousPerformRequestsWithTarget:NSApp - selector:@selector(stopModal) - object:nil]; - } return returnMethod; } @@ -667,8 +682,10 @@ NSString *const kDefaultServerType = @"google"; textView:(NSTextView*)textView doCommandBySelector:(SEL)commandSelector { BOOL result = NO; - // If the user has entered text, don't end editing on "return" - if (commandSelector == @selector(insertNewline:) + // If the user has entered text on the comment field, don't end + // editing on "return". + if (control == commentsEntryField_ && + commandSelector == @selector(insertNewline:) && [[textView string] length] > 0) { [textView insertNewlineIgnoringFieldEditor:self]; result = YES; @@ -676,6 +693,50 @@ doCommandBySelector:(SEL)commandSelector { 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 - //============================================================================= @@ -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 - //============================================================================= - (BOOL)reportIntervalElapsed { @@ -730,6 +802,11 @@ doCommandBySelector:(SEL)commandSelector { return YES; } +- (BOOL)isOnDemand { + return [[parameters_ objectForKey:@BREAKPAD_ON_DEMAND] + isEqualToString:@"YES"]; +} + - (BOOL)shouldSubmitSilently { return [[parameters_ objectForKey:@BREAKPAD_SKIP_CONFIRM] isEqualToString:@"YES"]; @@ -745,21 +822,40 @@ doCommandBySelector:(SEL)commandSelector { isEqualToString:@"YES"]; } -- (NSString*)shortCrashDialogMessage { +- (NSString*)shortDialogMessage { NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY]; if (![displayName length]) displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT]; - return [NSString stringWithFormat:NSLocalizedString(@"headerFmt", @""), - displayName]; + if ([self isOnDemand]) { + 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]; if (![vendor length]) 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 { @@ -940,6 +1036,77 @@ doCommandBySelector:(SEL)commandSelector { [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 //============================================================================= |