bug 378581 - merge platform implementations of crash reporter client. Patch by Dave Camp <dcamp@mozilla.com>, r=me
authorted.mielczarek@gmail.com
Tue, 24 Jul 2007 18:06:03 -0700
changeset 3882 e7346ae77d75d070312c65e143ce581cb09904a4
parent 3881 9c0ad941f1aed2dbbe1f749bbbe917758f1da765
child 3883 79a76a05a4798947eedc32f24deb57a5762ad2af
push idunknown
push userunknown
push dateunknown
reviewersme
bugs378581
milestone1.9a7pre
bug 378581 - merge platform implementations of crash reporter client. Patch by Dave Camp <dcamp@mozilla.com>, r=me
toolkit/crashreporter/client/Makefile.in
toolkit/crashreporter/client/crashreporter.cpp
toolkit/crashreporter/client/crashreporter.h
toolkit/crashreporter/client/crashreporter_osx.h
toolkit/crashreporter/client/crashreporter_osx.mm
toolkit/crashreporter/client/crashreporter_win.cpp
toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib
toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
--- a/toolkit/crashreporter/client/Makefile.in
+++ b/toolkit/crashreporter/client/Makefile.in
@@ -48,18 +48,20 @@ PROGRAM = crashreporter$(BIN_SUFFIX)
 DIST_PROGRAM = crashreporter$(BIN_SUFFIX)
 REQUIRES = sender
 
 #XXX: should move to toolkit/locale
 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) \
 	$(NULL)
 LOCAL_INCLUDES += -I$(srcdir)
 RCINCLUDE = crashreporter.rc
 DEFINES += -DUNICODE -D_UNICODE
 OS_LIBS += $(call EXPAND_LIBNAME,comctl32 shell32 wininet shlwapi)
new file mode 100644
--- /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 <ted.mielczarek@gmail.com>
+ *  Dave Camp <dcamp@mozilla.com>
+ *
+ * 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 <fstream>
+#include <sstream>
+
+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
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/client/crashreporter.h
@@ -0,0 +1,74 @@
+#ifndef CRASHREPORTER_H__
+#define CRASHREPORTER_H__
+
+#include <string>
+#include <map>
+#include <stdlib.h>
+#include <stdio.h>
+
+#if defined(XP_WIN32)
+
+#include <windows.h>
+#define UI_SNPRINTF _snprintf
+#define UI_DIR_SEPARATOR "\\"
+
+#else
+
+#define UI_SNPRINTF snprintf
+#define UI_DIR_SEPARATOR "/"
+
+#endif
+
+typedef std::map<std::string, std::string> 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
--- a/toolkit/crashreporter/client/crashreporter_osx.h
+++ b/toolkit/crashreporter/client/crashreporter_osx.h
@@ -36,40 +36,54 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef CRASHREPORTER_OSX_H__
 #define CRASHREPORTER_OSX_H__
 
 #include <Cocoa/Cocoa.h>
 #include "HTTPMultipartUpload.h"
