mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2024-11-24 16:25:49 +01:00
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:
parent
23c364a2b4
commit
22734848ea
@ -88,6 +88,7 @@
|
||||
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 */; };
|
||||
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 */; };
|
||||
F9C44DB30EF07288003AEBAA /* crashduringload in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAD0EF07288003AEBAA /* crashduringload */; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
@ -535,6 +537,7 @@
|
||||
F92C56A60ECE04B6009BE4BA /* sender */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F9B6309F100FF96B00D0F4AC /* goArrow.png */,
|
||||
F92C56A70ECE04C5009BE4BA /* crash_report_sender.h */,
|
||||
F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */,
|
||||
F945849C0F280E3C009A47BF /* Localizable.strings */,
|
||||
@ -813,6 +816,7 @@
|
||||
4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */,
|
||||
33880C800F9E097100817F82 /* InfoPlist.strings in Resources */,
|
||||
3329D4ED0FA16D820007BBC5 /* Breakpad.nib in Resources */,
|
||||
F9B630A0100FF96B00D0F4AC /* goArrow.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -89,6 +89,7 @@ extern "C" {
|
||||
#define BREAKPAD_PROCESS_CRASH_TIME "BreakpadProcessCrashTime"
|
||||
#define BREAKPAD_LOGFILE_KEY_PREFIX "BreakpadAppLogFile"
|
||||
#define BREAKPAD_SERVER_PARAMETER_PREFIX "BreakpadServerParameterPrefix_"
|
||||
#define BREAKPAD_ON_DEMAND "BreakpadOnDemand"
|
||||
|
||||
// Optional user-defined function to dec to decide if we should handle
|
||||
// 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
|
||||
// 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.
|
||||
//
|
||||
@ -242,6 +243,12 @@ typedef bool (*BreakpadFilterCallback)(int exception_type,
|
||||
// server without leaking Breakpad's
|
||||
// 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.
|
||||
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
|
||||
// the number of keys and their length.
|
||||
void BreakpadAddUploadParameter(BreakpadRef ref, NSString *key,
|
||||
NSString *value);
|
||||
NSString *value);
|
||||
|
||||
// This method will remove a previously-added parameter from the
|
||||
// upload parameter set.
|
||||
|
@ -409,7 +409,6 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||
NSString *timeout = [parameters objectForKey:@BREAKPAD_CONFIRM_TIMEOUT];
|
||||
NSArray *logFilePaths = [parameters objectForKey:@BREAKPAD_LOGFILES];
|
||||
NSString *logFileTailSize = [parameters objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE];
|
||||
NSString *reportEmail = [parameters objectForKey:@BREAKPAD_EMAIL];
|
||||
NSString *requestUserText =
|
||||
[parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS];
|
||||
NSString *requestEmail = [parameters objectForKey:@BREAKPAD_REQUEST_EMAIL];
|
||||
@ -451,7 +450,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||
vendor = @"Vendor not specified";
|
||||
}
|
||||
|
||||
// Normalize the values
|
||||
// Normalize the values.
|
||||
if (skipConfirm) {
|
||||
skipConfirm = [skipConfirm uppercaseString];
|
||||
|
||||
@ -504,7 +503,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||
[resourcePath stringByAppendingPathComponent:@"Inspector"];
|
||||
}
|
||||
|
||||
// Verify that there is an Inspector tool
|
||||
// Verify that there is an Inspector tool.
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:inspectorPathString]) {
|
||||
DEBUGLOG(stderr, "Cannot find Inspector tool\n");
|
||||
return false;
|
||||
@ -517,7 +516,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
||||
reporterPathString = [[NSBundle bundleWithPath:reporterPathString] executablePath];
|
||||
}
|
||||
|
||||
// Verify that there is a Reporter application
|
||||
// Verify that there is a Reporter application.
|
||||
if (![[NSFileManager defaultManager]
|
||||
fileExistsAtPath:reporterPathString]) {
|
||||
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) {
|
||||
// For each key-value pair, call BreakpadAddUploadParameter()
|
||||
NSEnumerator *keyEnumerator = [serverParameters keyEnumerator];
|
||||
@ -633,7 +627,9 @@ void Breakpad::RemoveKeyValue(NSString *key) {
|
||||
|
||||
//=============================================================================
|
||||
void Breakpad::GenerateAndSendReport() {
|
||||
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "YES");
|
||||
HandleException(0, 0, 0, mach_thread_self());
|
||||
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "NO");
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
|
@ -319,6 +319,12 @@ kern_return_t Inspector::ReadMessages() {
|
||||
printf("parameter count = %d\n", info.parameter_count);
|
||||
#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
|
||||
// we are expected to read.
|
||||
// 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) {
|
||||
KeyValueMessageData &key_value_data =
|
||||
(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);
|
||||
} else {
|
||||
@ -336,6 +350,11 @@ kern_return_t Inspector::ReadMessages() {
|
||||
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;
|
||||
@ -379,7 +398,7 @@ bool Inspector::InspectTask() {
|
||||
SetCrashTimeParameters();
|
||||
// If the client app has not specified a minidump directory,
|
||||
// use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name>
|
||||
if (0 == strlen(minidumpDirectory)) {
|
||||
if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) {
|
||||
NSArray *libraryDirectories =
|
||||
NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
|
||||
NSUserDomainMask,
|
||||
|
14
src/client/mac/sender/Breakpad.nib/classes.nib
generated
14
src/client/mac/sender/Breakpad.nib/classes.nib
generated
@ -4,6 +4,14 @@
|
||||
<dict>
|
||||
<key>IBClasses</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CLASS</key>
|
||||
<string>LengthLimitingTextField</string>
|
||||
<key>LANGUAGE</key>
|
||||
<string>ObjC</string>
|
||||
<key>SUPERCLASS</key>
|
||||
<string>NSTextField</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ACTIONS</key>
|
||||
<dict>
|
||||
@ -26,10 +34,14 @@
|
||||
<string>NSButton</string>
|
||||
<key>commentMessage_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>commentsEntryField_</key>
|
||||
<string>LengthLimitingTextField</string>
|
||||
<key>countdownLabel_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>dialogTitle_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>emailEntryField_</key>
|
||||
<string>NSView</string>
|
||||
<string>LengthLimitingTextField</string>
|
||||
<key>emailLabel_</key>
|
||||
<string>NSTextField</string>
|
||||
<key>emailMessage_</key>
|
||||
|
6
src/client/mac/sender/Breakpad.nib/info.nib
generated
6
src/client/mac/sender/Breakpad.nib/info.nib
generated
@ -3,17 +3,17 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IBFramework Version</key>
|
||||
<string>672</string>
|
||||
<string>676</string>
|
||||
<key>IBLastKnownRelativeProjectPath</key>
|
||||
<string>../Breakpad.xcodeproj</string>
|
||||
<key>IBOldestOS</key>
|
||||
<integer>5</integer>
|
||||
<key>IBOpenObjects</key>
|
||||
<array>
|
||||
<integer>2</integer>
|
||||
<integer>132</integer>
|
||||
</array>
|
||||
<key>IBSystem Version</key>
|
||||
<string>9G55</string>
|
||||
<string>9J61</string>
|
||||
<key>targetFramework</key>
|
||||
<string>IBCocoaFramework</string>
|
||||
</dict>
|
||||
|
BIN
src/client/mac/sender/Breakpad.nib/keyedobjects.nib
generated
BIN
src/client/mac/sender/Breakpad.nib/keyedobjects.nib
generated
Binary file not shown.
Binary file not shown.
@ -41,6 +41,24 @@
|
||||
extern NSString *const kGoogleServerType;
|
||||
extern NSString *const kSocorroServerType;
|
||||
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 {
|
||||
@public
|
||||
IBOutlet NSWindow *alertWindow_; // The alert window
|
||||
@ -50,26 +68,34 @@ extern NSString *const kDefaultServerType;
|
||||
IBOutlet NSBox *preEmailBox_;
|
||||
IBOutlet NSBox *emailSectionBox_;
|
||||
// Localized elements (or things that need to be moved during localization).
|
||||
IBOutlet NSTextField *dialogTitle_;
|
||||
IBOutlet NSTextField *commentMessage_;
|
||||
IBOutlet NSTextField *emailMessage_;
|
||||
IBOutlet NSTextField *emailLabel_;
|
||||
IBOutlet NSTextField *privacyLinkLabel_;
|
||||
IBOutlet NSButton *sendButton_;
|
||||
IBOutlet NSButton *cancelButton_;
|
||||
IBOutlet NSView *emailEntryField_;
|
||||
IBOutlet NSView *privacyLinkArrow_;
|
||||
IBOutlet NSTextField *dialogTitle_;
|
||||
IBOutlet NSTextField *commentMessage_;
|
||||
IBOutlet NSTextField *emailMessage_;
|
||||
IBOutlet NSTextField *emailLabel_;
|
||||
IBOutlet NSTextField *privacyLinkLabel_;
|
||||
IBOutlet NSButton *sendButton_;
|
||||
IBOutlet NSButton *cancelButton_;
|
||||
IBOutlet LengthLimitingTextField *emailEntryField_;
|
||||
IBOutlet LengthLimitingTextField *commentsEntryField_;
|
||||
IBOutlet NSTextField *countdownLabel_;
|
||||
IBOutlet NSView *privacyLinkArrow_;
|
||||
|
||||
// Text field bindings, for user input.
|
||||
NSString *commentsValue_; // Comments from the user
|
||||
NSString *emailValue_; // Email from the user
|
||||
|
||||
NSString *countdownMessage_; // Message indicating time
|
||||
// left for input.
|
||||
@private
|
||||
int configFile_; // File descriptor for config file
|
||||
NSMutableDictionary *parameters_; // Key value pairs of data (STRONG)
|
||||
NSData *minidumpContents_; // The data in the minidump (STRONG)
|
||||
NSData *logFileData_; // An NSdata for the tar,
|
||||
// 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
|
||||
// server type name to a
|
||||
// dictionary of server
|
||||
@ -107,4 +133,7 @@ extern NSString *const kDefaultServerType;
|
||||
- (NSString *)emailValue;
|
||||
- (void)setEmailValue:(NSString *)value;
|
||||
|
||||
- (NSString *)countdownMessage;
|
||||
- (void)setCountdownMessage:(NSString *)value;
|
||||
|
||||
@end
|
||||
|
@ -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
|
||||
|
||||
//=============================================================================
|
||||
|
BIN
src/client/mac/sender/goArrow.png
Normal file
BIN
src/client/mac/sender/goArrow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
@ -209,8 +209,9 @@ const mach_port_t kNoLastExceptionThread = MACH_PORT_NULL;
|
||||
@"Last exception type is not 0 for on demand");
|
||||
STAssertEquals(last_exception_code_, 0,
|
||||
@"Last exception code is not 0 for on demand");
|
||||
STAssertEquals(last_exception_thread_, (mach_port_t)0,
|
||||
@"Last exception thread is not 0 for on demand");
|
||||
STAssertEquals(last_exception_thread_, mach_thread_self(),
|
||||
@"Last exception thread is not mach_thread_self() "
|
||||
"for on demand");
|
||||
}
|
||||
|
||||
@end
|
||||
|
Loading…
Reference in New Issue
Block a user