diff --git a/toolkit/crashreporter/client/Makefile.in b/toolkit/crashreporter/client/Makefile.in index c9fe393c052..f68354dc819 100644 --- a/toolkit/crashreporter/client/Makefile.in +++ b/toolkit/crashreporter/client/Makefile.in @@ -53,8 +53,10 @@ DIST_FILES = crashreporter.ini LOCAL_INCLUDES = -I$(srcdir)/../airbag/src +CPPSRCS = crashreporter.cpp + ifeq ($(OS_ARCH),WINNT) -CPPSRCS = crashreporter_win.cpp +CPPSRCS += crashreporter_win.cpp LIBS += \ $(DEPTH)/toolkit/airbag/airbag/src/client/windows/sender/$(LIB_PREFIX)crash_report_sender_s.$(LIB_SUFFIX) \ $(DEPTH)/toolkit/airbag/airbag/src/common/windows/$(LIB_PREFIX)breakpad_windows_common_s.$(LIB_SUFFIX) \ diff --git a/toolkit/crashreporter/client/crashreporter.cpp b/toolkit/crashreporter/client/crashreporter.cpp new file mode 100644 index 00000000000..3d09b0e8a20 --- /dev/null +++ b/toolkit/crashreporter/client/crashreporter.cpp @@ -0,0 +1,278 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Toolkit Crash Reporter + * + * The Initial Developer of the Original Code is + * Mozilla Corporation + * Portions created by the Initial Developer are Copyright (C) 2006-2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Ted Mielczarek + * Dave Camp + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "crashreporter.h" + +#include +#include + +using std::string; +using std::istream; +using std::ifstream; +using std::istringstream; +using std::ostream; +using std::ofstream; + +StringTable gStrings; +int gArgc; +const char** gArgv; + +static string gSendURL; +static string gDumpFile; +static string gExtraFile; +static string gSettingsPath; +static bool gDeleteDump = true; + + +static string kExtraDataExtension = ".extra"; + +static bool ReadStrings(istream &in, StringTable &strings) +{ + string currentSection; + while (!in.eof()) { + string line; + std::getline(in, line); + int sep = line.find('='); + if (sep >= 0) { + string key, value; + key = line.substr(0, sep); + value = line.substr(sep + 1); + strings[key] = value; + } + } + + return true; +} + +static bool ReadStringsFromFile(const string& path, + StringTable& strings) +{ + ifstream f(path.c_str(), std::ios::in); + if (!f.is_open()) return false; + + return ReadStrings(f, strings); +} + +static bool ReadConfig() +{ + string iniPath; + if (!UIGetIniPath(iniPath)) + return false; + + if (!ReadStringsFromFile(iniPath, gStrings)) + return false; + + gSendURL = gStrings["URL"]; + + string deleteSetting = gStrings["Delete"]; + gDeleteDump = deleteSetting.empty() || atoi(deleteSetting.c_str()) != 0; + + return true; +} + +static string GetExtraDataFilename(const string& dumpfile) +{ + string filename(dumpfile); + int dot = filename.rfind('.'); + if (dot < 0) + return ""; + + filename.replace(dot, filename.length() - dot, kExtraDataExtension); + return filename; +} + +static string Basename(const string& file) +{ + int slashIndex = file.rfind(UI_DIR_SEPARATOR); + if (slashIndex >= 0) + return file.substr(slashIndex + 1); + else + return file; +} + +static bool MoveCrashData(const string& toDir, + string& dumpfile, + string& extrafile) +{ + if (!UIEnsurePathExists(toDir)) { + UIError(toDir.c_str()); + return false; + } + + string newDump = toDir + UI_DIR_SEPARATOR + Basename(dumpfile); + string newExtra = toDir + UI_DIR_SEPARATOR + Basename(extrafile); + + if (!UIMoveFile(dumpfile, newDump) || + !UIMoveFile(extrafile, newExtra)) { + UIError(dumpfile.c_str()); + UIError(newDump.c_str()); + return false; + } + + dumpfile = newDump; + extrafile = newExtra; + + return true; +} + +static bool AddSubmittedReport(const string& serverResponse) +{ + StringTable responseItems; + istringstream in(serverResponse); + ReadStrings(in, responseItems); + + if (responseItems.find("CrashID") == responseItems.end()) + return false; + + string submittedDir = + gSettingsPath + UI_DIR_SEPARATOR + "submitted"; + if (!UIEnsurePathExists(submittedDir)) { + return false; + } + + string path = submittedDir + UI_DIR_SEPARATOR + + responseItems["CrashID"] + ".txt"; + + ofstream file(path.c_str()); + if (!file.is_open()) + return false; + + char buf[1024]; + UI_SNPRINTF(buf, 1024, + gStrings["CrashID"].c_str(), + responseItems["CrashID"].c_str()); + file << buf << "\n"; + + if (responseItems.find("ViewURL") != responseItems.end()) { + UI_SNPRINTF(buf, 1024, + gStrings["ViewURL"].c_str(), + responseItems["ViewURL"].c_str()); + file << buf << "\n"; + } + + file.close(); + + return true; + +} + +bool CrashReporterSendCompleted(bool success, + const string& serverResponse) +{ + if (success) { + if (gDeleteDump) { + if (!gDumpFile.empty()) + UIDeleteFile(gDumpFile); + if (!gExtraFile.empty()) + UIDeleteFile(gExtraFile); + } + + return AddSubmittedReport(serverResponse); + } + return true; +} + +int main(int argc, const char** argv) +{ + gArgc = argc; + gArgv = argv; + + if (!ReadConfig()) { + UIError("Couldn't read configuration"); + return 0; + } + + if (!UIInit()) + return 0; + + if (argc > 1) { + gDumpFile = argv[1]; + } + + if (gDumpFile.empty()) { + // no dump file specified, run the default UI + UIShowDefaultUI(); + } else { + gExtraFile = GetExtraDataFilename(gDumpFile); + if (gExtraFile.empty()) { + UIError("Couldn't get extra data filename"); + return 0; + } + + StringTable queryParameters; + if (!ReadStringsFromFile(gExtraFile, queryParameters)) { + UIError("Couldn't read extra data"); + return 0; + } + + if (queryParameters.find("ProductName") == queryParameters.end()) { + UIError("No product name specified"); + return 0; + } + + string product = queryParameters["ProductName"]; + string vendor = queryParameters["Vendor"]; + if (!UIGetSettingsPath(vendor, product, gSettingsPath)) { + UIError("Couldn't get settings path"); + return 0; + } + + string pendingDir = gSettingsPath + UI_DIR_SEPARATOR + "pending"; + if (!MoveCrashData(pendingDir, gDumpFile, gExtraFile)) { + UIError("Couldn't move crash data"); + return 0; + } + + UIShowCrashUI(gDumpFile, queryParameters, gSendURL); + } + + UIShutdown(); + + return 0; +} + +#if defined(XP_WIN) && !defined(__GNUC__) +// We need WinMain in order to not be a console app. This function is unused +// if we are a console application. +int WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR args, int ) +{ + // Do the real work. + return main(__argc, (const char**)__argv); +} +#endif diff --git a/toolkit/crashreporter/client/crashreporter.h b/toolkit/crashreporter/client/crashreporter.h new file mode 100644 index 00000000000..8b97b4524c5 --- /dev/null +++ b/toolkit/crashreporter/client/crashreporter.h @@ -0,0 +1,74 @@ +#ifndef CRASHREPORTER_H__ +#define CRASHREPORTER_H__ + +#include +#include +#include +#include + +#if defined(XP_WIN32) + +#include +#define UI_SNPRINTF _snprintf +#define UI_DIR_SEPARATOR "\\" + +#else + +#define UI_SNPRINTF snprintf +#define UI_DIR_SEPARATOR "/" + +#endif + +typedef std::map StringTable; + +#define ST_OK "Ok" +#define ST_CANCEL "Cancel" +#define ST_SEND "Send" +#define ST_DONTSEND "DontSend" +#define ST_CLOSE "Close" +#define ST_CRASHREPORTERTITLE "CrashReporterTitle" +#define ST_CRASHREPORTERDESCRIPTION "CrashReporterDescription" +#define ST_RADIOENABLE "RadioEnable" +#define ST_RADIODISABLE "RadioDisable" +#define ST_SENDTITLE "SendTitle" +#define ST_SUBMITSUCCESS "SubmitSuccess" +#define ST_SUBMITFAILED "SubmitFailed" + +//============================================================================= +// implemented in crashreporter.cpp +//============================================================================= + +extern StringTable gStrings; +extern int gArgc; +extern const char** gArgv; + +// The UI finished sending the report +bool CrashReporterSendCompleted(bool success, + const std::string& serverResponse); + +//============================================================================= +// implemented in the platform-specific files +//============================================================================= + +bool UIInit(); +void UIShutdown(); + +// Run the UI for when the app was launched without a dump file +void UIShowDefaultUI(); + +// Run the UI for when the app was launched with a dump file +void UIShowCrashUI(const std::string& dumpfile, + const StringTable& queryParameters, + const std::string& sendURL); + +void UIError(const std::string& message); + +bool UIGetIniPath(std::string& path); +bool UIGetSettingsPath(const std::string& vendor, + const std::string& product, + std::string& settingsPath); +bool UIEnsurePathExists(const std::string& path); +bool UIMoveFile(const std::string& oldfile, const std::string& newfile); +bool UIDeleteFile(const std::string& oldfile); + +#endif diff --git a/toolkit/crashreporter/client/crashreporter_osx.h b/toolkit/crashreporter/client/crashreporter_osx.h index 4271c1c5ef5..66486803b5b 100644 --- a/toolkit/crashreporter/client/crashreporter_osx.h +++ b/toolkit/crashreporter/client/crashreporter_osx.h @@ -41,34 +41,48 @@ #include #include "HTTPMultipartUpload.h" +#include "crashreporter.h" @interface CrashReporterUI : NSObject { - /* Enabled view */ - IBOutlet NSView *enableView; + IBOutlet NSWindow* window; - IBOutlet NSTextField *descriptionLabel; - IBOutlet NSButton *disableReportingButton; - IBOutlet NSButton *dontSendButton; - IBOutlet NSButton *sendButton; + /* Enabled view */ + IBOutlet NSView* enableView; + + IBOutlet NSTextField* descriptionLabel; + IBOutlet NSButton* disableReportingButton; + IBOutlet NSButton* dontSendButton; + IBOutlet NSButton* sendButton; /* Upload progress view */ - IBOutlet NSView *uploadingView; + IBOutlet NSView* uploadingView; - IBOutlet NSTextField *progressLabel; - IBOutlet NSProgressIndicator *progressBar; - IBOutlet NSButton *closeButton; + IBOutlet NSTextField* progressLabel; + IBOutlet NSProgressIndicator* progressBar; + IBOutlet NSButton* closeButton; + + /* Error view */ + IBOutlet NSView* errorView; + IBOutlet NSTextField* errorLabel; + IBOutlet NSButton* errorCloseButton; HTTPMultipartUpload *mPost; } +- (void)showDefaultUI; +- (void)showCrashUI:(const std::string&)dumpfile + queryParameters:(const StringTable&)queryParameters + sendURL:(const std::string&)sendURL; +- (void)showErrorUI:(const std::string&)dumpfile; + - (IBAction)closeClicked:(id)sender; - (IBAction)sendClicked:(id)sender; - (void)setView:(NSWindow *)w newView: (NSView *)v animate: (BOOL) animate; -- (void)setupPost; +- (bool)setupPost; - (void)uploadThread:(id)post; -- (void)uploadComplete:(id)error; +- (void)uploadComplete:(id)data; @end diff --git a/toolkit/crashreporter/client/crashreporter_osx.mm b/toolkit/crashreporter/client/crashreporter_osx.mm index 959aa606af3..095dd7be347 100644 --- a/toolkit/crashreporter/client/crashreporter_osx.mm +++ b/toolkit/crashreporter/client/crashreporter_osx.mm @@ -37,91 +37,79 @@ * ***** END LICENSE BLOCK ***** */ #import +#import +#include "crashreporter.h" #include "crashreporter_osx.h" - -#include -#include -#include +#include +#include +#include using std::string; -using std::map; -using std::ifstream; -typedef map StringTable; -typedef map StringTableSections; +static NSAutoreleasePool* gMainPool; +static CrashReporterUI* gUI = 0; +static string gDumpFile; +static StringTable gQueryParameters; +static string gSendURL; -static string kExtraDataExtension = ".extra"; -static string gMinidumpPath; -static string gExtraDataPath; -static StringTableSections gStrings; -static StringTable gExtraData; - -static BOOL gSendFailed; +#define NSSTR(s) [NSString stringWithUTF8String:(s).c_str()] static NSString * -Str(const char *aName, const char *aSection="Strings") +Str(const char* aName) { - string str = gStrings[aSection][aName]; + string str = gStrings[aName]; if (str.empty()) str = "?"; - return [NSString stringWithUTF8String:str.c_str()]; -} - -static bool -ReadStrings(const string &aPath, StringTableSections *aSections) -{ - StringTableSections §ions = *aSections; - - ifstream f(aPath.c_str()); - if (!f.is_open()) return false; - - string currentSection; - while (!f.eof()) { - string line; - std::getline(f, line); - if (line[0] == ';') continue; - - if (line[0] == '[') { - int close = line.find(']'); - if (close >= 0) - currentSection = line.substr(1, close - 1); - continue; - } - - int sep = line.find('='); - if (sep >= 0) - sections[currentSection][line.substr(0, sep)] = line.substr(sep + 1); - } - - return f.eof(); + return NSSTR(str); } @implementation CrashReporterUI -(void)awakeFromNib { - NSWindow *w = [descriptionLabel window]; - [w center]; + gUI = self; + [window center]; - [w setTitle:[[NSBundle mainBundle] + [window setTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]]; - [descriptionLabel setStringValue:Str("CrashReporterDescription")]; - [disableReportingButton setTitle:Str("RadioDisable")]; + [descriptionLabel setStringValue:Str(ST_CRASHREPORTERDESCRIPTION)]; + [disableReportingButton setTitle:Str(ST_RADIODISABLE)]; - if (!gMinidumpPath.empty()) { - [sendButton setTitle:Str("Send")]; - [sendButton setKeyEquivalent:@"\r"]; - [dontSendButton setTitle:Str("DontSend")]; - } else { - [dontSendButton setFrame:[sendButton frame]]; - [dontSendButton setTitle:Str("Close")]; - [dontSendButton setKeyEquivalent:@"\r"]; - [sendButton removeFromSuperview]; - } + [closeButton setTitle:Str(ST_CLOSE)]; + [errorCloseButton setTitle:Str(ST_CLOSE)]; +} - [closeButton setTitle:Str("Close")]; +-(void)showDefaultUI +{ + [dontSendButton setFrame:[sendButton frame]]; + [dontSendButton setTitle:Str(ST_CLOSE)]; + [dontSendButton setKeyEquivalent:@"\r"]; + [sendButton removeFromSuperview]; - [w makeKeyAndOrderFront:nil]; + [window makeKeyAndOrderFront:nil]; +} + +-(void)showCrashUI:(const string&)dumpfile + queryParameters:(const StringTable&)queryParameters + sendURL:(const string&)sendURL +{ + gDumpFile = dumpfile; + gQueryParameters = queryParameters; + gSendURL = sendURL; + + [sendButton setTitle:Str(ST_SEND)]; + [sendButton setKeyEquivalent:@"\r"]; + [dontSendButton setTitle:Str(ST_DONTSEND)]; + + [window makeKeyAndOrderFront:nil]; +} + +-(void)showErrorUI:(const string&)message +{ + [errorLabel setStringValue: NSSTR(message)]; + + [self setView: window newView: errorView animate: NO]; + [window makeKeyAndOrderFront:nil]; } -(IBAction)closeClicked:(id)sender @@ -131,29 +119,24 @@ ReadStrings(const string &aPath, StringTableSections *aSections) -(IBAction)sendClicked:(id)sender { - NSWindow *w = [descriptionLabel window]; - + [self setView: window newView: uploadingView animate: YES]; [progressBar startAnimation: self]; - [progressLabel setStringValue:Str("SendTitle")]; + [progressLabel setStringValue:Str(ST_SENDTITLE)]; - [self setupPost]; + if (![self setupPost]) + [NSApp terminate]; - if (mPost) { - [self setView: w newView: uploadingView animate: YES]; - [progressBar startAnimation: self]; - - [NSThread detachNewThreadSelector:@selector(uploadThread:) - toTarget:self - withObject:nil]; - } + [NSThread detachNewThreadSelector:@selector(uploadThread:) + toTarget:self + withObject:mPost]; } --(void)setView:(NSWindow *)w newView: (NSView *)v animate: (BOOL)animate +-(void)setView:(NSWindow*)w newView: (NSView*)v animate: (BOOL)animate { NSRect frame = [w frame]; NSRect oldViewFrame = [[w contentView] frame]; - NSRect newViewFrame = [uploadingView frame]; + NSRect newViewFrame = [v frame]; frame.origin.y += oldViewFrame.size.height - newViewFrame.size.height; frame.size.height -= oldViewFrame.size.height - newViewFrame.size.height; @@ -165,109 +148,181 @@ ReadStrings(const string &aPath, StringTableSections *aSections) [w setFrame: frame display: true animate: animate]; } --(void)setupPost +-(bool)setupPost { - NSURL *url = [NSURL URLWithString:Str("URL", "Settings")]; - if (!url) return; + NSURL* url = [NSURL URLWithString:NSSTR(gSendURL)]; + if (!url) return false; mPost = [[HTTPMultipartUpload alloc] initWithURL: url]; - if (!mPost) return; + if (!mPost) return false; - NSMutableDictionary *parameters = - [[NSMutableDictionary alloc] initWithCapacity: gExtraData.size()]; + NSMutableDictionary* parameters = + [[NSMutableDictionary alloc] initWithCapacity: gQueryParameters.size()]; + if (!parameters) return false; - StringTable::const_iterator end = gExtraData.end(); - for (StringTable::const_iterator i = gExtraData.begin(); i != end; i++) { - NSString *key = [NSString stringWithUTF8String: i->first.c_str()]; - NSString *value = [NSString stringWithUTF8String: i->second.c_str()]; + StringTable::const_iterator end = gQueryParameters.end(); + for (StringTable::const_iterator i = gQueryParameters.begin(); + i != end; + i++) { + NSString* key = NSSTR(i->first); + NSString* value = NSSTR(i->second); [parameters setObject: value forKey: key]; } + [mPost addFileAtPath: NSSTR(gDumpFile) name: @"upload_file_minidump"]; [mPost setParameters: parameters]; - [mPost addFileAtPath: [NSString stringWithUTF8String: gMinidumpPath.c_str()] - name: @"upload_file_minidump"]; + return true; } --(void)uploadComplete:(id)error +-(void)uploadComplete:(id)data { [progressBar stopAnimation: self]; - NSHTTPURLResponse *response = [mPost response]; + NSHTTPURLResponse* response = [mPost response]; - NSString *status; - if (error || !response || [response statusCode] != 200) { - status = Str("SubmitFailed"); - gSendFailed = YES; - } else - status = Str("SubmitSuccess"); + NSString* status; + bool success; + string reply; + if (!data || !response || [response statusCode] != 200) { + status = Str(ST_SUBMITFAILED); + success = false; + reply = ""; + } else { + status = Str(ST_SUBMITSUCCESS); + success = true; + + NSString* encodingName = [response textEncodingName]; + NSStringEncoding encoding; + if (encodingName) { + encoding = CFStringConvertEncodingToNSStringEncoding( + CFStringConvertIANACharSetNameToEncoding((CFStringRef)encodingName)); + } else { + encoding = NSISOLatin1StringEncoding; + } + NSString* r = [[NSString alloc] initWithData: data encoding: encoding]; + reply = [r UTF8String]; + } [progressLabel setStringValue: status]; [closeButton setEnabled: true]; [closeButton setKeyEquivalent:@"\r"]; + + CrashReporterSendCompleted(success, reply); } -(void)uploadThread:(id)post { - NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; - NSError *error = nil; - [mPost send: &error]; + NSAutoreleasePool* autoreleasepool = [[NSAutoreleasePool alloc] init]; + NSError* error = nil; + NSData* data = [post send: &error]; + if (error) + data = nil; [self performSelectorOnMainThread: @selector(uploadComplete:) - withObject: error - waitUntilDone: nil]; + withObject: data + waitUntilDone: YES]; [autoreleasepool release]; } @end -string -GetExtraDataFilename(const string& dumpfile) -{ - string filename(dumpfile); - int dot = filename.rfind('.'); - if (dot < 0) - return ""; +/* === Crashreporter UI Functions === */ - filename.replace(dot, filename.length() - dot, kExtraDataExtension); - return filename; +bool UIInit() +{ + gMainPool = [[NSAutoreleasePool alloc] init]; + [NSApplication sharedApplication]; + [NSBundle loadNibNamed:@"MainMenu" owner:NSApp]; + + return true; } -int -main(int argc, char *argv[]) +void UIShutdown() { - NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; - string iniPath(argv[0]); - iniPath.append(".ini"); - if (!ReadStrings(iniPath, &gStrings)) { - printf("couldn't read strings\n"); - return -1; - } - - if (argc > 1) { - gMinidumpPath = argv[1]; - gExtraDataPath = GetExtraDataFilename(gMinidumpPath); - if (!gExtraDataPath.empty()) { - StringTableSections table; - ReadStrings(gExtraDataPath, &table); - gExtraData = table[""]; - } - } - - gSendFailed = NO; - - int ret = NSApplicationMain(argc, (const char **) argv); - - string deleteSetting = gStrings["Settings"]["Delete"]; - if (!gSendFailed && - (deleteSetting.empty() || atoi(deleteSetting.c_str()) > 0)) { - remove(gMinidumpPath.c_str()); - if (!gExtraDataPath.empty()) - remove(gExtraDataPath.c_str()); - } - - [autoreleasepool release]; - - return ret; + [gMainPool release]; +} + +void UIShowDefaultUI() +{ + [gUI showDefaultUI]; + [NSApp run]; +} + +void UIShowCrashUI(const string& dumpfile, + const StringTable& queryParameters, + const string& sendURL) +{ + [gUI showCrashUI: dumpfile + queryParameters: queryParameters + sendURL: sendURL]; + [NSApp run]; +} + +void UIError(const string& message) +{ + if (!gUI) { + // UI failed to initialize, printing is the best we can do + printf("Error: %s\n", message.c_str()); + return; + } + + [gUI showErrorUI: message]; + [NSApp run]; +} + +bool UIGetIniPath(string& path) +{ + path = gArgv[0]; + path.append(".ini"); + + return true; +} + +bool UIGetSettingsPath(const string& vendor, + const string& product, + string& settingsPath) +{ + NSArray* paths; + paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, + NSUserDomainMask, + YES); + if ([paths count] < 1) + return false; + + NSString* destPath = [paths objectAtIndex:0]; + + // Note that MacOS ignores the vendor when creating the profile hierarchy - + // all application preferences directories live alongside one another in + // ~/Library/Application Support/ + destPath = [destPath stringByAppendingPathComponent: NSSTR(product)]; + destPath = [destPath stringByAppendingPathComponent: @"Crash Reports"]; + + settingsPath = [destPath UTF8String]; + + if (!UIEnsurePathExists(settingsPath)) + return false; + + return true; +} + +bool UIEnsurePathExists(const string& path) +{ + int ret = mkdir(path.c_str(), S_IRWXU); + int e = errno; + if (ret == -1 && e != EEXIST) + return false; + + return true; +} + +bool UIMoveFile(const string& file, const string& newfile) +{ + return (rename(file.c_str(), newfile.c_str()) != -1); +} + +bool UIDeleteFile(const string& file) +{ + return (unlink(file.c_str()) != -1); } diff --git a/toolkit/crashreporter/client/crashreporter_win.cpp b/toolkit/crashreporter/client/crashreporter_win.cpp index 5f4b1eba822..d135c6c9f28 100644 --- a/toolkit/crashreporter/client/crashreporter_win.cpp +++ b/toolkit/crashreporter/client/crashreporter_win.cpp @@ -39,6 +39,8 @@ #undef WIN32_LEAN_AND_MEAN #endif +#include "crashreporter.h" + #include #include #include @@ -48,7 +50,6 @@ #include "resource.h" #include "client/windows/sender/crash_report_sender.h" #include "common/windows/string_utils-inl.h" -#include #define CRASH_REPORTER_KEY L"Software\\Mozilla\\Crash Reporter" #define CRASH_REPORTER_VALUE L"Enabled" @@ -58,68 +59,25 @@ using std::string; using std::wstring; using std::map; -using std::ifstream; -using std::ofstream; - -bool ReadConfig(); -BOOL CALLBACK EnableDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, - LPARAM lParam); -BOOL CALLBACK SendDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, - LPARAM lParam); -HANDLE CreateSendThread(HWND hDlg, LPCTSTR dumpFile); -bool CheckCrashReporterEnabled(bool* enabled); -void SetCrashReporterEnabled(bool enabled); -bool SendCrashReport(wstring dumpFile, - const map* query_parameters, - wstring* server_response); -DWORD WINAPI SendThreadProc(LPVOID param); +static wstring UTF8ToWide(const string& utf8, bool *success = 0); +static string WideToUTF8(const wstring& wide, bool *success = 0); +static DWORD WINAPI SendThreadProc(LPVOID param); typedef struct { HWND hDlg; wstring dumpFile; - const map* query_parameters; + const StringTable* query_parameters; + wstring send_url; wstring* server_response; } SENDTHREADDATA; -TCHAR sendURL[2048] = L"\0"; -bool deleteDump = true; +static wstring Str(const char* key) +{ + return UTF8ToWide(gStrings[key]); +} -// Sort of a hack to get l10n -enum { - ST_OK, - ST_CANCEL, - ST_CRASHREPORTERTITLE, - ST_CRASHREPORTERDESCRIPTION, - ST_RADIOENABLE, - ST_RADIODISABLE, - ST_SENDTITLE, - ST_SUBMITSUCCESS, - ST_SUBMITFAILED, - ST_CRASHID, - ST_CRASHDETAILSURL, - NUM_STRINGS -}; - -LPCTSTR stringNames[] = { - L"Ok", - L"Cancel", - L"CrashReporterTitle", - L"CrashReporterDescription", - L"RadioEnable", - L"RadioDisable", - L"SendTitle", - L"SubmitSuccess", - L"SubmitFailed", - L"CrashID", - L"CrashDetailsURL" -}; - -LPTSTR strings[NUM_STRINGS]; - -const wchar_t* kExtraDataExtension = L".extra"; - -void DoInitCommonControls() +static void DoInitCommonControls() { INITCOMMONCONTROLSEX ic; ic.dwSize = sizeof(INITCOMMONCONTROLSEX); @@ -129,77 +87,15 @@ void DoInitCommonControls() LoadLibrary(L"riched20.dll"); } -bool LoadStrings(LPCTSTR fileName) -{ - for (int i=ST_OK; i 1024... - } - return true; -} - -bool GetSettingsPath(wstring vendor, wstring product, - wstring& settings_path) -{ - wchar_t path[MAX_PATH]; - if(SUCCEEDED(SHGetFolderPath(NULL, - CSIDL_APPDATA, - NULL, - 0, - path))) { - if (!vendor.empty()) { - PathAppend(path, vendor.c_str()); - } - PathAppend(path, product.c_str()); - PathAppend(path, L"Crash Reports"); - // in case it doesn't exist - CreateDirectory(path, NULL); - settings_path = path; - return true; - } - return false; -} - -bool EnsurePathExists(wstring base_path, wstring sub_path) -{ - wstring path = base_path + L"\\" + sub_path; - return CreateDirectory(path.c_str(), NULL) != 0; -} - -bool ReadConfig() -{ - TCHAR fileName[MAX_PATH]; - - if (GetModuleFileName(NULL, fileName, MAX_PATH)) { - // get crashreporter ini - LPTSTR s = wcsrchr(fileName, '.'); - if (s) { - wcscpy(s, L".ini"); - - GetPrivateProfileString(L"Settings", L"URL", L"", sendURL, 2048, fileName); - - TCHAR tmp[16]; - GetPrivateProfileString(L"Settings", L"Delete", L"1", tmp, 16, fileName); - deleteDump = _wtoi(tmp) > 0; - - return LoadStrings(fileName); - } - } - return false; -} - -BOOL CALLBACK EnableDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) +static BOOL CALLBACK EnableDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: - SetWindowText(hwndDlg, strings[ST_CRASHREPORTERTITLE]); - SetDlgItemText(hwndDlg, IDOK, strings[ST_OK]); - SetDlgItemText(hwndDlg, IDC_RADIOENABLE, strings[ST_RADIOENABLE]); - SetDlgItemText(hwndDlg, IDC_RADIODISABLE, strings[ST_RADIODISABLE]); - SetDlgItemText(hwndDlg, IDC_DESCRIPTIONTEXT, strings[ST_CRASHREPORTERDESCRIPTION]); + SetWindowText(hwndDlg, Str(ST_CRASHREPORTERTITLE).c_str()); + SetDlgItemText(hwndDlg, IDOK, Str(ST_OK).c_str()); + SetDlgItemText(hwndDlg, IDC_RADIOENABLE, Str(ST_RADIOENABLE).c_str()); + SetDlgItemText(hwndDlg, IDC_RADIODISABLE, Str(ST_RADIODISABLE).c_str()); + SetDlgItemText(hwndDlg, IDC_DESCRIPTIONTEXT, Str(ST_CRASHREPORTERDESCRIPTION).c_str()); SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETTARGETDEVICE, (WPARAM)NULL, 0); SetFocus(GetDlgItem(hwndDlg, IDC_RADIOENABLE)); CheckRadioButton(hwndDlg, IDC_RADIOENABLE, IDC_RADIODISABLE, lParam ? IDC_RADIOENABLE : IDC_RADIODISABLE); @@ -218,7 +114,7 @@ BOOL CALLBACK EnableDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM } } -bool GetRegValue(HKEY hRegKey, LPCTSTR valueName, DWORD* value) +static bool GetRegValue(HKEY hRegKey, LPCTSTR valueName, DWORD* value) { DWORD type, dataSize; dataSize = sizeof(DWORD); @@ -229,7 +125,7 @@ bool GetRegValue(HKEY hRegKey, LPCTSTR valueName, DWORD* value) return false; } -bool CheckCrashReporterEnabled(bool* enabled) +static bool CheckCrashReporterEnabled(bool* enabled) { *enabled = false; bool found = false; @@ -263,7 +159,7 @@ bool CheckCrashReporterEnabled(bool* enabled) return found; } -void SetCrashReporterEnabled(bool enabled) +static void SetCrashReporterEnabled(bool enabled) { HKEY hRegKey; if (RegCreateKey(HKEY_CURRENT_USER, CRASH_REPORTER_KEY, &hRegKey) == ERROR_SUCCESS) { @@ -273,7 +169,7 @@ void SetCrashReporterEnabled(bool enabled) } } -BOOL CALLBACK SendDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) +static BOOL CALLBACK SendDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) { static bool finishedOk = false; static HANDLE hThread = NULL; @@ -282,8 +178,8 @@ BOOL CALLBACK SendDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM l case WM_INITDIALOG: { //init strings - SetWindowText(hwndDlg, strings[ST_SENDTITLE]); - SetDlgItemText(hwndDlg, IDCANCEL, strings[ST_CANCEL]); + SetWindowText(hwndDlg, Str(ST_SENDTITLE).c_str()); + SetDlgItemText(hwndDlg, IDCANCEL, Str(ST_CANCEL).c_str()); // init progressmeter SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETRANGE, 0, MAKELPARAM(0, 100)); SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETPOS, 0, 0); @@ -299,10 +195,16 @@ BOOL CALLBACK SendDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM l finishedOk = (wParam == 1); if (finishedOk) { SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETPOS, 100, 0); - MessageBox(hwndDlg, strings[ST_SUBMITSUCCESS], strings[ST_CRASHREPORTERTITLE], MB_OK | MB_ICONINFORMATION); + MessageBox(hwndDlg, + Str(ST_SUBMITSUCCESS).c_str(), + Str(ST_CRASHREPORTERTITLE).c_str(), + MB_OK | MB_ICONINFORMATION); } else { - MessageBox(hwndDlg, strings[ST_SUBMITFAILED], strings[ST_CRASHREPORTERTITLE], MB_OK | MB_ICONERROR); + MessageBox(hwndDlg, + Str(ST_SUBMITFAILED).c_str(), + Str(ST_CRASHREPORTERTITLE).c_str(), + MB_OK | MB_ICONERROR); } EndDialog(hwndDlg, finishedOk ? 1 : 0); return TRUE; @@ -318,14 +220,16 @@ BOOL CALLBACK SendDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM l } } -bool SendCrashReport(wstring dumpFile, - const map* query_parameters, - wstring* server_response) +static bool SendCrashReport(wstring dumpFile, + const StringTable* query_parameters, + wstring send_url, + wstring* server_response) { SENDTHREADDATA td; td.hDlg = NULL; td.dumpFile = dumpFile; td.query_parameters = query_parameters; + td.send_url = send_url; td.server_response = server_response; int res = (int)DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_SENDDIALOG), NULL, @@ -334,20 +238,26 @@ bool SendCrashReport(wstring dumpFile, return (res >= 0); } -DWORD WINAPI SendThreadProc(LPVOID param) +static DWORD WINAPI SendThreadProc(LPVOID param) { bool finishedOk; SENDTHREADDATA* td = (SENDTHREADDATA*)param; - wstring url(sendURL); - - if (url.empty()) { + if (td->send_url.empty()) { finishedOk = false; } else { + mapquery_parameters; + StringTable::const_iterator end = td->query_parameters->end(); + for (StringTable::const_iterator i = td->query_parameters->begin(); + i != end; + i++) { + query_parameters[UTF8ToWide(i->first)] = UTF8ToWide(i->second); + } + finishedOk = (google_breakpad::CrashReportSender - ::SendCrashReport(url, - *(td->query_parameters), + ::SendCrashReport(td->send_url, + query_parameters, td->dumpFile, td->server_response) == google_breakpad::RESULT_SUCCEEDED); @@ -357,243 +267,173 @@ DWORD WINAPI SendThreadProc(LPVOID param) return 0; } -bool ConvertUTF8ToWide(const char* utf8_string, wstring& ucs2_string) +static wstring UTF8ToWide(const string& utf8, bool *success) { wchar_t* buffer = NULL; - int buffer_size = MultiByteToWideChar(CP_UTF8, 0, utf8_string, + int buffer_size = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, NULL, 0); - if(buffer_size == 0) - return false; + if(buffer_size == 0) { + if (success) + *success = false; + return L""; + } buffer = new wchar_t[buffer_size]; - if(buffer == NULL) - return false; - - MultiByteToWideChar(CP_UTF8, 0, utf8_string, + if(buffer == NULL) { + if (success) + *success = false; + return L""; + } + + MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, buffer, buffer_size); - ucs2_string = buffer; + wstring str = buffer; delete [] buffer; - return true; + + if (success) + *success = true; + + return str; } -bool ConvertWideToUTF8(const wchar_t* ucs2_string, string& utf8_string) +static string WideToUTF8(const wstring& wide, bool *success) { char* buffer = NULL; - int buffer_size = WideCharToMultiByte(CP_UTF8, 0, ucs2_string, + int buffer_size = WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, NULL, 0, NULL, NULL); - if(buffer_size == 0) - return false; + if(buffer_size == 0) { + if (success) + *success = false; + return ""; + } buffer = new char[buffer_size]; - if(buffer == NULL) - return false; - - WideCharToMultiByte(CP_UTF8, 0, ucs2_string, + if(buffer == NULL) { + if (success) + *success = false; + return ""; + } + + WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, buffer, buffer_size, NULL, NULL); - utf8_string = buffer; + string utf8 = buffer; delete [] buffer; - return true; + + if (success) + *success = true; + + return utf8; } -void ReadKeyValuePairs(const wstring& data, - map& values) +/* === Crashreporter UI Functions === */ + +bool UIInit() { - if (data.empty()) - return; - - string line; - int line_begin = 0; - int line_end = data.find('\n'); - while(line_end >= 0) { - int pos = data.find('=', line_begin); - if (pos >= 0 && pos < line_end) { - wstring key, value; - key = data.substr(line_begin, pos - line_begin); - value = data.substr(pos + 1, line_end - pos - 1); - values[key] = value; - } - line_begin = line_end + 1; - line_end = data.find('\n', line_begin); - } -} - -bool ReadExtraData(const wstring& filename, - map& query_parameters) -{ -#if _MSC_VER >= 1400 // MSVC 2005/8 - ifstream file; - file.open(filename.c_str(), std::ios::in); -#else // _MSC_VER >= 1400 - ifstream file(_wfopen(filename.c_str(), L"rb")); -#endif // _MSC_VER >= 1400 - if (!file.is_open()) - return false; - - wstring data; - char* buf; - file.seekg(0, std::ios::end); - int file_size = file.tellg(); - file.seekg(0, std::ios::beg); - buf = new char[file_size+1]; - if (!buf) - return false; - - file.read(buf, file_size); - buf[file_size] ='\0'; - ConvertUTF8ToWide(buf, data); - delete buf; - - ReadKeyValuePairs(data, query_parameters); - - file.close(); - return true; -} - -wstring GetExtraDataFilename(const wstring& dumpfile) -{ - wstring filename(dumpfile); - int dot = filename.rfind('.'); - if (dot < 0) - return L""; - - filename.replace(dot, filename.length() - dot, kExtraDataExtension); - return filename; -} - -bool AddSubmittedReport(wstring settings_path, wstring server_response) -{ - map response_items; - ReadKeyValuePairs(server_response, response_items); - - if (response_items.find(L"CrashID") == response_items.end()) - return false; - - wstring submitted_path = settings_path + L"\\submitted\\" + - response_items[L"CrashID"] + L".txt"; - -#if _MSC_VER >= 1400 // MSVC 2005/8 - ofstream file; - file.open(submitted_path.c_str(), std::ios::out); -#else // _MSC_VER >= 1400 - ofstream file(_wfopen(submitted_path.c_str(), L"wb")); -#endif // _MSC_VER >= 1400 - if (!file.is_open()) - return false; - - wchar_t buf[1024]; - wsprintf(buf, strings[ST_CRASHID], response_items[L"CrashID"].c_str()); - wstring data = buf; - data += L"\n"; - if (response_items.find(L"ViewURL") != response_items.end()) { - wsprintf(buf, strings[ST_CRASHDETAILSURL], - response_items[L"ViewURL"].c_str()); - data += buf; - data += L"\n"; - } - - string utf8_data; - if (!ConvertWideToUTF8(data.c_str(), utf8_data)) - return false; - - file << utf8_data; - file.close(); - return true; -} - -int main(int argc, char **argv) -{ - map query_parameters; - DoInitCommonControls(); - if (!ReadConfig()) { - MessageBox(NULL, L"Missing crashreporter.ini file", L"Crash Reporter Error", MB_OK | MB_ICONSTOP); - return 0; - } + return true; +} - wstring dumpfile; - bool enabled = false; +void UIShutdown() +{ +} - if (argc > 1) { - if (!ConvertUTF8ToWide(argv[1], dumpfile)) - return 0; - } +void UIShowDefaultUI() +{ + bool enabled; + // no dump file specified, just ask about enabling + if (!CheckCrashReporterEnabled(&enabled)) + enabled = true; - if (dumpfile.empty()) { - // no dump file specified, just ask about enabling - if (!CheckCrashReporterEnabled(&enabled)) - enabled = true; + enabled = (1 == DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_ENABLEDIALOG), NULL, (DLGPROC)EnableDialogProc, (LPARAM)enabled)); + SetCrashReporterEnabled(enabled); +} - enabled = (1 == DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_ENABLEDIALOG), NULL, (DLGPROC)EnableDialogProc, (LPARAM)enabled)); +void UIShowCrashUI(const string& dumpfile, + const StringTable& query_parameters, + const string& send_url) +{ + bool enabled; + if (!CheckCrashReporterEnabled(&enabled)) { + //ask user if crash reporter should be enabled + enabled = (1 == DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_ENABLEDIALOG), NULL, (DLGPROC)EnableDialogProc, (LPARAM)true)); SetCrashReporterEnabled(enabled); } - else { - wstring extrafile = GetExtraDataFilename(dumpfile); - if (!extrafile.empty()) { - ReadExtraData(extrafile, query_parameters); - } - else { - //XXX: print error message - return 0; - } - - if (query_parameters.find(L"ProductName") == query_parameters.end()) { - //XXX: print error message - return 0; - } - - wstring product = query_parameters[L"ProductName"]; - wstring vendor; - if (query_parameters.find(L"Vendor") != query_parameters.end()) { - vendor = query_parameters[L"Vendor"]; - } - wstring settings_path; - - if(!GetSettingsPath(vendor, product, settings_path)) { - //XXX: print error message - return 0; - } - - EnsurePathExists(settings_path, L"pending"); - EnsurePathExists(settings_path, L"submitted"); - - // now we move the crash report and extra data to pending - wstring newfile = settings_path + L"\\pending\\" + - google_breakpad::WindowsStringUtils::GetBaseName(dumpfile); - MoveFile(dumpfile.c_str(), newfile.c_str()); - dumpfile = newfile; - - newfile = settings_path + L"\\pending\\" + - google_breakpad::WindowsStringUtils::GetBaseName(extrafile); - MoveFile(extrafile.c_str(), newfile.c_str()); - extrafile = newfile; - - if (!CheckCrashReporterEnabled(&enabled)) { - //ask user if crash reporter should be enabled - enabled = (1 == DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_ENABLEDIALOG), NULL, (DLGPROC)EnableDialogProc, (LPARAM)true)); - SetCrashReporterEnabled(enabled); - } - // if enabled, send crash report - if (enabled) { - wstring server_response; - if (SendCrashReport(dumpfile, &query_parameters, &server_response)) { - if (deleteDump) { - DeleteFile(dumpfile.c_str()); - if (!extrafile.empty()) - DeleteFile(extrafile.c_str()); - } - AddSubmittedReport(settings_path, server_response); - } - //TODO: show details? - } + // if enabled, send crash report + if (enabled) { + wstring server_response; + bool success = SendCrashReport(UTF8ToWide(dumpfile), + &query_parameters, + UTF8ToWide(send_url), + &server_response); + CrashReporterSendCompleted(success, WideToUTF8(server_response)); } } -#if defined(XP_WIN) && !defined(__GNUC__) -// We need WinMain in order to not be a console app. This function is unused -// if we are a console application. -int WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR args, int ) +void UIError(const string& message) { - // Do the real work. - return main(__argc, __argv); + wstring title = Str(ST_CRASHREPORTERTITLE); + if (title.empty()) + title = L"Crash Reporter Error"; + + MessageBox(NULL, UTF8ToWide(message).c_str(), title.c_str(), + MB_OK | MB_ICONSTOP); +} + +bool UIGetIniPath(string& path) +{ + wchar_t fileName[MAX_PATH]; + if (GetModuleFileName(NULL, fileName, MAX_PATH)) { + // get crashreporter ini + wchar_t* s = wcsrchr(fileName, '.'); + if (s) { + wcscpy(s, L".ini"); + path = WideToUTF8(fileName); + return true; + } + } + + return false; +} + +bool UIGetSettingsPath(const string& vendor, + const string& product, + string& settings_path) +{ + wchar_t path[MAX_PATH]; + if(SUCCEEDED(SHGetFolderPath(NULL, + CSIDL_APPDATA, + NULL, + 0, + path))) { + if (!vendor.empty()) { + PathAppend(path, UTF8ToWide(vendor).c_str()); + } + PathAppend(path, UTF8ToWide(product).c_str()); + PathAppend(path, L"Crash Reports"); + // in case it doesn't exist + CreateDirectory(path, NULL); + settings_path = WideToUTF8(path); + return true; + } + return false; +} + +bool UIEnsurePathExists(const string& path) +{ + if (CreateDirectory(UTF8ToWide(path).c_str(), NULL) == 0) { + if (GetLastError() != ERROR_ALREADY_EXISTS) + return false; + } + + return true; +} + +bool UIMoveFile(const string& oldfile, const string& newfile) +{ + return MoveFile(UTF8ToWide(oldfile).c_str(), UTF8ToWide(newfile).c_str()); +} + +bool UIDeleteFile(const string& oldfile) +{ + return DeleteFile(UTF8ToWide(oldfile).c_str()); } -#endif diff --git a/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib index 3f5bb620adc..1dc7677285c 100644 --- a/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib +++ b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib @@ -10,10 +10,14 @@ disableReportingButton = NSButton; dontSendButton = NSButton; enableView = NSView; + errorCloseButton = NSButton; + errorLabel = NSTextField; + errorView = NSView; progressBar = NSProgressIndicator; progressLabel = NSTextField; sendButton = NSButton; uploadingView = NSView; + window = NSWindow; }; SUPERCLASS = NSObject; }, diff --git a/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib index 18dc633857e..14d5f631c2a 100644 --- a/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib +++ b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib @@ -7,11 +7,13 @@ IBEditorPositions 282 - 445 520 550 163 0 0 1440 878 + 24 681 550 163 0 0 1440 878 29 447 315 137 44 0 0 1440 878 356 643 213 551 213 0 0 1440 878 + 386 + 23 537 456 145 0 0 1440 878 IBFramework Version 446.1 @@ -24,9 +26,10 @@ 2 IBOpenObjects - 21 - 29 + 386 282 + 29 + 21 IBSystem Version 8P2137 diff --git a/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib index 5c6535a5771..46138d68736 100644 Binary files a/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib and b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib differ