+#include "crashreporter.h"
 
 @interface CrashReporterUI : NSObject
 {
+    IBOutlet NSWindow* window;
+
     /* Enabled view */
-    IBOutlet NSView *enableView;
+    IBOutlet NSView* enableView;
 
-    IBOutlet NSTextField *descriptionLabel;
-    IBOutlet NSButton *disableReportingButton;
-    IBOutlet NSButton *dontSendButton;
-    IBOutlet NSButton *sendButton;
+    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
 
 #endif
--- a/toolkit/crashreporter/client/crashreporter_osx.mm
+++ b/toolkit/crashreporter/client/crashreporter_osx.mm
@@ -32,242 +32,297 @@
  * 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 ***** */
 
 #import <Cocoa/Cocoa.h>
+#import <CoreFoundation/CoreFoundation.h>
+#include "crashreporter.h"
 #include "crashreporter_osx.h"
-
-#include <string>
-#include <map>
-#include <fstream>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
 
 using std::string;
-using std::map;
-using std::ifstream;
-
-typedef map<string,string> StringTable;
-typedef map<string,StringTable> StringTableSections;
 
-static string               kExtraDataExtension = ".extra";
+static NSAutoreleasePool* gMainPool;
+static CrashReporterUI* gUI = 0;
+static string gDumpFile;
+static StringTable gQueryParameters;
+static string gSendURL;
 
-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")
-{
-  string str = gStrings[aSection][aName];
-  if (str.empty()) str = "?";
-  return [NSString stringWithUTF8String:str.c_str()];
-}
-
-static bool
-ReadStrings(const string &aPath, StringTableSections *aSections)
+Str(const char* aName)
 {
-  StringTableSections &sections = *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();
+  string str = gStrings[aName];
+  if (str.empty()) str = "?";
+  return NSSTR(str);
 }
 
 @implementation CrashReporterUI
 
 -(void)awakeFromNib
 {
-  NSWindow *w = [descriptionLabel window];
-  [w center];
+  gUI = self;
+  [window center];
+
+  [window setTitle:[[NSBundle mainBundle]
+                objectForInfoDictionaryKey:@"CFBundleName"]];
+  [descriptionLabel setStringValue:Str(ST_CRASHREPORTERDESCRIPTION)];
+  [disableReportingButton setTitle:Str(ST_RADIODISABLE)];
 
-  [w setTitle:[[NSBundle mainBundle]
-                objectForInfoDictionaryKey:@"CFBundleName"]];
-  [descriptionLabel setStringValue:Str("CrashReporterDescription")];
-  [disableReportingButton setTitle:Str("RadioDisable")];
+  [closeButton setTitle:Str(ST_CLOSE)];
+  [errorCloseButton setTitle:Str(ST_CLOSE)];
+}
+
+-(void)showDefaultUI
+{
+  [dontSendButton setFrame:[sendButton frame]];
+  [dontSendButton setTitle:Str(ST_CLOSE)];
+  [dontSendButton setKeyEquivalent:@"\r"];
+  [sendButton removeFromSuperview];
+
+  [window makeKeyAndOrderFront:nil];
+}
 
-  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];
-  }
+-(void)showCrashUI:(const string&)dumpfile
+   queryParameters:(const StringTable&)queryParameters
+           sendURL:(const string&)sendURL
+{
+  gDumpFile = dumpfile;
+  gQueryParameters = queryParameters;
+  gSendURL = sendURL;
 
-  [closeButton setTitle:Str("Close")];
+  [sendButton setTitle:Str(ST_SEND)];
+  [sendButton setKeyEquivalent:@"\r"];
+  [dontSendButton setTitle:Str(ST_DONTSEND)];
+
+  [window makeKeyAndOrderFront:nil];
+}
 
-  [w 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
 {
   [NSApp terminate: self];
 }
 
 -(IBAction)sendClicked:(id)sender
 {
-  NSWindow *w = [descriptionLabel window];
-
+  [self setView: window newView: uploadingView animate: YES];
   [progressBar startAnimation: self];
-  [progressLabel setStringValue:Str("SendTitle")];
-
-  [self setupPost];
+  [progressLabel setStringValue:Str(ST_SENDTITLE)];
 
-  if (mPost) {
-    [self setView: w newView: uploadingView animate: YES];
-    [progressBar startAnimation: self];
+  if (![self setupPost])
+    [NSApp terminate];
 
-    [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;
 
   frame.origin.x += oldViewFrame.size.width - newViewFrame.size.width;
   frame.size.width -= oldViewFrame.size.width - newViewFrame.size.width;
 
   [w setContentView: v];
   [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)
+/* === Crashreporter UI Functions === */
+
+bool UIInit()
 {
-  string filename(dumpfile);
-  int dot = filename.rfind('.');
-  if (dot < 0)
-    return "";
+  gMainPool = [[NSAutoreleasePool alloc] init];
+  [NSApplication sharedApplication];
+  [NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
 
-  filename.replace(dot, filename.length() - dot, kExtraDataExtension);
-  return filename;
+  return true;
+}
+
+void UIShutdown()
+{
+  [gMainPool release];
 }
 
-int
-main(int argc, char *argv[])
+void UIShowDefaultUI()
+{
+  [gUI showDefaultUI];
+  [NSApp run];
+}
+
+void UIShowCrashUI(const string& dumpfile,
+                   const StringTable& queryParameters,
+                   const string& sendURL)
 {
-  NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
-  string iniPath(argv[0]);
-  iniPath.append(".ini");
-  if (!ReadStrings(iniPath, &gStrings)) {
-    printf("couldn't read strings\n");
-    return -1;
+  [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;
   }
 
-  if (argc > 1) {
-    gMinidumpPath = argv[1];
-    gExtraDataPath = GetExtraDataFilename(gMinidumpPath);
-    if (!gExtraDataPath.empty()) {
-      StringTableSections table;
-      ReadStrings(gExtraDataPath, &table);
-      gExtraData = table[""];
-    }
-  }
+  [gUI showErrorUI: message];
+  [NSApp run];
+}
+
+bool UIGetIniPath(string& path)
+{
+  path = gArgv[0];
+  path.append(".ini");
+
+  return true;
+}
 
-  gSendFailed = NO;
+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];
 
-  int ret = NSApplicationMain(argc,  (const char **) argv);
+  // 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;
+}
 
-  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());
-  }
+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;
+}
 
-  [autoreleasepool release];
+bool UIMoveFile(const string& file, const string& newfile)
+{
+  return (rename(file.c_str(), newfile.c_str()) != -1);
+}
 
-  return ret;
+bool UIDeleteFile(const string& file)
+{
+  return (unlink(file.c_str()) != -1);
 }
--- a/toolkit/crashreporter/client/crashreporter_win.cpp
+++ b/toolkit/crashreporter/client/crashreporter_win.cpp
@@ -34,177 +34,73 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifdef WIN32_LEAN_AND_MEAN
 #undef WIN32_LEAN_AND_MEAN
 #endif
 
+#include "crashreporter.h"
+
 #include <windows.h>
 #include <commctrl.h>
 #include <richedit.h>
 #include <shellapi.h>
 #include <shlobj.h>
 #include <shlwapi.h>
 #include "resource.h"
 #include "client/windows/sender/crash_report_sender.h"
 #include "common/windows/string_utils-inl.h"
-#include <fstream>
 
 #define CRASH_REPORTER_KEY L"Software\\Mozilla\\Crash Reporter"
 #define CRASH_REPORTER_VALUE L"Enabled"
 
 #define WM_UPLOADCOMPLETE WM_APP
 
 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<wstring,wstring>* 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<wstring,wstring>* query_parameters;
+  const StringTable* query_parameters;
+  wstring send_url;
   wstring* server_response;
 } SENDTHREADDATA;
 
-TCHAR sendURL[2048] = L"\0";
-bool  deleteDump = true;
-
-// 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
-};
+static wstring Str(const char* key)
+{
+  return UTF8ToWide(gStrings[key]);
+}
 
-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);
 	ic.dwICC = ICC_PROGRESS_CLASS;
 	InitCommonControlsEx(&ic);
   // also get the rich edit control
   LoadLibrary(L"riched20.dll");
 }
 
-bool LoadStrings(LPCTSTR fileName)
-{
-  for (int i=ST_OK; i<NUM_STRINGS; i++) {
-    strings[i] = new TCHAR[1024];
-    GetPrivateProfileString(L"Strings", stringNames[i], L"", strings[i], 1024, fileName);
-    if (stringNames[i][0] == '\0')
-      return false;
-    //XXX should probably check for strings > 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);
     return FALSE;
 
   case WM_COMMAND:
     if (LOWORD(wParam) == IDOK && HIWORD(wParam) == BN_CLICKED)
       {
@@ -213,28 +109,28 @@ BOOL CALLBACK EnableDialogProc(HWND hwnd
       }
     return FALSE;
 
   default: 
     return FALSE; 
   } 
 }
 
-bool GetRegValue(HKEY hRegKey, LPCTSTR valueName, DWORD* value)
+static bool GetRegValue(HKEY hRegKey, LPCTSTR valueName, DWORD* value)
 {
 	DWORD type, dataSize;
 	dataSize = sizeof(DWORD);
 	if (RegQueryValueEx(hRegKey, valueName, NULL, &type, (LPBYTE)value, &dataSize) == ERROR_SUCCESS
 		&& type == REG_DWORD)
 		return true;
 
 	return false;
 }
 
-bool CheckCrashReporterEnabled(bool* enabled)
+static bool CheckCrashReporterEnabled(bool* enabled)
 {
   *enabled = false;
   bool found = false;
   HKEY hRegKey;
   DWORD val;
   // see if our reg key is set globally
   if (RegOpenKey(HKEY_LOCAL_MACHINE, CRASH_REPORTER_KEY, &hRegKey) == ERROR_SUCCESS)
     {
@@ -258,342 +154,286 @@ bool CheckCrashReporterEnabled(bool* ena
           RegCloseKey(hRegKey);
         }
     }
 
   // didn't find our reg key, ask user
   return found;
 }
 
-void SetCrashReporterEnabled(bool enabled)
+static void SetCrashReporterEnabled(bool enabled)
 {
   HKEY hRegKey;
   if (RegCreateKey(HKEY_CURRENT_USER, CRASH_REPORTER_KEY, &hRegKey) == ERROR_SUCCESS) {
     DWORD data = (enabled ? 1 : 0);
     RegSetValueEx(hRegKey, CRASH_REPORTER_VALUE, 0, REG_DWORD, (LPBYTE)&data, sizeof(data));
     RegCloseKey(hRegKey);
   }
 }
 
-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;
 
   switch (message) {
   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);
       // now create a thread to actually do the sending
       SENDTHREADDATA* td = (SENDTHREADDATA*)lParam;
       td->hDlg = hwndDlg;
       CreateThread(NULL, 0, SendThreadProc, td, 0, NULL);
     }
     return TRUE;
 
   case WM_UPLOADCOMPLETE:
     WaitForSingleObject(hThread, INFINITE);
     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;
 
   case WM_COMMAND:
     if (LOWORD(wParam) == IDCANCEL && HIWORD(wParam) == BN_CLICKED) {
       EndDialog(hwndDlg, finishedOk ? 1 : 0);
     }
     return TRUE;
 
   default:
     return FALSE; 
   } 
 }
 
-bool SendCrashReport(wstring dumpFile,
-                     const map<wstring,wstring>* 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,
                                 (DLGPROC)SendDialogProc, (LPARAM)&td);
 
   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 {
+    map<wstring,wstring>query_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);
   }
   PostMessage(td->hDlg, WM_UPLOADCOMPLETE, finishedOk ? 1 : 0, 0);
 
   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,
-                      -1, buffer, buffer_size);
-  ucs2_string = buffer;
-  delete [] buffer;
-  return true;
-}
+  if(buffer == NULL) {
+    if (success)
+      *success = false;
+    return L"";
+  }
 
-bool ConvertWideToUTF8(const wchar_t* ucs2_string, string& utf8_string)
-{
-  char* buffer = NULL;
-  int buffer_size = WideCharToMultiByte(CP_UTF8, 0, ucs2_string,
-                                        -1, NULL, 0, NULL, NULL);
-  if(buffer_size == 0)
-    return false;
+  MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(),
+                      -1, buffer, buffer_size);
+  wstring str = buffer;
+  delete [] buffer;
 
-  buffer = new char[buffer_size];
-  if(buffer == NULL)
-    return false;
-  
-  WideCharToMultiByte(CP_UTF8, 0, ucs2_string,
-                      -1, buffer, buffer_size, NULL, NULL);
-  utf8_string = buffer;
-  delete [] buffer;
-  return true;
+  if (success)
+    *success = true;
+
+  return str;
 }
 
-void ReadKeyValuePairs(const wstring& data,
-                       map<wstring, wstring>& values)
+static string WideToUTF8(const wstring& wide, bool *success)
 {
-  if (data.empty())
-    return;
+  char* buffer = NULL;
+  int buffer_size = WideCharToMultiByte(CP_UTF8, 0, wide.c_str(),
+                                        -1, NULL, 0, NULL, NULL);
+  if(buffer_size == 0) {
+    if (success)
+      *success = false;
+    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);
+  buffer = new char[buffer_size];
+  if(buffer == NULL) {
+    if (success)
+      *success = false;
+    return "";
   }
+
+  WideCharToMultiByte(CP_UTF8, 0, wide.c_str(),
+                      -1, buffer, buffer_size, NULL, NULL);
+  string utf8 = buffer;
+  delete [] buffer;
+
+  if (success)
+    *success = true;
+
+  return utf8;
 }
 
-bool ReadExtraData(const wstring& filename,
-                   map<wstring, wstring>& 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;
+/* === Crashreporter UI Functions === */
 
-  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();
+bool UIInit()
+{
+  DoInitCommonControls();
   return true;
 }
 
-wstring GetExtraDataFilename(const wstring& dumpfile)
+void UIShutdown()
+{
+}
+
+void UIShowDefaultUI()
+{
+  bool enabled;
+  // 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);
+}
+
+void UIShowCrashUI(const string& dumpfile,
+                   const StringTable& query_parameters,
+                   const string& send_url)
 {
-  wstring filename(dumpfile);
-  int dot = filename.rfind('.');
-  if (dot < 0)
-    return L"";
+  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);
+  }
+  // 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));
+  }
+}
 
-  filename.replace(dot, filename.length() - dot, kExtraDataExtension);
-  return filename;
+void UIError(const string& message)
+{
+  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 AddSubmittedReport(wstring settings_path, wstring server_response)
+bool UIGetIniPath(string& path)
 {
-  map<wstring, wstring> 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";
+  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;
+    }
   }
 
-  string utf8_data;
-  if (!ConvertWideToUTF8(data.c_str(), utf8_data))
-    return false;
+  return false;
+}
 
-  file << utf8_data;
-  file.close();
+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;
 }
 
-int main(int argc, char **argv)
+bool UIMoveFile(const string& oldfile, const string& newfile)
 {
-  map<wstring,wstring> query_parameters;
-
-  DoInitCommonControls();
-  if (!ReadConfig()) {
-    MessageBox(NULL, L"Missing crashreporter.ini file", L"Crash Reporter Error", MB_OK | MB_ICONSTOP);
-    return 0;
-  }
-
-  wstring dumpfile;
-  bool enabled = false;
-
-  if (argc > 1) {
-    if (!ConvertUTF8ToWide(argv[1], dumpfile))
-      return 0;
-  }
-
-  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);
-  }
-  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?
-    }
-  }
+  return MoveFile(UTF8ToWide(oldfile).c_str(), UTF8ToWide(newfile).c_str());
 }
 
-#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 )
+bool UIDeleteFile(const string& oldfile)
 {
-  // Do the real work.
-  return main(__argc, __argv);
+  return DeleteFile(UTF8ToWide(oldfile).c_str());
 }
-#endif
--- 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
@@ -5,19 +5,23 @@
             CLASS = CrashReporterUI; 
             LANGUAGE = ObjC; 
             OUTLETS = {
                 closeButton = NSButton; 
                 descriptionLabel = NSTextField; 
                 disableReportingButton = NSButton; 
                 dontSendButton = NSButton; 
                 enableView = NSView; 
+                errorCloseButton = NSButton; 
+                errorLabel = NSTextField; 
+                errorView = NSView; 
                 progressBar = NSProgressIndicator; 
                 progressLabel = NSTextField; 
                 sendButton = NSButton; 
                 uploadingView = NSView; 
+                window = NSWindow; 
             }; 
             SUPERCLASS = NSObject; 
         }, 
         {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }
     ); 
     IBVersion = 1; 
 }
\ No newline at end of file
--- 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
@@ -2,33 +2,36 @@
 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
 	<key>IBDocumentLocation</key>
 	<string>299 87 356 240 0 0 1440 878 </string>
 	<key>IBEditorPositions</key>
 	<dict>
 		<key>282</key>
-		<string>445 520 550 163 0 0 1440 878 </string>
+		<string>24 681 550 163 0 0 1440 878 </string>
 		<key>29</key>
 		<string>447 315 137 44 0 0 1440 878 </string>
 		<key>356</key>
 		<string>643 213 551 213 0 0 1440 878 </string>
+		<key>386</key>
+		<string>23 537 456 145 0 0 1440 878 </string>
 	</dict>
 	<key>IBFramework Version</key>
 	<string>446.1</string>
 	<key>IBLockedObjects</key>
 	<array>
 		<integer>282</integer>
 		<integer>356</integer>
 	</array>
 	<key>IBOldestOS</key>
 	<integer>2</integer>
 	<key>IBOpenObjects</key>
 	<array>
-		<integer>21</integer>
+		<integer>386</integer>
+		<integer>282</integer>
 		<integer>29</integer>
-		<integer>282</integer>
+		<integer>21</integer>
 	</array>
 	<key>IBSystem Version</key>
 	<string>8P2137</string>
 </dict>
 </plist>
index 5c6535a57719dc2acc1aa92a744f4ac3b3781088..46138d68736b5534c8b428c76e9187a17ae1299c
GIT binary patch
literal 10097
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