summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/client
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /toolkit/crashreporter/client
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/crashreporter/client')
-rw-r--r--toolkit/crashreporter/client/Makefile.in20
-rw-r--r--toolkit/crashreporter/client/Throbber-small.avibin0 -> 3584 bytes
-rw-r--r--toolkit/crashreporter/client/crashreporter.cpp759
-rw-r--r--toolkit/crashreporter/client/crashreporter.exe.manifest38
-rw-r--r--toolkit/crashreporter/client/crashreporter.h158
-rw-r--r--toolkit/crashreporter/client/crashreporter.icobin0 -> 25214 bytes
-rwxr-xr-xtoolkit/crashreporter/client/crashreporter.rc148
-rw-r--r--toolkit/crashreporter/client/crashreporter_gtk_common.cpp453
-rw-r--r--toolkit/crashreporter/client/crashreporter_gtk_common.h50
-rw-r--r--toolkit/crashreporter/client/crashreporter_linux.cpp576
-rw-r--r--toolkit/crashreporter/client/crashreporter_osx.h107
-rw-r--r--toolkit/crashreporter/client/crashreporter_osx.mm922
-rw-r--r--toolkit/crashreporter/client/crashreporter_unix_common.cpp85
-rw-r--r--toolkit/crashreporter/client/crashreporter_win.cpp1568
-rw-r--r--toolkit/crashreporter/client/macbuild/Contents/Info.plist32
-rw-r--r--toolkit/crashreporter/client/macbuild/Contents/PkgInfo2
-rw-r--r--toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in8
-rw-r--r--toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib102
-rw-r--r--toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib18
-rw-r--r--toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nibbin0 -> 25518 bytes
-rw-r--r--toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenuRTL.nib/classes.nib102
-rw-r--r--toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenuRTL.nib/info.nib18
-rw-r--r--toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenuRTL.nib/keyedobjects.nibbin0 -> 27032 bytes
-rw-r--r--toolkit/crashreporter/client/macbuild/Contents/Resources/crashreporter.icnsbin0 -> 61743 bytes
-rw-r--r--toolkit/crashreporter/client/moz.build78
-rw-r--r--toolkit/crashreporter/client/resource.h37
26 files changed, 5281 insertions, 0 deletions
diff --git a/toolkit/crashreporter/client/Makefile.in b/toolkit/crashreporter/client/Makefile.in
new file mode 100644
index 000000000..88be52dff
--- /dev/null
+++ b/toolkit/crashreporter/client/Makefile.in
@@ -0,0 +1,20 @@
+# vim:set ts=8 sw=8 sts=8 noet:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+ifeq ($(OS_ARCH),WINNT)
+MOZ_WINCONSOLE = 0
+endif
+
+include $(topsrcdir)/config/rules.mk
+
+ifeq ($(OS_ARCH),Darwin)
+libs::
+ $(NSINSTALL) -D $(DIST)/bin/crashreporter.app
+ rsync -a -C --exclude '*.in' $(srcdir)/macbuild/Contents $(DIST)/bin/crashreporter.app
+ sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \
+ iconv -f UTF-8 -t UTF-16 > $(DIST)/bin/crashreporter.app/Contents/Resources/English.lproj/InfoPlist.strings
+ $(NSINSTALL) -D $(DIST)/bin/crashreporter.app/Contents/MacOS
+ $(NSINSTALL) $(DIST)/bin/crashreporter $(DIST)/bin/crashreporter.app/Contents/MacOS
+endif
diff --git a/toolkit/crashreporter/client/Throbber-small.avi b/toolkit/crashreporter/client/Throbber-small.avi
new file mode 100644
index 000000000..640ea62c0
--- /dev/null
+++ b/toolkit/crashreporter/client/Throbber-small.avi
Binary files differ
diff --git a/toolkit/crashreporter/client/crashreporter.cpp b/toolkit/crashreporter/client/crashreporter.cpp
new file mode 100644
index 000000000..10aa65dd2
--- /dev/null
+++ b/toolkit/crashreporter/client/crashreporter.cpp
@@ -0,0 +1,759 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "crashreporter.h"
+
+#ifdef _MSC_VER
+// Disable exception handler warnings.
+# pragma warning( disable : 4530 )
+#endif
+
+#include <fstream>
+#include <sstream>
+#include <memory>
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+
+using std::string;
+using std::istream;
+using std::ifstream;
+using std::istringstream;
+using std::ostringstream;
+using std::ostream;
+using std::ofstream;
+using std::vector;
+using std::auto_ptr;
+
+namespace CrashReporter {
+
+StringTable gStrings;
+string gSettingsPath;
+string gEventsPath;
+int gArgc;
+char** gArgv;
+
+enum SubmissionResult {Succeeded, Failed};
+
+static auto_ptr<ofstream> gLogStream(nullptr);
+static string gReporterDumpFile;
+static string gExtraFile;
+static string gMemoryFile;
+
+static const char kExtraDataExtension[] = ".extra";
+static const char kMemoryReportExtension[] = ".memory.json.gz";
+
+void UIError(const string& message)
+{
+ string errorMessage;
+ if (!gStrings[ST_CRASHREPORTERERROR].empty()) {
+ char buf[2048];
+ UI_SNPRINTF(buf, 2048,
+ gStrings[ST_CRASHREPORTERERROR].c_str(),
+ message.c_str());
+ errorMessage = buf;
+ } else {
+ errorMessage = message;
+ }
+
+ UIError_impl(errorMessage);
+}
+
+static string Unescape(const string& str)
+{
+ string ret;
+ for (string::const_iterator iter = str.begin();
+ iter != str.end();
+ iter++) {
+ if (*iter == '\\') {
+ iter++;
+ if (*iter == '\\'){
+ ret.push_back('\\');
+ } else if (*iter == 'n') {
+ ret.push_back('\n');
+ } else if (*iter == 't') {
+ ret.push_back('\t');
+ }
+ } else {
+ ret.push_back(*iter);
+ }
+ }
+
+ return ret;
+}
+
+static string Escape(const string& str)
+{
+ string ret;
+ for (string::const_iterator iter = str.begin();
+ iter != str.end();
+ iter++) {
+ if (*iter == '\\') {
+ ret += "\\\\";
+ } else if (*iter == '\n') {
+ ret += "\\n";
+ } else if (*iter == '\t') {
+ ret += "\\t";
+ } else {
+ ret.push_back(*iter);
+ }
+ }
+
+ return ret;
+}
+
+bool ReadStrings(istream& in, StringTable& strings, bool unescape)
+{
+ 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);
+ if (unescape)
+ value = Unescape(value);
+ strings[key] = value;
+ }
+ }
+
+ return true;
+}
+
+bool ReadStringsFromFile(const string& path,
+ StringTable& strings,
+ bool unescape)
+{
+ ifstream* f = UIOpenRead(path);
+ bool success = false;
+ if (f->is_open()) {
+ success = ReadStrings(*f, strings, unescape);
+ f->close();
+ }
+
+ delete f;
+ return success;
+}
+
+bool WriteStrings(ostream& out,
+ const string& header,
+ StringTable& strings,
+ bool escape)
+{
+ out << "[" << header << "]" << std::endl;
+ for (StringTable::iterator iter = strings.begin();
+ iter != strings.end();
+ iter++) {
+ out << iter->first << "=";
+ if (escape)
+ out << Escape(iter->second);
+ else
+ out << iter->second;
+
+ out << std::endl;
+ }
+
+ return true;
+}
+
+bool WriteStringsToFile(const string& path,
+ const string& header,
+ StringTable& strings,
+ bool escape)
+{
+ ofstream* f = UIOpenWrite(path.c_str());
+ bool success = false;
+ if (f->is_open()) {
+ success = WriteStrings(*f, header, strings, escape);
+ f->close();
+ }
+
+ delete f;
+ return success;
+}
+
+static string Basename(const string& file)
+{
+ string::size_type slashIndex = file.rfind(UI_DIR_SEPARATOR);
+ if (slashIndex != string::npos)
+ return file.substr(slashIndex + 1);
+ else
+ return file;
+}
+
+static string GetDumpLocalID()
+{
+ string localId = Basename(gReporterDumpFile);
+ string::size_type dot = localId.rfind('.');
+
+ if (dot == string::npos)
+ return "";
+
+ return localId.substr(0, dot);
+}
+
+// This appends the StackTraces entry generated by the minidump analyzer to the
+// main crash event so that it can be picked up by Firefox once it restarts
+static void AppendStackTracesToEventFile(const string& aStackTraces)
+{
+ if (gEventsPath.empty()) {
+ // If there is no path for finding the crash event, skip this step.
+ return;
+ }
+
+ string localId = GetDumpLocalID();
+ string path = gEventsPath + UI_DIR_SEPARATOR + localId;
+ ofstream* f = UIOpenWrite(path.c_str(), true);
+
+ if (f->is_open()) {
+ *f << "StackTraces=" << aStackTraces;
+ f->close();
+ }
+
+ delete f;
+}
+
+static void WriteSubmissionEvent(SubmissionResult result,
+ const string& remoteId)
+{
+ if (gEventsPath.empty()) {
+ // If there is no path for writing the submission event, skip it.
+ return;
+ }
+
+ string localId = GetDumpLocalID();
+ string fpath = gEventsPath + UI_DIR_SEPARATOR + localId + "-submission";
+ ofstream* f = UIOpenWrite(fpath.c_str(), false, true);
+ time_t tm;
+ time(&tm);
+
+ if (f->is_open()) {
+ *f << "crash.submission.1\n";
+ *f << tm << "\n";
+ *f << localId << "\n";
+ *f << (result == Succeeded ? "true" : "false") << "\n";
+ *f << remoteId;
+
+ f->close();
+ }
+
+ delete f;
+}
+
+void LogMessage(const std::string& message)
+{
+ if (gLogStream.get()) {
+ char date[64];
+ time_t tm;
+ time(&tm);
+ if (strftime(date, sizeof(date) - 1, "%c", localtime(&tm)) == 0)
+ date[0] = '\0';
+ (*gLogStream) << "[" << date << "] " << message << std::endl;
+ }
+}
+
+static void OpenLogFile()
+{
+ string logPath = gSettingsPath + UI_DIR_SEPARATOR + "submit.log";
+ gLogStream.reset(UIOpenWrite(logPath.c_str(), true));
+}
+
+static bool ReadConfig()
+{
+ string iniPath;
+ if (!UIGetIniPath(iniPath))
+ return false;
+
+ if (!ReadStringsFromFile(iniPath, gStrings, true))
+ return false;
+
+ // See if we have a string override file, if so process it
+ char* overrideEnv = getenv("MOZ_CRASHREPORTER_STRINGS_OVERRIDE");
+ if (overrideEnv && *overrideEnv && UIFileExists(overrideEnv))
+ ReadStringsFromFile(overrideEnv, gStrings, true);
+
+ return true;
+}
+
+static string
+GetAdditionalFilename(const string& dumpfile, const char* extension)
+{
+ string filename(dumpfile);
+ int dot = filename.rfind('.');
+ if (dot < 0)
+ return "";
+
+ filename.replace(dot, filename.length() - dot, extension);
+ return filename;
+}
+
+static bool MoveCrashData(const string& toDir,
+ string& dumpfile,
+ string& extrafile,
+ string& memoryfile)
+{
+ if (!UIEnsurePathExists(toDir)) {
+ UIError(gStrings[ST_ERROR_CREATEDUMPDIR]);
+ return false;
+ }
+
+ string newDump = toDir + UI_DIR_SEPARATOR + Basename(dumpfile);
+ string newExtra = toDir + UI_DIR_SEPARATOR + Basename(extrafile);
+ string newMemory = toDir + UI_DIR_SEPARATOR + Basename(memoryfile);
+
+ if (!UIMoveFile(dumpfile, newDump)) {
+ UIError(gStrings[ST_ERROR_DUMPFILEMOVE]);
+ return false;
+ }
+
+ if (!UIMoveFile(extrafile, newExtra)) {
+ UIError(gStrings[ST_ERROR_EXTRAFILEMOVE]);
+ return false;
+ }
+
+ if (!memoryfile.empty()) {
+ // Ignore errors from moving the memory file
+ if (!UIMoveFile(memoryfile, newMemory)) {
+ UIDeleteFile(memoryfile);
+ newMemory.erase();
+ }
+ memoryfile = newMemory;
+ }
+
+ dumpfile = newDump;
+ extrafile = newExtra;
+
+ return true;
+}
+
+static bool AddSubmittedReport(const string& serverResponse)
+{
+ StringTable responseItems;
+ istringstream in(serverResponse);
+ ReadStrings(in, responseItems, false);
+
+ if (responseItems.find("StopSendingReportsFor") != responseItems.end()) {
+ // server wants to tell us to stop sending reports for a certain version
+ string reportPath =
+ gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" +
+ responseItems["StopSendingReportsFor"];
+
+ ofstream* reportFile = UIOpenWrite(reportPath);
+ if (reportFile->is_open()) {
+ // don't really care about the contents
+ *reportFile << 1 << "\n";
+ reportFile->close();
+ }
+ delete reportFile;
+ }
+
+ if (responseItems.find("Discarded") != responseItems.end()) {
+ // server discarded this report... save it so the user can resubmit it
+ // manually
+ return false;
+ }
+
+ 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 = UIOpenWrite(path);
+ if (!file->is_open()) {
+ delete file;
+ 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["CrashDetailsURL"].c_str(),
+ responseItems["ViewURL"].c_str());
+ *file << buf << "\n";
+ }
+
+ file->close();
+ delete file;
+
+ WriteSubmissionEvent(Succeeded, responseItems["CrashID"]);
+ return true;
+}
+
+void DeleteDump()
+{
+ const char* noDelete = getenv("MOZ_CRASHREPORTER_NO_DELETE_DUMP");
+ if (!noDelete || *noDelete == '\0') {
+ if (!gReporterDumpFile.empty())
+ UIDeleteFile(gReporterDumpFile);
+ if (!gExtraFile.empty())
+ UIDeleteFile(gExtraFile);
+ if (!gMemoryFile.empty())
+ UIDeleteFile(gMemoryFile);
+ }
+}
+
+void SendCompleted(bool success, const string& serverResponse)
+{
+ if (success) {
+ if (AddSubmittedReport(serverResponse)) {
+ DeleteDump();
+ }
+ else {
+ string directory = gReporterDumpFile;
+ int slashpos = directory.find_last_of("/\\");
+ if (slashpos < 2)
+ return;
+ directory.resize(slashpos);
+ UIPruneSavedDumps(directory);
+ WriteSubmissionEvent(Failed, "");
+ }
+ } else {
+ WriteSubmissionEvent(Failed, "");
+ }
+}
+
+bool ShouldEnableSending()
+{
+ srand(time(0));
+ return ((rand() % 100) < MOZ_CRASHREPORTER_ENABLE_PERCENT);
+}
+
+} // namespace CrashReporter
+
+using namespace CrashReporter;
+
+void RewriteStrings(StringTable& queryParameters)
+{
+ // rewrite some UI strings with the values from the query parameters
+ string product = queryParameters["ProductName"];
+ string vendor = queryParameters["Vendor"];
+ if (vendor.empty()) {
+ // Assume Mozilla if no vendor is specified
+ vendor = "Mozilla";
+ }
+
+ char buf[4096];
+ UI_SNPRINTF(buf, sizeof(buf),
+ gStrings[ST_CRASHREPORTERVENDORTITLE].c_str(),
+ vendor.c_str());
+ gStrings[ST_CRASHREPORTERTITLE] = buf;
+
+
+ string str = gStrings[ST_CRASHREPORTERPRODUCTERROR];
+ // Only do the replacement here if the string has two
+ // format specifiers to start. Otherwise
+ // we assume it has the product name hardcoded.
+ string::size_type pos = str.find("%s");
+ if (pos != string::npos)
+ pos = str.find("%s", pos+2);
+ if (pos != string::npos) {
+ // Leave a format specifier for UIError to fill in
+ UI_SNPRINTF(buf, sizeof(buf),
+ gStrings[ST_CRASHREPORTERPRODUCTERROR].c_str(),
+ product.c_str(),
+ "%s");
+ gStrings[ST_CRASHREPORTERERROR] = buf;
+ }
+ else {
+ // product name is hardcoded
+ gStrings[ST_CRASHREPORTERERROR] = str;
+ }
+
+ UI_SNPRINTF(buf, sizeof(buf),
+ gStrings[ST_CRASHREPORTERDESCRIPTION].c_str(),
+ product.c_str());
+ gStrings[ST_CRASHREPORTERDESCRIPTION] = buf;
+
+ UI_SNPRINTF(buf, sizeof(buf),
+ gStrings[ST_CHECKSUBMIT].c_str(),
+ vendor.c_str());
+ gStrings[ST_CHECKSUBMIT] = buf;
+
+ UI_SNPRINTF(buf, sizeof(buf),
+ gStrings[ST_CHECKEMAIL].c_str(),
+ vendor.c_str());
+ gStrings[ST_CHECKEMAIL] = buf;
+
+ UI_SNPRINTF(buf, sizeof(buf),
+ gStrings[ST_RESTART].c_str(),
+ product.c_str());
+ gStrings[ST_RESTART] = buf;
+
+ UI_SNPRINTF(buf, sizeof(buf),
+ gStrings[ST_QUIT].c_str(),
+ product.c_str());
+ gStrings[ST_QUIT] = buf;
+
+ UI_SNPRINTF(buf, sizeof(buf),
+ gStrings[ST_ERROR_ENDOFLIFE].c_str(),
+ product.c_str());
+ gStrings[ST_ERROR_ENDOFLIFE] = buf;
+}
+
+bool CheckEndOfLifed(string version)
+{
+ string reportPath =
+ gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" + version;
+ return UIFileExists(reportPath);
+}
+
+#ifndef RELEASE_OR_BETA
+
+static string
+GetMinidumpAnalyzerPath()
+{
+ string path = gArgv[0];
+ size_t pos = path.rfind(UI_CRASH_REPORTER_FILENAME BIN_SUFFIX);
+ path.erase(pos);
+ path.append(UI_MINIDUMP_ANALYZER_FILENAME BIN_SUFFIX);
+
+ return path;
+}
+
+#endif
+
+int main(int argc, char** argv)
+{
+ gArgc = argc;
+ gArgv = argv;
+
+ if (!ReadConfig()) {
+ UIError("Couldn't read configuration.");
+ return 0;
+ }
+
+ if (!UIInit())
+ return 0;
+
+ if (argc > 1) {
+ gReporterDumpFile = argv[1];
+ }
+
+ if (gReporterDumpFile.empty()) {
+ // no dump file specified, run the default UI
+ UIShowDefaultUI();
+ } else {
+#ifndef RELEASE_OR_BETA
+ // start by running minidump analyzer, this is currently enabled only in
+ // nightly and aurora
+ UIRunMinidumpAnalyzer(GetMinidumpAnalyzerPath(), gReporterDumpFile);
+#endif
+
+ // go ahead with the crash reporter
+ gExtraFile = GetAdditionalFilename(gReporterDumpFile, kExtraDataExtension);
+ if (gExtraFile.empty()) {
+ UIError(gStrings[ST_ERROR_BADARGUMENTS]);
+ return 0;
+ }
+
+ if (!UIFileExists(gExtraFile)) {
+ UIError(gStrings[ST_ERROR_EXTRAFILEEXISTS]);
+ return 0;
+ }
+
+ gMemoryFile = GetAdditionalFilename(gReporterDumpFile,
+ kMemoryReportExtension);
+ if (!UIFileExists(gMemoryFile)) {
+ gMemoryFile.erase();
+ }
+
+ StringTable queryParameters;
+ if (!ReadStringsFromFile(gExtraFile, queryParameters, true)) {
+ UIError(gStrings[ST_ERROR_EXTRAFILEREAD]);
+ return 0;
+ }
+
+ if (queryParameters.find("ProductName") == queryParameters.end()) {
+ UIError(gStrings[ST_ERROR_NOPRODUCTNAME]);
+ return 0;
+ }
+
+ // There is enough information in the extra file to rewrite strings
+ // to be product specific
+ RewriteStrings(queryParameters);
+
+ if (queryParameters.find("ServerURL") == queryParameters.end()) {
+ UIError(gStrings[ST_ERROR_NOSERVERURL]);
+ return 0;
+ }
+
+ // Hopefully the settings path exists in the environment. Try that before
+ // asking the platform-specific code to guess.
+#ifdef XP_WIN32
+ static const wchar_t kDataDirKey[] = L"MOZ_CRASHREPORTER_DATA_DIRECTORY";
+ const wchar_t *settingsPath = _wgetenv(kDataDirKey);
+ if (settingsPath && *settingsPath) {
+ gSettingsPath = WideToUTF8(settingsPath);
+ }
+#else
+ static const char kDataDirKey[] = "MOZ_CRASHREPORTER_DATA_DIRECTORY";
+ const char *settingsPath = getenv(kDataDirKey);
+ if (settingsPath && *settingsPath) {
+ gSettingsPath = settingsPath;
+ }
+#endif
+ else {
+ string product = queryParameters["ProductName"];
+ string vendor = queryParameters["Vendor"];
+ if (!UIGetSettingsPath(vendor, product, gSettingsPath)) {
+ gSettingsPath.clear();
+ }
+ }
+
+ if (gSettingsPath.empty() || !UIEnsurePathExists(gSettingsPath)) {
+ UIError(gStrings[ST_ERROR_NOSETTINGSPATH]);
+ return 0;
+ }
+
+ OpenLogFile();
+
+#ifdef XP_WIN32
+ static const wchar_t kEventsDirKey[] = L"MOZ_CRASHREPORTER_EVENTS_DIRECTORY";
+ const wchar_t *eventsPath = _wgetenv(kEventsDirKey);
+ if (eventsPath && *eventsPath) {
+ gEventsPath = WideToUTF8(eventsPath);
+ }
+#else
+ static const char kEventsDirKey[] = "MOZ_CRASHREPORTER_EVENTS_DIRECTORY";
+ const char *eventsPath = getenv(kEventsDirKey);
+ if (eventsPath && *eventsPath) {
+ gEventsPath = eventsPath;
+ }
+#endif
+ else {
+ gEventsPath.clear();
+ }
+
+ // Update the crash event with stacks if they are present
+ auto stackTracesItr = queryParameters.find("StackTraces");
+ if (stackTracesItr != queryParameters.end()) {
+ AppendStackTracesToEventFile(stackTracesItr->second);
+ }
+
+ if (!UIFileExists(gReporterDumpFile)) {
+ UIError(gStrings[ST_ERROR_DUMPFILEEXISTS]);
+ return 0;
+ }
+
+ string pendingDir = gSettingsPath + UI_DIR_SEPARATOR + "pending";
+ if (!MoveCrashData(pendingDir, gReporterDumpFile, gExtraFile,
+ gMemoryFile)) {
+ return 0;
+ }
+
+ string sendURL = queryParameters["ServerURL"];
+ // we don't need to actually send this
+ queryParameters.erase("ServerURL");
+
+ queryParameters["Throttleable"] = "1";
+
+ // re-set XUL_APP_FILE for xulrunner wrapped apps
+ const char *appfile = getenv("MOZ_CRASHREPORTER_RESTART_XUL_APP_FILE");
+ if (appfile && *appfile) {
+ const char prefix[] = "XUL_APP_FILE=";
+ char *env = (char*) malloc(strlen(appfile) + strlen(prefix) + 1);
+ if (!env) {
+ UIError("Out of memory");
+ return 0;
+ }
+ strcpy(env, prefix);
+ strcat(env, appfile);
+ putenv(env);
+ free(env);
+ }
+
+ vector<string> restartArgs;
+
+ ostringstream paramName;
+ int i = 0;
+ paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++;
+ const char *param = getenv(paramName.str().c_str());
+ while (param && *param) {
+ restartArgs.push_back(param);
+
+ paramName.str("");
+ paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++;
+ param = getenv(paramName.str().c_str());
+ }
+
+ // allow override of the server url via environment variable
+ //XXX: remove this in the far future when our robot
+ // masters force everyone to use XULRunner
+ char* urlEnv = getenv("MOZ_CRASHREPORTER_URL");
+ if (urlEnv && *urlEnv) {
+ sendURL = urlEnv;
+ }
+
+ // see if this version has been end-of-lifed
+ if (queryParameters.find("Version") != queryParameters.end() &&
+ CheckEndOfLifed(queryParameters["Version"])) {
+ UIError(gStrings[ST_ERROR_ENDOFLIFE]);
+ DeleteDump();
+ return 0;
+ }
+
+ StringTable files;
+ files["upload_file_minidump"] = gReporterDumpFile;
+ if (!gMemoryFile.empty()) {
+ files["memory_report"] = gMemoryFile;
+ }
+
+ if (!UIShowCrashUI(files, queryParameters, sendURL, restartArgs))
+ DeleteDump();
+ }
+
+ UIShutdown();
+
+ return 0;
+}
+
+#if defined(XP_WIN) && !defined(__GNUC__)
+#include <windows.h>
+
+// We need WinMain in order to not be a console app. This function is unused
+// if we are a console application.
+int WINAPI wWinMain( HINSTANCE, HINSTANCE, LPWSTR args, int )
+{
+ // Remove everything except close window from the context menu
+ {
+ HKEY hkApp;
+ RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications", 0,
+ nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr,
+ &hkApp, nullptr);
+ RegCloseKey(hkApp);
+ if (RegCreateKeyExW(HKEY_CURRENT_USER,
+ L"Software\\Classes\\Applications\\crashreporter.exe",
+ 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE,
+ nullptr, &hkApp, nullptr) == ERROR_SUCCESS) {
+ RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0);
+ RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0);
+ RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0);
+ RegCloseKey(hkApp);
+ }
+ }
+
+ char** argv = static_cast<char**>(malloc(__argc * sizeof(char*)));
+ for (int i = 0; i < __argc; i++) {
+ argv[i] = strdup(WideToUTF8(__wargv[i]).c_str());
+ }
+
+ // Do the real work.
+ return main(__argc, argv);
+}
+#endif
diff --git a/toolkit/crashreporter/client/crashreporter.exe.manifest b/toolkit/crashreporter/client/crashreporter.exe.manifest
new file mode 100644
index 000000000..f8587bfad
--- /dev/null
+++ b/toolkit/crashreporter/client/crashreporter.exe.manifest
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assemblyIdentity
+ version="1.0.0.0"
+ processorArchitecture="*"
+ name="CrashReporter"
+ type="win32"
+/>
+<description>Crash Reporter</description>
+<dependency>
+ <dependentAssembly>
+ <assemblyIdentity
+ type="win32"
+ name="Microsoft.Windows.Common-Controls"
+ version="6.0.0.0"
+ processorArchitecture="*"
+ publicKeyToken="6595b64144ccf1df"
+ language="*"
+ />
+ </dependentAssembly>
+</dependency>
+<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
+ <ms_asmv3:security>
+ <ms_asmv3:requestedPrivileges>
+ <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </ms_asmv3:requestedPrivileges>
+ </ms_asmv3:security>
+</ms_asmv3:trustInfo>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/toolkit/crashreporter/client/crashreporter.h b/toolkit/crashreporter/client/crashreporter.h
new file mode 100644
index 000000000..8c5ca3613
--- /dev/null
+++ b/toolkit/crashreporter/client/crashreporter.h
@@ -0,0 +1,158 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CRASHREPORTER_H__
+#define CRASHREPORTER_H__
+
+#ifdef _MSC_VER
+# pragma warning( push )
+// Disable exception handler warnings.
+# pragma warning( disable : 4530 )
+#endif
+
+#include <string>
+#include <map>
+#include <vector>
+#include <stdlib.h>
+#include <stdio.h>
+#include <iostream>
+#include <fstream>
+
+#define MAX_COMMENT_LENGTH 500
+
+#if defined(XP_WIN32)
+
+#include <windows.h>
+
+#define UI_SNPRINTF _snprintf
+#define UI_DIR_SEPARATOR "\\"
+
+std::string WideToUTF8(const std::wstring& wide, bool* success = 0);
+
+#else
+
+#define UI_SNPRINTF snprintf
+#define UI_DIR_SEPARATOR "/"
+
+#endif
+
+#define UI_CRASH_REPORTER_FILENAME "crashreporter"
+#define UI_MINIDUMP_ANALYZER_FILENAME "minidump-analyzer"
+
+typedef std::map<std::string, std::string> StringTable;
+
+#define ST_CRASHREPORTERTITLE "CrashReporterTitle"
+#define ST_CRASHREPORTERVENDORTITLE "CrashReporterVendorTitle"
+#define ST_CRASHREPORTERERROR "CrashReporterErrorText"
+#define ST_CRASHREPORTERPRODUCTERROR "CrashReporterProductErrorText2"
+#define ST_CRASHREPORTERHEADER "CrashReporterSorry"
+#define ST_CRASHREPORTERDESCRIPTION "CrashReporterDescriptionText2"
+#define ST_CRASHREPORTERDEFAULT "CrashReporterDefault"
+#define ST_VIEWREPORT "Details"
+#define ST_VIEWREPORTTITLE "ViewReportTitle"
+#define ST_COMMENTGRAYTEXT "CommentGrayText"
+#define ST_EXTRAREPORTINFO "ExtraReportInfo"
+#define ST_CHECKSUBMIT "CheckSendReport"
+#define ST_CHECKURL "CheckIncludeURL"
+#define ST_CHECKEMAIL "CheckAllowEmail"
+#define ST_EMAILGRAYTEXT "EmailGrayText"
+#define ST_REPORTPRESUBMIT "ReportPreSubmit2"
+#define ST_REPORTDURINGSUBMIT "ReportDuringSubmit2"
+#define ST_REPORTSUBMITSUCCESS "ReportSubmitSuccess"
+#define ST_SUBMITFAILED "ReportSubmitFailed"
+#define ST_QUIT "Quit2"
+#define ST_RESTART "Restart"
+#define ST_OK "Ok"
+#define ST_CLOSE "Close"
+
+#define ST_ERROR_BADARGUMENTS "ErrorBadArguments"
+#define ST_ERROR_EXTRAFILEEXISTS "ErrorExtraFileExists"
+#define ST_ERROR_EXTRAFILEREAD "ErrorExtraFileRead"
+#define ST_ERROR_EXTRAFILEMOVE "ErrorExtraFileMove"
+#define ST_ERROR_DUMPFILEEXISTS "ErrorDumpFileExists"
+#define ST_ERROR_DUMPFILEMOVE "ErrorDumpFileMove"
+#define ST_ERROR_NOPRODUCTNAME "ErrorNoProductName"
+#define ST_ERROR_NOSERVERURL "ErrorNoServerURL"
+#define ST_ERROR_NOSETTINGSPATH "ErrorNoSettingsPath"
+#define ST_ERROR_CREATEDUMPDIR "ErrorCreateDumpDir"
+#define ST_ERROR_ENDOFLIFE "ErrorEndOfLife"
+
+//=============================================================================
+// implemented in crashreporter.cpp
+//=============================================================================
+
+namespace CrashReporter {
+ extern StringTable gStrings;
+ extern std::string gSettingsPath;
+ extern std::string gEventsPath;
+ extern int gArgc;
+ extern char** gArgv;
+
+ void UIError(const std::string& message);
+
+ // The UI finished sending the report
+ void SendCompleted(bool success, const std::string& serverResponse);
+
+ bool ReadStrings(std::istream& in,
+ StringTable& strings,
+ bool unescape);
+ bool ReadStringsFromFile(const std::string& path,
+ StringTable& strings,
+ bool unescape);
+ bool WriteStrings(std::ostream& out,
+ const std::string& header,
+ StringTable& strings,
+ bool escape);
+ bool WriteStringsToFile(const std::string& path,
+ const std::string& header,
+ StringTable& strings,
+ bool escape);
+ void LogMessage(const std::string& message);
+ void DeleteDump();
+ bool ShouldEnableSending();
+
+ static const unsigned int kSaveCount = 10;
+}
+
+//=============================================================================
+// 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
+// Return true if the user sent (or tried to send) the crash report,
+// false if they chose not to, and it should be deleted.
+bool UIShowCrashUI(const StringTable& files,
+ const StringTable& queryParameters,
+ const std::string& sendURL,
+ const std::vector<std::string>& restartArgs);
+
+void UIError_impl(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 UIFileExists(const std::string& path);
+bool UIMoveFile(const std::string& oldfile, const std::string& newfile);
+bool UIDeleteFile(const std::string& oldfile);
+std::ifstream* UIOpenRead(const std::string& filename);
+std::ofstream* UIOpenWrite(const std::string& filename,
+ bool append=false,
+ bool binary=false);
+void UIPruneSavedDumps(const std::string& directory);
+void UIRunMinidumpAnalyzer(const std::string& exename,
+ const std::string& filename);
+
+#ifdef _MSC_VER
+# pragma warning( pop )
+#endif
+
+#endif
diff --git a/toolkit/crashreporter/client/crashreporter.ico b/toolkit/crashreporter/client/crashreporter.ico
new file mode 100644
index 000000000..29ac3c618
--- /dev/null
+++ b/toolkit/crashreporter/client/crashreporter.ico
Binary files differ
diff --git a/toolkit/crashreporter/client/crashreporter.rc b/toolkit/crashreporter/client/crashreporter.rc
new file mode 100755
index 000000000..0ccd0757f
--- /dev/null
+++ b/toolkit/crashreporter/client/crashreporter.rc
@@ -0,0 +1,148 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winresrc.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winresrc.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+1 24 "crashreporter.exe.manifest"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_MAINICON ICON "crashreporter.ico"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// AVI
+//
+
+IDR_THROBBER AVI "Throbber-small.avi"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_SENDDIALOG DIALOGEX 0, 0, 241, 187
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_APPWINDOW
+CAPTION "Sending Crash Report..."
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_DESCRIPTIONTEXT,"RICHEDIT50W",ES_MULTILINE | ES_READONLY,8,7,226,12,WS_EX_TRANSPARENT
+ CONTROL "tell mozilla about this crash so they can fix it",IDC_SUBMITREPORTCHECK,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,25,222,10
+ CHECKBOX "details...",IDC_VIEWREPORTBUTTON,24,40,54,14,BS_PUSHLIKE
+ EDITTEXT IDC_COMMENTTEXT,24,59,210,43,ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL
+ CONTROL "include the address of the page i was on",IDC_INCLUDEURLCHECK,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,24,107,210,10
+ CONTROL "tell mozilla to email me with more information",IDC_EMAILMECHECK,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,24,120,210,10
+ EDITTEXT IDC_EMAILTEXT,36,133,198,14,ES_AUTOHSCROLL
+ CONTROL "",IDC_THROBBER,"SysAnimate32",ACS_TRANSPARENT | NOT WS_VISIBLE | WS_TABSTOP,4,152,16,16
+ LTEXT "your crash report will be submitted when you restart",IDC_PROGRESSTEXT,24,152,210,10,SS_NOPREFIX
+ DEFPUSHBUTTON "restart firefox",IDC_RESTARTBUTTON,84,166,68,14
+ PUSHBUTTON "quit without sending",IDC_CLOSEBUTTON,157,166,77,14
+END
+
+IDD_VIEWREPORTDIALOG DIALOGEX 0, 0, 208, 126
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION
+CAPTION "view report"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_VIEWREPORTTEXT,"RICHEDIT50W",ES_MULTILINE | ES_READONLY | WS_BORDER | WS_VSCROLL | WS_TABSTOP,7,7,194,92
+ DEFPUSHBUTTON "OK",IDOK,151,105,50,14
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_SENDDIALOG, DIALOG
+ BEGIN
+ LEFTMARGIN, 8
+ RIGHTMARGIN, 234
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 180
+ END
+
+ IDD_VIEWREPORTDIALOG, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 201
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 119
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/toolkit/crashreporter/client/crashreporter_gtk_common.cpp b/toolkit/crashreporter/client/crashreporter_gtk_common.cpp
new file mode 100644
index 000000000..395a339bf
--- /dev/null
+++ b/toolkit/crashreporter/client/crashreporter_gtk_common.cpp
@@ -0,0 +1,453 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "crashreporter.h"
+
+#include <unistd.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "common/linux/http_upload.h"
+#include "crashreporter.h"
+#include "crashreporter_gtk_common.h"
+
+#ifndef GDK_KEY_Escape
+#define GDK_KEY_Escape GDK_Escape
+#endif
+
+using std::string;
+using std::vector;
+
+using namespace CrashReporter;
+
+GtkWidget* gWindow = 0;
+GtkWidget* gSubmitReportCheck = 0;
+GtkWidget* gIncludeURLCheck = 0;
+GtkWidget* gThrobber = 0;
+GtkWidget* gProgressLabel = 0;
+GtkWidget* gCloseButton = 0;
+GtkWidget* gRestartButton = 0;
+
+bool gInitialized = false;
+bool gDidTrySend = false;
+StringTable gFiles;
+StringTable gQueryParameters;
+string gHttpProxy;
+string gAuth;
+string gCACertificateFile;
+string gSendURL;
+string gURLParameter;
+vector<string> gRestartArgs;
+GThread* gSendThreadID;
+
+// From crashreporter_linux.cpp
+void SaveSettings();
+void SendReport();
+void TryInitGnome();
+void UpdateSubmit();
+
+static bool RestartApplication()
+{
+ char** argv = reinterpret_cast<char**>(
+ malloc(sizeof(char*) * (gRestartArgs.size() + 1)));
+
+ if (!argv) return false;
+
+ unsigned int i;
+ for (i = 0; i < gRestartArgs.size(); i++) {
+ argv[i] = (char*)gRestartArgs[i].c_str();
+ }
+ argv[i] = 0;
+
+ pid_t pid = fork();
+ if (pid == -1)
+ return false;
+ else if (pid == 0) {
+ (void)execv(argv[0], argv);
+ _exit(1);
+ }
+
+ free(argv);
+
+ return true;
+}
+
+// Quit the app, used as a timeout callback
+static gboolean CloseApp(gpointer data)
+{
+ gtk_main_quit();
+ g_thread_join(gSendThreadID);
+ return FALSE;
+}
+
+static gboolean ReportCompleted(gpointer success)
+{
+ gtk_widget_hide(gThrobber);
+ string str = success ? gStrings[ST_REPORTSUBMITSUCCESS]
+ : gStrings[ST_SUBMITFAILED];
+ gtk_label_set_text(GTK_LABEL(gProgressLabel), str.c_str());
+ g_timeout_add(5000, CloseApp, 0);
+ return FALSE;
+}
+
+#ifdef MOZ_ENABLE_GCONF
+#define HTTP_PROXY_DIR "/system/http_proxy"
+
+void LoadProxyinfo()
+{
+ class GConfClient;
+ typedef GConfClient * (*_gconf_default_fn)();
+ typedef gboolean (*_gconf_bool_fn)(GConfClient *, const gchar *, GError **);
+ typedef gint (*_gconf_int_fn)(GConfClient *, const gchar *, GError **);
+ typedef gchar * (*_gconf_string_fn)(GConfClient *, const gchar *, GError **);
+
+ if (getenv ("http_proxy"))
+ return; // libcurl can use the value from the environment
+
+ static void* gconfLib = dlopen("libgconf-2.so.4", RTLD_LAZY);
+ if (!gconfLib)
+ return;
+
+ _gconf_default_fn gconf_client_get_default =
+ (_gconf_default_fn)dlsym(gconfLib, "gconf_client_get_default");
+ _gconf_bool_fn gconf_client_get_bool =
+ (_gconf_bool_fn)dlsym(gconfLib, "gconf_client_get_bool");
+ _gconf_int_fn gconf_client_get_int =
+ (_gconf_int_fn)dlsym(gconfLib, "gconf_client_get_int");
+ _gconf_string_fn gconf_client_get_string =
+ (_gconf_string_fn)dlsym(gconfLib, "gconf_client_get_string");
+
+ if(!(gconf_client_get_default &&
+ gconf_client_get_bool &&
+ gconf_client_get_int &&
+ gconf_client_get_string)) {
+ dlclose(gconfLib);
+ return;
+ }
+
+ GConfClient *conf = gconf_client_get_default();
+
+ if (gconf_client_get_bool(conf, HTTP_PROXY_DIR "/use_http_proxy", nullptr)) {
+ gint port;
+ gchar *host = nullptr, *httpproxy = nullptr;
+
+ host = gconf_client_get_string(conf, HTTP_PROXY_DIR "/host", nullptr);
+ port = gconf_client_get_int(conf, HTTP_PROXY_DIR "/port", nullptr);
+
+ if (port && host && *host != '\0') {
+ httpproxy = g_strdup_printf("http://%s:%d/", host, port);
+ gHttpProxy = httpproxy;
+ }
+
+ g_free(host);
+ g_free(httpproxy);
+
+ if (gconf_client_get_bool(conf, HTTP_PROXY_DIR "/use_authentication",
+ nullptr)) {
+ gchar *user, *password, *auth = nullptr;
+
+ user = gconf_client_get_string(conf,
+ HTTP_PROXY_DIR "/authentication_user",
+ nullptr);
+ password = gconf_client_get_string(conf,
+ HTTP_PROXY_DIR
+ "/authentication_password",
+ nullptr);
+
+ if (user && password) {
+ auth = g_strdup_printf("%s:%s", user, password);
+ gAuth = auth;
+ }
+
+ g_free(user);
+ g_free(password);
+ g_free(auth);
+ }
+ }
+
+ g_object_unref(conf);
+
+ // Don't dlclose gconfLib as libORBit-2 uses atexit().
+}
+#endif
+
+gpointer SendThread(gpointer args)
+{
+ string response, error;
+ long response_code;
+
+ bool success = google_breakpad::HTTPUpload::SendRequest
+ (gSendURL,
+ gQueryParameters,
+ gFiles,
+ gHttpProxy, gAuth,
+ gCACertificateFile,
+ &response,
+ &response_code,
+ &error);
+ if (success) {
+ LogMessage("Crash report submitted successfully");
+ }
+ else {
+ LogMessage("Crash report submission failed: " + error);
+ }
+
+ SendCompleted(success, response);
+ // Apparently glib is threadsafe, and will schedule this
+ // on the main thread, see:
+ // http://library.gnome.org/devel/gtk-faq/stable/x499.html
+ g_idle_add(ReportCompleted, (gpointer)success);
+
+ return nullptr;
+}
+
+gboolean WindowDeleted(GtkWidget* window,
+ GdkEvent* event,
+ gpointer userData)
+{
+ SaveSettings();
+ gtk_main_quit();
+ return TRUE;
+}
+
+gboolean check_escape(GtkWidget* window,
+ GdkEventKey* event,
+ gpointer userData)
+{
+ if (event->keyval == GDK_KEY_Escape) {
+ gtk_main_quit();
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void MaybeSubmitReport()
+{
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gSubmitReportCheck))) {
+ gDidTrySend = true;
+ SendReport();
+ } else {
+ gtk_main_quit();
+ }
+}
+
+void CloseClicked(GtkButton* button,
+ gpointer userData)
+{
+ SaveSettings();
+ MaybeSubmitReport();
+}
+
+void RestartClicked(GtkButton* button,
+ gpointer userData)
+{
+ SaveSettings();
+ RestartApplication();
+ MaybeSubmitReport();
+}
+
+static void UpdateURL()
+{
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gIncludeURLCheck))) {
+ gQueryParameters["URL"] = gURLParameter;
+ } else {
+ gQueryParameters.erase("URL");
+ }
+}
+
+void SubmitReportChecked(GtkButton* sender, gpointer userData)
+{
+ UpdateSubmit();
+}
+
+void IncludeURLClicked(GtkButton* sender, gpointer userData)
+{
+ UpdateURL();
+}
+
+/* === Crashreporter UI Functions === */
+
+bool UIInit()
+{
+ // breakpad probably left us with blocked signals, unblock them here
+ sigset_t signals, old;
+ sigfillset(&signals);
+ sigprocmask(SIG_UNBLOCK, &signals, &old);
+
+ // tell glib we're going to use threads
+ g_thread_init(nullptr);
+
+ if (gtk_init_check(&gArgc, &gArgv)) {
+ gInitialized = true;
+
+ if (gStrings.find("isRTL") != gStrings.end() &&
+ gStrings["isRTL"] == "yes")
+ gtk_widget_set_default_direction(GTK_TEXT_DIR_RTL);
+
+ return true;
+ }
+
+ return false;
+}
+
+void UIShowDefaultUI()
+{
+ GtkWidget* errorDialog =
+ gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "%s", gStrings[ST_CRASHREPORTERDEFAULT].c_str());
+
+ gtk_window_set_title(GTK_WINDOW(errorDialog),
+ gStrings[ST_CRASHREPORTERTITLE].c_str());
+ gtk_dialog_run(GTK_DIALOG(errorDialog));
+}
+
+void UIError_impl(const string& message)
+{
+ if (!gInitialized) {
+ // Didn't initialize, this is the best we can do
+ printf("Error: %s\n", message.c_str());
+ return;
+ }
+
+ GtkWidget* errorDialog =
+ gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "%s", message.c_str());
+
+ gtk_window_set_title(GTK_WINDOW(errorDialog),
+ gStrings[ST_CRASHREPORTERTITLE].c_str());
+ gtk_dialog_run(GTK_DIALOG(errorDialog));
+}
+
+bool UIGetIniPath(string& path)
+{
+ path = gArgv[0];
+ path.append(".ini");
+
+ return true;
+}
+
+/*
+ * Settings are stored in ~/.vendor/product, or
+ * ~/.product if vendor is empty.
+ */
+bool UIGetSettingsPath(const string& vendor,
+ const string& product,
+ string& settingsPath)
+{
+ char* home = getenv("HOME");
+
+ if (!home)
+ return false;
+
+ settingsPath = home;
+ settingsPath += "/.";
+ if (!vendor.empty()) {
+ string lc_vendor;
+ std::transform(vendor.begin(), vendor.end(), back_inserter(lc_vendor),
+ (int(*)(int)) std::tolower);
+ settingsPath += lc_vendor + "/";
+ }
+ string lc_product;
+ std::transform(product.begin(), product.end(), back_inserter(lc_product),
+ (int(*)(int)) std::tolower);
+ settingsPath += lc_product + "/Crash Reports";
+ 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 UIFileExists(const string& path)
+{
+ struct stat sb;
+ int ret = stat(path.c_str(), &sb);
+ if (ret == -1 || !(sb.st_mode & S_IFREG))
+ return false;
+
+ return true;
+}
+
+bool UIMoveFile(const string& file, const string& newfile)
+{
+ if (!rename(file.c_str(), newfile.c_str()))
+ return true;
+ if (errno != EXDEV)
+ return false;
+
+ // use system /bin/mv instead, time to fork
+ pid_t pID = vfork();
+ if (pID < 0) {
+ // Failed to fork
+ return false;
+ }
+ if (pID == 0) {
+ char* const args[4] = {
+ const_cast<char*>("mv"),
+ strdup(file.c_str()),
+ strdup(newfile.c_str()),
+ 0
+ };
+ if (args[1] && args[2])
+ execve("/bin/mv", args, 0);
+ free(args[1]);
+ free(args[2]);
+ exit(-1);
+ }
+ int status;
+ waitpid(pID, &status, 0);
+ return UIFileExists(newfile);
+}
+
+bool UIDeleteFile(const string& file)
+{
+ return (unlink(file.c_str()) != -1);
+}
+
+std::ifstream* UIOpenRead(const string& filename)
+{
+ return new std::ifstream(filename.c_str(), std::ios::in);
+}
+
+std::ofstream* UIOpenWrite(const string& filename,
+ bool append, // append=false
+ bool binary) // binary=false
+{
+ std::ios_base::openmode mode = std::ios::out;
+
+ if (append) {
+ mode = mode | std::ios::app;
+ }
+
+ if (binary) {
+ mode = mode | std::ios::binary;
+ }
+
+ return new std::ofstream(filename.c_str(), mode);
+}
diff --git a/toolkit/crashreporter/client/crashreporter_gtk_common.h b/toolkit/crashreporter/client/crashreporter_gtk_common.h
new file mode 100644
index 000000000..3a6350c5b
--- /dev/null
+++ b/toolkit/crashreporter/client/crashreporter_gtk_common.h
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CRASHREPORTER_GTK_COMMON_H__
+#define CRASHREPORTER_GTK_COMMON_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include <string>
+#include <vector>
+
+const char kIniFile[] = "crashreporter.ini";
+
+extern GtkWidget* gWindow;
+extern GtkWidget* gSubmitReportCheck;
+extern GtkWidget* gIncludeURLCheck;
+extern GtkWidget* gThrobber;
+extern GtkWidget* gProgressLabel;
+extern GtkWidget* gCloseButton;
+extern GtkWidget* gRestartButton;
+
+extern std::vector<std::string> gRestartArgs;
+extern GThread* gSendThreadID;
+
+extern bool gInitialized;
+extern bool gDidTrySend;
+extern StringTable gFiles;
+extern StringTable gQueryParameters;
+extern std::string gHttpProxy;
+extern std::string gAuth;
+extern std::string gCACertificateFile;
+extern std::string gSendURL;
+extern std::string gURLParameter;
+
+void LoadProxyinfo();
+gpointer SendThread(gpointer args);
+gboolean WindowDeleted(GtkWidget* window,
+ GdkEvent* event,
+ gpointer userData);
+gboolean check_escape(GtkWidget* window, GdkEventKey* event, gpointer data);
+void SubmitReportChecked(GtkButton* sender, gpointer userData);
+void IncludeURLClicked(GtkButton* sender, gpointer userData);
+void CloseClicked(GtkButton* button,
+ gpointer userData);
+void RestartClicked(GtkButton* button,
+ gpointer userData);
+
+#endif // CRASHREPORTER_GTK_COMMON_H__
diff --git a/toolkit/crashreporter/client/crashreporter_linux.cpp b/toolkit/crashreporter/client/crashreporter_linux.cpp
new file mode 100644
index 000000000..6e7ccce40
--- /dev/null
+++ b/toolkit/crashreporter/client/crashreporter_linux.cpp
@@ -0,0 +1,576 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+#include <cctype>
+
+#include "crashreporter.h"
+#include "crashreporter_gtk_common.h"
+
+#define LABEL_MAX_CHAR_WIDTH 48
+
+using std::string;
+using std::vector;
+
+using namespace CrashReporter;
+
+static GtkWidget* gViewReportButton = 0;
+static GtkWidget* gCommentTextLabel = 0;
+static GtkWidget* gCommentText = 0;
+static GtkWidget* gEmailMeCheck = 0;
+static GtkWidget* gEmailEntryLabel = 0;
+static GtkWidget* gEmailEntry = 0;
+
+static bool gEmailFieldHint = true;
+static bool gCommentFieldHint = true;
+
+// handle from dlopen'ing libgnome
+static void* gnomeLib = nullptr;
+// handle from dlopen'ing libgnomeui
+static void* gnomeuiLib = nullptr;
+
+static void LoadSettings()
+{
+ /*
+ * NOTE! This code needs to stay in sync with the preference checking
+ * code in in nsExceptionHandler.cpp.
+ */
+
+ StringTable settings;
+ if (ReadStringsFromFile(gSettingsPath + "/" + kIniFile, settings, true)) {
+ if (settings.find("Email") != settings.end()) {
+ gtk_entry_set_text(GTK_ENTRY(gEmailEntry), settings["Email"].c_str());
+ gEmailFieldHint = false;
+ }
+ if (settings.find("EmailMe") != settings.end()) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gEmailMeCheck),
+ settings["EmailMe"][0] != '0');
+ }
+ if (settings.find("IncludeURL") != settings.end() &&
+ gIncludeURLCheck != 0) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gIncludeURLCheck),
+ settings["IncludeURL"][0] != '0');
+ }
+ bool enabled;
+ if (settings.find("SubmitReport") != settings.end())
+ enabled = settings["SubmitReport"][0] != '0';
+ else
+ enabled = ShouldEnableSending();
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gSubmitReportCheck),
+ enabled);
+ }
+}
+
+void SaveSettings()
+{
+ /*
+ * NOTE! This code needs to stay in sync with the preference setting
+ * code in in nsExceptionHandler.cpp.
+ */
+
+ StringTable settings;
+
+ ReadStringsFromFile(gSettingsPath + "/" + kIniFile, settings, true);
+ if (!gEmailFieldHint)
+ settings["Email"] = gtk_entry_get_text(GTK_ENTRY(gEmailEntry));
+ else
+ settings.erase("Email");
+
+ settings["EmailMe"] =
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gEmailMeCheck)) ? "1" : "0";
+ if (gIncludeURLCheck != 0)
+ settings["IncludeURL"] =
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gIncludeURLCheck))
+ ? "1" : "0";
+ settings["SubmitReport"] =
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gSubmitReportCheck))
+ ? "1" : "0";
+
+ WriteStringsToFile(gSettingsPath + "/" + kIniFile,
+ "Crash Reporter", settings, true);
+}
+
+void SendReport()
+{
+ // disable all our gui controls, show the throbber + change the progress text
+ gtk_widget_set_sensitive(gSubmitReportCheck, FALSE);
+ gtk_widget_set_sensitive(gViewReportButton, FALSE);
+ gtk_widget_set_sensitive(gCommentText, FALSE);
+ if (gIncludeURLCheck)
+ gtk_widget_set_sensitive(gIncludeURLCheck, FALSE);
+ gtk_widget_set_sensitive(gEmailMeCheck, FALSE);
+ gtk_widget_set_sensitive(gEmailEntry, FALSE);
+ gtk_widget_set_sensitive(gCloseButton, FALSE);
+ if (gRestartButton)
+ gtk_widget_set_sensitive(gRestartButton, FALSE);
+ gtk_widget_show_all(gThrobber);
+ gtk_label_set_text(GTK_LABEL(gProgressLabel),
+ gStrings[ST_REPORTDURINGSUBMIT].c_str());
+
+#ifdef MOZ_ENABLE_GCONF
+ LoadProxyinfo();
+#endif
+
+ // and spawn a thread to do the sending
+ GError* err;
+ gSendThreadID = g_thread_create(SendThread, nullptr, TRUE, &err);
+}
+
+static void ShowReportInfo(GtkTextView* viewReportTextView)
+{
+ GtkTextBuffer* buffer =
+ gtk_text_view_get_buffer(viewReportTextView);
+
+ GtkTextIter start, end;
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+
+ gtk_text_buffer_delete(buffer, &start, &end);
+
+ for (StringTable::iterator iter = gQueryParameters.begin();
+ iter != gQueryParameters.end();
+ iter++) {
+ gtk_text_buffer_insert(buffer, &end, iter->first.c_str(), -1);
+ gtk_text_buffer_insert(buffer, &end, ": ", -1);
+ gtk_text_buffer_insert(buffer, &end, iter->second.c_str(), -1);
+ gtk_text_buffer_insert(buffer, &end, "\n", -1);
+ }
+
+ gtk_text_buffer_insert(buffer, &end, "\n", -1);
+ gtk_text_buffer_insert(buffer, &end,
+ gStrings[ST_EXTRAREPORTINFO].c_str(), -1);
+}
+
+void UpdateSubmit()
+{
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gSubmitReportCheck))) {
+ gtk_widget_set_sensitive(gViewReportButton, TRUE);
+ gtk_widget_set_sensitive(gCommentText, TRUE);
+ if (gIncludeURLCheck)
+ gtk_widget_set_sensitive(gIncludeURLCheck, TRUE);
+ gtk_widget_set_sensitive(gEmailMeCheck, TRUE);
+ gtk_widget_set_sensitive(gEmailEntry,
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gEmailMeCheck)));
+ gtk_label_set_text(GTK_LABEL(gProgressLabel),
+ gStrings[ST_REPORTPRESUBMIT].c_str());
+ } else {
+ gtk_widget_set_sensitive(gViewReportButton, FALSE);
+ gtk_widget_set_sensitive(gCommentText, FALSE);
+ if (gIncludeURLCheck)
+ gtk_widget_set_sensitive(gIncludeURLCheck, FALSE);
+ gtk_widget_set_sensitive(gEmailMeCheck, FALSE);
+ gtk_widget_set_sensitive(gEmailEntry, FALSE);
+ gtk_label_set_text(GTK_LABEL(gProgressLabel), "");
+ }
+}
+
+static void ViewReportClicked(GtkButton* button,
+ gpointer userData)
+{
+ GtkDialog* dialog =
+ GTK_DIALOG(gtk_dialog_new_with_buttons(gStrings[ST_VIEWREPORTTITLE].c_str(),
+ GTK_WINDOW(gWindow),
+ GTK_DIALOG_MODAL,
+ GTK_STOCK_OK,
+ GTK_RESPONSE_OK,
+ nullptr));
+
+ GtkWidget* scrolled = gtk_scrolled_window_new(0, 0);
+ gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(dialog)), scrolled);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled),
+ GTK_SHADOW_IN);
+#if (MOZ_WIDGET_GTK >= 3)
+ gtk_widget_set_vexpand(scrolled, TRUE);
+#endif
+
+ GtkWidget* viewReportTextView = gtk_text_view_new();
+ gtk_container_add(GTK_CONTAINER(scrolled), viewReportTextView);
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(viewReportTextView), FALSE);
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(viewReportTextView),
+ GTK_WRAP_WORD);
+ gtk_widget_set_size_request(GTK_WIDGET(viewReportTextView), -1, 100);
+
+ ShowReportInfo(GTK_TEXT_VIEW(viewReportTextView));
+
+ gtk_dialog_set_default_response(dialog, GTK_RESPONSE_OK);
+ gtk_widget_set_size_request(GTK_WIDGET(dialog), 400, 200);
+ gtk_widget_show_all(GTK_WIDGET(dialog));
+ gtk_dialog_run(dialog);
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+static void CommentChanged(GtkTextBuffer* buffer, gpointer userData)
+{
+ GtkTextIter start, end;
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ const char* comment = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
+ if (comment[0] == '\0' || gCommentFieldHint)
+ gQueryParameters.erase("Comments");
+ else
+ gQueryParameters["Comments"] = comment;
+}
+
+static void CommentInsert(GtkTextBuffer* buffer,
+ GtkTextIter* location,
+ gchar* text,
+ gint len,
+ gpointer userData)
+{
+ GtkTextIter start, end;
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ const char* comment = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
+
+ // limit to 500 bytes in utf-8
+ if (strlen(comment) + len > MAX_COMMENT_LENGTH) {
+ g_signal_stop_emission_by_name(buffer, "insert-text");
+ }
+}
+
+static void UpdateHintText(GtkWidget* widget, gboolean gainedFocus,
+ bool* hintShowing, const char* hintText)
+{
+ GtkTextBuffer* buffer = nullptr;
+ if (GTK_IS_TEXT_VIEW(widget))
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
+
+ if (gainedFocus) {
+ if (*hintShowing) {
+ if (buffer == nullptr) { // sort of cheating
+ gtk_entry_set_text(GTK_ENTRY(widget), "");
+ }
+ else { // GtkTextView
+ gtk_text_buffer_set_text(buffer, "", 0);
+ }
+ gtk_widget_modify_text(widget, GTK_STATE_NORMAL, nullptr);
+ *hintShowing = false;
+ }
+ }
+ else {
+ // lost focus
+ const char* text = nullptr;
+ if (buffer == nullptr) {
+ text = gtk_entry_get_text(GTK_ENTRY(widget));
+ }
+ else {
+ GtkTextIter start, end;
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
+ }
+
+ if (text == nullptr || text[0] == '\0') {
+ *hintShowing = true;
+
+ if (buffer == nullptr) {
+ gtk_entry_set_text(GTK_ENTRY(widget), hintText);
+ }
+ else {
+ gtk_text_buffer_set_text(buffer, hintText, -1);
+ }
+
+ gtk_widget_modify_text(widget, GTK_STATE_NORMAL,
+ &gtk_widget_get_style(widget)->text[GTK_STATE_INSENSITIVE]);
+ }
+ }
+}
+
+static gboolean CommentFocusChange(GtkWidget* widget, GdkEventFocus* event,
+ gpointer userData)
+{
+ UpdateHintText(widget, event->in, &gCommentFieldHint,
+ gStrings[ST_COMMENTGRAYTEXT].c_str());
+
+ return FALSE;
+}
+
+static void UpdateEmail()
+{
+ const char* email = gtk_entry_get_text(GTK_ENTRY(gEmailEntry));
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gEmailMeCheck))) {
+ gtk_widget_set_sensitive(gEmailEntry, TRUE);
+ } else {
+ email = "";
+ gtk_widget_set_sensitive(gEmailEntry, FALSE);
+ }
+ if (email[0] == '\0' || gEmailFieldHint)
+ gQueryParameters.erase("Email");
+ else
+ gQueryParameters["Email"] = email;
+}
+
+static void EmailMeClicked(GtkButton* sender, gpointer userData)
+{
+ UpdateEmail();
+}
+
+static void EmailChanged(GtkEditable* editable, gpointer userData)
+{
+ UpdateEmail();
+}
+
+static gboolean EmailFocusChange(GtkWidget* widget, GdkEventFocus* event,
+ gpointer userData)
+{
+ UpdateHintText(widget, event->in, &gEmailFieldHint,
+ gStrings[ST_EMAILGRAYTEXT].c_str());
+
+ return FALSE;
+}
+
+typedef struct _GnomeProgram GnomeProgram;
+typedef struct _GnomeModuleInfo GnomeModuleInfo;
+typedef GnomeProgram * (*_gnome_program_init_fn)(const char *, const char *,
+ const GnomeModuleInfo *, int,
+ char **, const char *, ...);
+typedef const GnomeModuleInfo * (*_libgnomeui_module_info_get_fn)();
+
+void TryInitGnome()
+{
+ gnomeLib = dlopen("libgnome-2.so.0", RTLD_LAZY);
+ if (!gnomeLib)
+ return;
+
+ gnomeuiLib = dlopen("libgnomeui-2.so.0", RTLD_LAZY);
+ if (!gnomeuiLib)
+ return;
+
+ _gnome_program_init_fn gnome_program_init =
+ (_gnome_program_init_fn)(dlsym(gnomeLib, "gnome_program_init"));
+ _libgnomeui_module_info_get_fn libgnomeui_module_info_get =
+ (_libgnomeui_module_info_get_fn)(dlsym(gnomeuiLib, "libgnomeui_module_info_get"));
+
+ if (gnome_program_init && libgnomeui_module_info_get) {
+ gnome_program_init("crashreporter", "1.0", libgnomeui_module_info_get(),
+ gArgc, gArgv, nullptr);
+ }
+
+}
+
+/* === Crashreporter UI Functions === */
+
+/*
+ * Anything not listed here is in crashreporter_gtk_common.cpp:
+ * UIInit
+ * UIShowDefaultUI
+ * UIError_impl
+ * UIGetIniPath
+ * UIGetSettingsPath
+ * UIEnsurePathExists
+ * UIFileExists
+ * UIMoveFile
+ * UIDeleteFile
+ * UIOpenRead
+ * UIOpenWrite
+ */
+
+void UIShutdown()
+{
+ if (gnomeuiLib)
+ dlclose(gnomeuiLib);
+ // Don't dlclose gnomeLib as libgnomevfs and libORBit-2 use atexit().
+}
+
+bool UIShowCrashUI(const StringTable& files,
+ const StringTable& queryParameters,
+ const string& sendURL,
+ const vector<string>& restartArgs)
+{
+ gFiles = files;
+ gQueryParameters = queryParameters;
+ gSendURL = sendURL;
+ gRestartArgs = restartArgs;
+ if (gQueryParameters.find("URL") != gQueryParameters.end())
+ gURLParameter = gQueryParameters["URL"];
+
+ gWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW(gWindow),
+ gStrings[ST_CRASHREPORTERTITLE].c_str());
+ gtk_window_set_resizable(GTK_WINDOW(gWindow), FALSE);
+ gtk_window_set_position(GTK_WINDOW(gWindow), GTK_WIN_POS_CENTER);
+ gtk_container_set_border_width(GTK_CONTAINER(gWindow), 12);
+ g_signal_connect(gWindow, "delete-event", G_CALLBACK(WindowDeleted), 0);
+ g_signal_connect(gWindow, "key_press_event", G_CALLBACK(check_escape), nullptr);
+
+ GtkWidget* vbox = gtk_vbox_new(FALSE, 6);
+ gtk_container_add(GTK_CONTAINER(gWindow), vbox);
+
+ GtkWidget* titleLabel = gtk_label_new("");
+ gtk_box_pack_start(GTK_BOX(vbox), titleLabel, FALSE, FALSE, 0);
+ gtk_misc_set_alignment(GTK_MISC(titleLabel), 0, 0.5);
+ char* markup = g_strdup_printf("<b>%s</b>",
+ gStrings[ST_CRASHREPORTERHEADER].c_str());
+ gtk_label_set_markup(GTK_LABEL(titleLabel), markup);
+ g_free(markup);
+
+ GtkWidget* descriptionLabel =
+ gtk_label_new(gStrings[ST_CRASHREPORTERDESCRIPTION].c_str());
+ gtk_box_pack_start(GTK_BOX(vbox), descriptionLabel, TRUE, TRUE, 0);
+ // force the label to line wrap
+#if (MOZ_WIDGET_GTK == 2)
+ gtk_widget_set_size_request(descriptionLabel, 400, -1);
+#else
+ gtk_label_set_max_width_chars(GTK_LABEL(descriptionLabel), LABEL_MAX_CHAR_WIDTH);
+#endif
+ gtk_label_set_line_wrap(GTK_LABEL(descriptionLabel), TRUE);
+ gtk_label_set_selectable(GTK_LABEL(descriptionLabel), TRUE);
+ gtk_misc_set_alignment(GTK_MISC(descriptionLabel), 0, 0.5);
+
+ // this is honestly how they suggest you indent a section
+ GtkWidget* indentBox = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), indentBox, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(indentBox), gtk_label_new(""), FALSE, FALSE, 6);
+
+ GtkWidget* innerVBox1 = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(indentBox), innerVBox1, TRUE, TRUE, 0);
+
+ gSubmitReportCheck =
+ gtk_check_button_new_with_label(gStrings[ST_CHECKSUBMIT].c_str());
+ gtk_box_pack_start(GTK_BOX(innerVBox1), gSubmitReportCheck, FALSE, FALSE, 0);
+ g_signal_connect(gSubmitReportCheck, "clicked",
+ G_CALLBACK(SubmitReportChecked), 0);
+
+ // indent again, below the "submit report" checkbox
+ GtkWidget* indentBox2 = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(innerVBox1), indentBox2, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(indentBox2), gtk_label_new(""), FALSE, FALSE, 6);
+
+ GtkWidget* innerVBox = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(indentBox2), innerVBox, TRUE, TRUE, 0);
+ gtk_box_set_spacing(GTK_BOX(innerVBox), 6);
+
+ GtkWidget* viewReportButtonBox = gtk_hbutton_box_new();
+ gtk_box_pack_start(GTK_BOX(innerVBox), viewReportButtonBox, FALSE, FALSE, 0);
+ gtk_box_set_spacing(GTK_BOX(viewReportButtonBox), 6);
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(viewReportButtonBox), GTK_BUTTONBOX_START);
+
+ gViewReportButton =
+ gtk_button_new_with_label(gStrings[ST_VIEWREPORT].c_str());
+ gtk_box_pack_start(GTK_BOX(viewReportButtonBox), gViewReportButton, FALSE, FALSE, 0);
+ g_signal_connect(gViewReportButton, "clicked", G_CALLBACK(ViewReportClicked), 0);
+
+ GtkWidget* scrolled = gtk_scrolled_window_new(0, 0);
+ gtk_container_add(GTK_CONTAINER(innerVBox), scrolled);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
+ GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled),
+ GTK_SHADOW_IN);
+#if (MOZ_WIDGET_GTK >= 3)
+ gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(scrolled), 100);
+#endif
+
+ gCommentTextLabel = gtk_label_new(gStrings[ST_COMMENTGRAYTEXT].c_str());
+ gCommentText = gtk_text_view_new();
+ gtk_label_set_mnemonic_widget(GTK_LABEL(gCommentTextLabel), gCommentText);
+ gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(gCommentText), FALSE);
+ g_signal_connect(gCommentText, "focus-in-event", G_CALLBACK(CommentFocusChange), 0);
+ g_signal_connect(gCommentText, "focus-out-event", G_CALLBACK(CommentFocusChange), 0);
+
+ GtkTextBuffer* commentBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gCommentText));
+ g_signal_connect(commentBuffer, "changed", G_CALLBACK(CommentChanged), 0);
+ g_signal_connect(commentBuffer, "insert-text", G_CALLBACK(CommentInsert), 0);
+
+ gtk_container_add(GTK_CONTAINER(scrolled), gCommentText);
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(gCommentText),
+ GTK_WRAP_WORD);
+ gtk_widget_set_size_request(GTK_WIDGET(gCommentText), -1, 100);
+
+ if (gQueryParameters.find("URL") != gQueryParameters.end()) {
+ gIncludeURLCheck =
+ gtk_check_button_new_with_label(gStrings[ST_CHECKURL].c_str());
+ gtk_box_pack_start(GTK_BOX(innerVBox), gIncludeURLCheck, FALSE, FALSE, 0);
+ g_signal_connect(gIncludeURLCheck, "clicked", G_CALLBACK(IncludeURLClicked), 0);
+ // on by default
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gIncludeURLCheck), TRUE);
+ }
+
+ gEmailMeCheck =
+ gtk_check_button_new_with_label(gStrings[ST_CHECKEMAIL].c_str());
+ gtk_box_pack_start(GTK_BOX(innerVBox), gEmailMeCheck, FALSE, FALSE, 0);
+ g_signal_connect(gEmailMeCheck, "clicked", G_CALLBACK(EmailMeClicked), 0);
+
+ GtkWidget* emailIndentBox = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(innerVBox), emailIndentBox, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(emailIndentBox), gtk_label_new(""),
+ FALSE, FALSE, 9);
+
+ gEmailEntryLabel = gtk_label_new(gStrings[ST_EMAILGRAYTEXT].c_str());
+ gEmailEntry = gtk_entry_new();
+ gtk_label_set_mnemonic_widget(GTK_LABEL(gEmailEntryLabel), gEmailEntry);
+ gtk_box_pack_start(GTK_BOX(emailIndentBox), gEmailEntry, TRUE, TRUE, 0);
+ g_signal_connect(gEmailEntry, "changed", G_CALLBACK(EmailChanged), 0);
+ g_signal_connect(gEmailEntry, "focus-in-event", G_CALLBACK(EmailFocusChange), 0);
+ g_signal_connect(gEmailEntry, "focus-out-event", G_CALLBACK(EmailFocusChange), 0);
+
+ GtkWidget* progressBox = gtk_hbox_new(FALSE, 6);
+ gtk_box_pack_start(GTK_BOX(vbox), progressBox, TRUE, TRUE, 0);
+
+ // Get the throbber image from alongside the executable
+ char* dir = g_path_get_dirname(gArgv[0]);
+ char* path = g_build_filename(dir, "Throbber-small.gif", nullptr);
+ g_free(dir);
+ gThrobber = gtk_image_new_from_file(path);
+ gtk_box_pack_start(GTK_BOX(progressBox), gThrobber, FALSE, FALSE, 0);
+
+ gProgressLabel =
+ gtk_label_new(gStrings[ST_REPORTPRESUBMIT].c_str());
+ gtk_box_pack_start(GTK_BOX(progressBox), gProgressLabel, TRUE, TRUE, 0);
+ // force the label to line wrap
+#if (MOZ_WIDGET_GTK == 2)
+ gtk_widget_set_size_request(gProgressLabel, 400, -1);
+#else
+ gtk_label_set_max_width_chars(GTK_LABEL(gProgressLabel), LABEL_MAX_CHAR_WIDTH);
+#endif
+ gtk_label_set_line_wrap(GTK_LABEL(gProgressLabel), TRUE);
+
+ GtkWidget* buttonBox = gtk_hbutton_box_new();
+ gtk_box_pack_end(GTK_BOX(vbox), buttonBox, FALSE, FALSE, 0);
+ gtk_box_set_spacing(GTK_BOX(buttonBox), 6);
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(buttonBox), GTK_BUTTONBOX_END);
+
+ gCloseButton =
+ gtk_button_new_with_label(gStrings[ST_QUIT].c_str());
+ gtk_box_pack_start(GTK_BOX(buttonBox), gCloseButton, FALSE, FALSE, 0);
+ gtk_widget_set_can_default(gCloseButton, TRUE);
+ g_signal_connect(gCloseButton, "clicked", G_CALLBACK(CloseClicked), 0);
+
+ gRestartButton = 0;
+ if (restartArgs.size() > 0) {
+ gRestartButton = gtk_button_new_with_label(gStrings[ST_RESTART].c_str());
+ gtk_box_pack_start(GTK_BOX(buttonBox), gRestartButton, FALSE, FALSE, 0);
+ gtk_widget_set_can_default(gRestartButton, TRUE);
+ g_signal_connect(gRestartButton, "clicked", G_CALLBACK(RestartClicked), 0);
+ }
+
+ gtk_widget_grab_focus(gSubmitReportCheck);
+
+ gtk_widget_grab_default(gRestartButton ? gRestartButton : gCloseButton);
+
+ LoadSettings();
+
+ UpdateEmail();
+ UpdateSubmit();
+
+ UpdateHintText(gCommentText, FALSE, &gCommentFieldHint,
+ gStrings[ST_COMMENTGRAYTEXT].c_str());
+ UpdateHintText(gEmailEntry, FALSE, &gEmailFieldHint,
+ gStrings[ST_EMAILGRAYTEXT].c_str());
+
+ gtk_widget_show_all(gWindow);
+ // stick this here to avoid the show_all above...
+ gtk_widget_hide(gThrobber);
+
+ gtk_main();
+
+ return gDidTrySend;
+}
diff --git a/toolkit/crashreporter/client/crashreporter_osx.h b/toolkit/crashreporter/client/crashreporter_osx.h
new file mode 100644
index 000000000..e274de0de
--- /dev/null
+++ b/toolkit/crashreporter/client/crashreporter_osx.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CRASHREPORTER_OSX_H__
+#define CRASHREPORTER_OSX_H__
+
+#include <Cocoa/Cocoa.h>
+#include "HTTPMultipartUpload.h"
+#include "crashreporter.h"
+
+// Defined below
+@class TextViewWithPlaceHolder;
+
+@interface CrashReporterUI : NSObject
+{
+ IBOutlet NSWindow* mWindow;
+
+ /* Crash reporter view */
+ IBOutlet NSTextField* mHeaderLabel;
+ IBOutlet NSTextField* mDescriptionLabel;
+ IBOutlet NSButton* mViewReportButton;
+ IBOutlet NSScrollView* mCommentScrollView;
+ IBOutlet TextViewWithPlaceHolder* mCommentText;
+ IBOutlet NSButton* mSubmitReportButton;
+ IBOutlet NSButton* mIncludeURLButton;
+ IBOutlet NSButton* mEmailMeButton;
+ IBOutlet NSTextField* mEmailText;
+ IBOutlet NSButton* mCloseButton;
+ IBOutlet NSButton* mRestartButton;
+ IBOutlet NSProgressIndicator* mProgressIndicator;
+ IBOutlet NSTextField* mProgressText;
+
+ /* Error view */
+ IBOutlet NSView* mErrorView;
+ IBOutlet NSTextField* mErrorHeaderLabel;
+ IBOutlet NSTextField* mErrorLabel;
+ IBOutlet NSButton* mErrorCloseButton;
+
+ /* For "show info" alert */
+ IBOutlet NSWindow* mViewReportWindow;
+ IBOutlet NSTextView* mViewReportTextView;
+ IBOutlet NSButton* mViewReportOkButton;
+
+ HTTPMultipartUpload* mPost;
+}
+
+- (void)showCrashUI:(const StringTable&)files
+ queryParameters:(const StringTable&)queryParameters
+ sendURL:(const std::string&)sendURL;
+- (void)showErrorUI:(const std::string&)message;
+- (void)showReportInfo;
+- (void)maybeSubmitReport;
+- (void)closeMeDown:(id)unused;
+
+- (IBAction)submitReportClicked:(id)sender;
+- (IBAction)viewReportClicked:(id)sender;
+- (IBAction)viewReportOkClicked:(id)sender;
+- (IBAction)closeClicked:(id)sender;
+- (IBAction)restartClicked:(id)sender;
+- (IBAction)includeURLClicked:(id)sender;
+- (IBAction)emailMeClicked:(id)sender;
+
+- (void)controlTextDidChange:(NSNotification *)note;
+- (void)textDidChange:(NSNotification *)aNotification;
+- (BOOL)textView:(NSTextView *)aTextView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString;
+
+- (void)doInitialResizing;
+- (float)setStringFitVertically:(NSControl*)control
+ string:(NSString*)str
+ resizeWindow:(BOOL)resizeWindow;
+- (void)setView:(NSView*)v animate: (BOOL) animate;
+- (void)enableControls:(BOOL)enabled;
+- (void)updateSubmit;
+- (void)updateURL;
+- (void)updateEmail;
+- (void)sendReport;
+- (bool)setupPost;
+- (void)uploadThread:(HTTPMultipartUpload*)post;
+- (void)uploadComplete:(NSData*)data;
+
+-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication;
+-(void)applicationWillTerminate:(NSNotification *)aNotification;
+
+@end
+
+/*
+ * Subclass NSTextView to provide a text view with placeholder text.
+ * Also provide a setEnabled implementation.
+ */
+@interface TextViewWithPlaceHolder : NSTextView {
+ NSMutableAttributedString *mPlaceHolderString;
+}
+
+- (BOOL)becomeFirstResponder;
+- (void)drawRect:(NSRect)rect;
+- (BOOL)resignFirstResponder;
+- (void)setPlaceholder:(NSString*)placeholder;
+- (void)insertTab:(id)sender;
+- (void)insertBacktab:(id)sender;
+- (void)setEnabled:(BOOL)enabled;
+- (void)dealloc;
+
+@end
+
+#endif
diff --git a/toolkit/crashreporter/client/crashreporter_osx.mm b/toolkit/crashreporter/client/crashreporter_osx.mm
new file mode 100644
index 000000000..0768d6da3
--- /dev/null
+++ b/toolkit/crashreporter/client/crashreporter_osx.mm
@@ -0,0 +1,922 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+#import <CoreFoundation/CoreFoundation.h>
+#include "crashreporter.h"
+#include "crashreporter_osx.h"
+#include <crt_externs.h>
+#include <spawn.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <sstream>
+
+using std::string;
+using std::vector;
+using std::ostringstream;
+
+using namespace CrashReporter;
+
+static NSAutoreleasePool* gMainPool;
+static CrashReporterUI* gUI = 0;
+static StringTable gFiles;
+static StringTable gQueryParameters;
+static string gURLParameter;
+static string gSendURL;
+static vector<string> gRestartArgs;
+static bool gDidTrySend = false;
+static bool gRTLlayout = false;
+
+static cpu_type_t pref_cpu_types[2] = {
+#if defined(__i386__)
+ CPU_TYPE_X86,
+#elif defined(__x86_64__)
+ CPU_TYPE_X86_64,
+#elif defined(__ppc__)
+ CPU_TYPE_POWERPC,
+#endif
+ CPU_TYPE_ANY };
+
+#define NSSTR(s) [NSString stringWithUTF8String:(s).c_str()]
+
+static NSString* Str(const char* aName)
+{
+ string str = gStrings[aName];
+ if (str.empty()) str = "?";
+ return NSSTR(str);
+}
+
+static bool RestartApplication()
+{
+ vector<char*> argv(gRestartArgs.size() + 1);
+
+ posix_spawnattr_t spawnattr;
+ if (posix_spawnattr_init(&spawnattr) != 0) {
+ return false;
+ }
+
+ // Set spawn attributes.
+ size_t attr_count = sizeof(pref_cpu_types) / sizeof(pref_cpu_types[0]);
+ size_t attr_ocount = 0;
+ if (posix_spawnattr_setbinpref_np(&spawnattr,
+ attr_count,
+ pref_cpu_types,
+ &attr_ocount) != 0 ||
+ attr_ocount != attr_count) {
+ posix_spawnattr_destroy(&spawnattr);
+ return false;
+ }
+
+ unsigned int i;
+ for (i = 0; i < gRestartArgs.size(); i++) {
+ argv[i] = (char*)gRestartArgs[i].c_str();
+ }
+ argv[i] = 0;
+
+ char **env = NULL;
+ char ***nsEnv = _NSGetEnviron();
+ if (nsEnv)
+ env = *nsEnv;
+ int result = posix_spawnp(NULL,
+ argv[0],
+ NULL,
+ &spawnattr,
+ &argv[0],
+ env);
+
+ posix_spawnattr_destroy(&spawnattr);
+
+ return result == 0;
+}
+
+@implementation CrashReporterUI
+
+-(void)awakeFromNib
+{
+ gUI = self;
+ [mWindow center];
+
+ [mWindow setTitle:[[NSBundle mainBundle]
+ objectForInfoDictionaryKey:@"CFBundleName"]];
+}
+
+-(void)showCrashUI:(const StringTable&)files
+ queryParameters:(const StringTable&)queryParameters
+ sendURL:(const string&)sendURL
+{
+ gFiles = files;
+ gQueryParameters = queryParameters;
+ gSendURL = sendURL;
+
+ [mWindow setTitle:Str(ST_CRASHREPORTERTITLE)];
+ [mHeaderLabel setStringValue:Str(ST_CRASHREPORTERHEADER)];
+
+ NSRect viewReportFrame = [mViewReportButton frame];
+ [mViewReportButton setTitle:Str(ST_VIEWREPORT)];
+ [mViewReportButton sizeToFit];
+ if (gRTLlayout) {
+ // sizeToFit will keep the left side fixed, so realign
+ float oldWidth = viewReportFrame.size.width;
+ viewReportFrame = [mViewReportButton frame];
+ viewReportFrame.origin.x += oldWidth - viewReportFrame.size.width;
+ [mViewReportButton setFrame: viewReportFrame];
+ }
+
+ [mSubmitReportButton setTitle:Str(ST_CHECKSUBMIT)];
+ [mIncludeURLButton setTitle:Str(ST_CHECKURL)];
+ [mEmailMeButton setTitle:Str(ST_CHECKEMAIL)];
+ [mViewReportOkButton setTitle:Str(ST_OK)];
+
+ [mCommentText setPlaceholder:Str(ST_COMMENTGRAYTEXT)];
+ if (gRTLlayout)
+ [mCommentText toggleBaseWritingDirection:self];
+ [[mEmailText cell] setPlaceholderString:Str(ST_EMAILGRAYTEXT)];
+
+ if (gQueryParameters.find("URL") != gQueryParameters.end()) {
+ // save the URL value in case the checkbox gets unchecked
+ gURLParameter = gQueryParameters["URL"];
+ }
+ else {
+ // no URL specified, hide checkbox
+ [mIncludeURLButton removeFromSuperview];
+ // shrink window to fit
+ NSRect frame = [mWindow frame];
+ NSRect includeURLFrame = [mIncludeURLButton frame];
+ NSRect emailFrame = [mEmailMeButton frame];
+ int buttonMask = [mViewReportButton autoresizingMask];
+ int checkMask = [mSubmitReportButton autoresizingMask];
+ int commentScrollMask = [mCommentScrollView autoresizingMask];
+
+ [mViewReportButton setAutoresizingMask:NSViewMinYMargin];
+ [mSubmitReportButton setAutoresizingMask:NSViewMinYMargin];
+ [mCommentScrollView setAutoresizingMask:NSViewMinYMargin];
+
+ // remove all the space in between
+ frame.size.height -= includeURLFrame.origin.y - emailFrame.origin.y;
+ [mWindow setFrame:frame display: true animate:NO];
+
+ [mViewReportButton setAutoresizingMask:buttonMask];
+ [mSubmitReportButton setAutoresizingMask:checkMask];
+ [mCommentScrollView setAutoresizingMask:commentScrollMask];
+ }
+
+ // resize some buttons horizontally and possibly some controls vertically
+ [self doInitialResizing];
+
+ // load default state of submit checkbox
+ // we don't just do this via IB because we want the default to be
+ // off a certain percentage of the time
+ BOOL submitChecked = NO;
+ NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
+ if (nil != [userDefaults objectForKey:@"submitReport"]) {
+ submitChecked = [userDefaults boolForKey:@"submitReport"];
+ }
+ else {
+ // use compile-time specified enable percentage
+ submitChecked = ShouldEnableSending();
+ [userDefaults setBool:submitChecked forKey:@"submitReport"];
+ }
+ [mSubmitReportButton setState:(submitChecked ? NSOnState : NSOffState)];
+
+ [self updateSubmit];
+ [self updateURL];
+ [self updateEmail];
+
+ [mWindow makeKeyAndOrderFront:nil];
+}
+
+-(void)showErrorUI:(const string&)message
+{
+ [self setView: mErrorView animate: NO];
+
+ [mErrorHeaderLabel setStringValue:Str(ST_CRASHREPORTERHEADER)];
+ [self setStringFitVertically:mErrorLabel
+ string:NSSTR(message)
+ resizeWindow:YES];
+ [mErrorCloseButton setTitle:Str(ST_OK)];
+
+ [mErrorCloseButton setKeyEquivalent:@"\r"];
+ [mWindow makeFirstResponder:mErrorCloseButton];
+ [mWindow makeKeyAndOrderFront:nil];
+}
+
+-(void)showReportInfo
+{
+ NSDictionary* boldAttr = [NSDictionary
+ dictionaryWithObject:
+ [NSFont boldSystemFontOfSize:
+ [NSFont smallSystemFontSize]]
+ forKey:NSFontAttributeName];
+ NSDictionary* normalAttr = [NSDictionary
+ dictionaryWithObject:
+ [NSFont systemFontOfSize:
+ [NSFont smallSystemFontSize]]
+ forKey:NSFontAttributeName];
+
+ [mViewReportTextView setString:@""];
+ for (StringTable::iterator iter = gQueryParameters.begin();
+ iter != gQueryParameters.end();
+ iter++) {
+ NSAttributedString* key = [[NSAttributedString alloc]
+ initWithString:NSSTR(iter->first + ": ")
+ attributes:boldAttr];
+ NSAttributedString* value = [[NSAttributedString alloc]
+ initWithString:NSSTR(iter->second + "\n")
+ attributes:normalAttr];
+ [[mViewReportTextView textStorage] appendAttributedString: key];
+ [[mViewReportTextView textStorage] appendAttributedString: value];
+ [key release];
+ [value release];
+ }
+
+ NSAttributedString* extra = [[NSAttributedString alloc]
+ initWithString:NSSTR("\n" + gStrings[ST_EXTRAREPORTINFO])
+ attributes:normalAttr];
+ [[mViewReportTextView textStorage] appendAttributedString: extra];
+ [extra release];
+}
+
+- (void)maybeSubmitReport
+{
+ if ([mSubmitReportButton state] == NSOnState) {
+ [self setStringFitVertically:mProgressText
+ string:Str(ST_REPORTDURINGSUBMIT)
+ resizeWindow:YES];
+ // disable all the controls
+ [self enableControls:NO];
+ [mSubmitReportButton setEnabled:NO];
+ [mRestartButton setEnabled:NO];
+ [mCloseButton setEnabled:NO];
+ [mProgressIndicator startAnimation:self];
+ gDidTrySend = true;
+ [self sendReport];
+ } else {
+ [NSApp terminate:self];
+ }
+}
+
+- (void)closeMeDown:(id)unused
+{
+ [NSApp terminate:self];
+}
+
+-(IBAction)submitReportClicked:(id)sender
+{
+ [self updateSubmit];
+ NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
+ [userDefaults setBool:([mSubmitReportButton state] == NSOnState)
+ forKey:@"submitReport"];
+ [userDefaults synchronize];
+}
+
+-(IBAction)viewReportClicked:(id)sender
+{
+ [self showReportInfo];
+ [NSApp beginSheet:mViewReportWindow modalForWindow:mWindow
+ modalDelegate:nil didEndSelector:nil contextInfo:nil];
+}
+
+- (IBAction)viewReportOkClicked:(id)sender
+{
+ [mViewReportWindow orderOut:nil];
+ [NSApp endSheet:mViewReportWindow];
+}
+
+-(IBAction)closeClicked:(id)sender
+{
+ [self maybeSubmitReport];
+}
+
+-(IBAction)restartClicked:(id)sender
+{
+ RestartApplication();
+ [self maybeSubmitReport];
+}
+
+- (IBAction)includeURLClicked:(id)sender
+{
+ [self updateURL];
+}
+
+-(IBAction)emailMeClicked:(id)sender
+{
+ [self updateEmail];
+}
+
+-(void)controlTextDidChange:(NSNotification *)note
+{
+ [self updateEmail];
+}
+
+- (void)textDidChange:(NSNotification *)aNotification
+{
+ // update comment parameter
+ if ([[[mCommentText textStorage] mutableString] length] > 0)
+ gQueryParameters["Comments"] = [[[mCommentText textStorage] mutableString]
+ UTF8String];
+ else
+ gQueryParameters.erase("Comments");
+}
+
+// Limit the comment field to 500 bytes in UTF-8
+- (BOOL)textView:(NSTextView *)aTextView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString
+{
+ // current string length + replacement text length - replaced range length
+ if (([[aTextView string]
+ lengthOfBytesUsingEncoding:NSUTF8StringEncoding]
+ + [replacementString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]
+ - [[[aTextView string] substringWithRange:affectedCharRange]
+ lengthOfBytesUsingEncoding:NSUTF8StringEncoding])
+ > MAX_COMMENT_LENGTH) {
+ return NO;
+ }
+ return YES;
+}
+
+- (void)doInitialResizing
+{
+ NSRect windowFrame = [mWindow frame];
+ NSRect restartFrame = [mRestartButton frame];
+ NSRect closeFrame = [mCloseButton frame];
+ // resize close button to fit text
+ float oldCloseWidth = closeFrame.size.width;
+ [mCloseButton setTitle:Str(ST_QUIT)];
+ [mCloseButton sizeToFit];
+ closeFrame = [mCloseButton frame];
+ // move close button left if it grew
+ if (!gRTLlayout) {
+ closeFrame.origin.x -= closeFrame.size.width - oldCloseWidth;
+ }
+
+ if (gRestartArgs.size() == 0) {
+ [mRestartButton removeFromSuperview];
+ if (!gRTLlayout) {
+ closeFrame.origin.x = restartFrame.origin.x +
+ (restartFrame.size.width - closeFrame.size.width);
+ }
+ else {
+ closeFrame.origin.x = restartFrame.origin.x;
+ }
+ [mCloseButton setFrame: closeFrame];
+ [mCloseButton setKeyEquivalent:@"\r"];
+ } else {
+ [mRestartButton setTitle:Str(ST_RESTART)];
+ // resize "restart" button
+ float oldRestartWidth = restartFrame.size.width;
+ [mRestartButton sizeToFit];
+ restartFrame = [mRestartButton frame];
+ if (!gRTLlayout) {
+ // move left by the amount that the button grew
+ restartFrame.origin.x -= restartFrame.size.width - oldRestartWidth;
+ closeFrame.origin.x -= restartFrame.size.width - oldRestartWidth;
+ }
+ else {
+ // shift the close button right in RTL
+ closeFrame.origin.x += restartFrame.size.width - oldRestartWidth;
+ }
+ [mRestartButton setFrame: restartFrame];
+ [mCloseButton setFrame: closeFrame];
+ // possibly resize window if both buttons no longer fit
+ // leave 20 px from either side of the window, and 12 px
+ // between the buttons
+ float neededWidth = closeFrame.size.width + restartFrame.size.width +
+ 2*20 + 12;
+
+ if (neededWidth > windowFrame.size.width) {
+ windowFrame.size.width = neededWidth;
+ [mWindow setFrame:windowFrame display: true animate: NO];
+ }
+ [mRestartButton setKeyEquivalent:@"\r"];
+ }
+
+ NSButton *checkboxes[] = {
+ mSubmitReportButton,
+ mIncludeURLButton,
+ mEmailMeButton
+ };
+
+ for (int i=0; i<3; i++) {
+ NSRect frame = [checkboxes[i] frame];
+ [checkboxes[i] sizeToFit];
+ if (gRTLlayout) {
+ // sizeToFit will keep the left side fixed, so realign
+ float oldWidth = frame.size.width;
+ frame = [checkboxes[i] frame];
+ frame.origin.x += oldWidth - frame.size.width;
+ [checkboxes[i] setFrame: frame];
+ }
+ // keep existing spacing on left side, + 20 px spare on right
+ float neededWidth = frame.origin.x + frame.size.width + 20;
+ if (neededWidth > windowFrame.size.width) {
+ windowFrame.size.width = neededWidth;
+ [mWindow setFrame:windowFrame display: true animate: NO];
+ }
+ }
+
+ // do this down here because we may have made the window wider
+ // up above
+ [self setStringFitVertically:mDescriptionLabel
+ string:Str(ST_CRASHREPORTERDESCRIPTION)
+ resizeWindow:YES];
+
+ // now pin all the controls (except quit/submit) in place,
+ // if we lengthen the window after this, it's just to lengthen
+ // the progress text, so nothing above that text should move.
+ NSView* views[] = {
+ mSubmitReportButton,
+ mViewReportButton,
+ mCommentScrollView,
+ mIncludeURLButton,
+ mEmailMeButton,
+ mEmailText,
+ mProgressIndicator,
+ mProgressText
+ };
+ for (unsigned int i=0; i<sizeof(views)/sizeof(views[0]); i++) {
+ [views[i] setAutoresizingMask:NSViewMinYMargin];
+ }
+}
+
+-(float)setStringFitVertically:(NSControl*)control
+ string:(NSString*)str
+ resizeWindow:(BOOL)resizeWindow
+{
+ // hack to make the text field grow vertically
+ NSRect frame = [control frame];
+ float oldHeight = frame.size.height;
+
+ frame.size.height = 10000;
+ NSSize oldCellSize = [[control cell] cellSizeForBounds: frame];
+ [control setStringValue: str];
+ NSSize newCellSize = [[control cell] cellSizeForBounds: frame];
+
+ float delta = newCellSize.height - oldCellSize.height;
+ frame.origin.y -= delta;
+ frame.size.height = oldHeight + delta;
+ [control setFrame: frame];
+
+ if (resizeWindow) {
+ NSRect frame = [mWindow frame];
+ frame.origin.y -= delta;
+ frame.size.height += delta;
+ [mWindow setFrame:frame display: true animate: NO];
+ }
+
+ return delta;
+}
+
+-(void)setView: (NSView*)v animate: (BOOL)animate
+{
+ NSRect frame = [mWindow frame];
+
+ NSRect oldViewFrame = [[mWindow contentView] 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;
+
+ [mWindow setContentView:v];
+ [mWindow setFrame:frame display:true animate:animate];
+}
+
+- (void)enableControls:(BOOL)enabled
+{
+ [mViewReportButton setEnabled:enabled];
+ [mIncludeURLButton setEnabled:enabled];
+ [mEmailMeButton setEnabled:enabled];
+ [mCommentText setEnabled:enabled];
+ [mCommentScrollView setHasVerticalScroller:enabled];
+ [self updateEmail];
+}
+
+-(void)updateSubmit
+{
+ if ([mSubmitReportButton state] == NSOnState) {
+ [self setStringFitVertically:mProgressText
+ string:Str(ST_REPORTPRESUBMIT)
+ resizeWindow:YES];
+ [mProgressText setHidden:NO];
+ // enable all the controls
+ [self enableControls:YES];
+ }
+ else {
+ // not submitting, disable all the controls under
+ // the submit checkbox, and hide the status text
+ [mProgressText setHidden:YES];
+ [self enableControls:NO];
+ }
+}
+
+-(void)updateURL
+{
+ if ([mIncludeURLButton state] == NSOnState && !gURLParameter.empty()) {
+ gQueryParameters["URL"] = gURLParameter;
+ } else {
+ gQueryParameters.erase("URL");
+ }
+}
+
+-(void)updateEmail
+{
+ if ([mEmailMeButton state] == NSOnState &&
+ [mSubmitReportButton state] == NSOnState) {
+ NSString* email = [mEmailText stringValue];
+ gQueryParameters["Email"] = [email UTF8String];
+ [mEmailText setEnabled:YES];
+ } else {
+ gQueryParameters.erase("Email");
+ [mEmailText setEnabled:NO];
+ }
+}
+
+-(void)sendReport
+{
+ if (![self setupPost]) {
+ LogMessage("Crash report submission failed: could not set up POST data");
+ [self setStringFitVertically:mProgressText
+ string:Str(ST_SUBMITFAILED)
+ resizeWindow:YES];
+ // quit after 5 seconds
+ [self performSelector:@selector(closeMeDown:) withObject:nil
+ afterDelay:5.0];
+ }
+
+ [NSThread detachNewThreadSelector:@selector(uploadThread:)
+ toTarget:self
+ withObject:mPost];
+}
+
+-(bool)setupPost
+{
+ NSURL* url = [NSURL URLWithString:[NSSTR(gSendURL) stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
+ if (!url) return false;
+
+ mPost = [[HTTPMultipartUpload alloc] initWithURL: url];
+ if (!mPost) return false;
+
+ NSMutableDictionary* parameters =
+ [[NSMutableDictionary alloc] initWithCapacity: gQueryParameters.size()];
+ if (!parameters) return false;
+
+ 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);
+ if (key && value) {
+ [parameters setObject: value forKey: key];
+ } else {
+ ostringstream message;
+ message << "Warning: skipping annotation '" << i->first
+ << "' due to malformed UTF-8 encoding";
+ LogMessage(message.str());
+ }
+ }
+
+ for (StringTable::const_iterator i = gFiles.begin();
+ i != gFiles.end();
+ i++) {
+ [mPost addFileAtPath: NSSTR(i->second) name: NSSTR(i->first)];
+ }
+
+ [mPost setParameters: parameters];
+ [parameters release];
+
+ return true;
+}
+
+-(void)uploadComplete:(NSData*)data
+{
+ NSHTTPURLResponse* response = [mPost response];
+ [mPost release];
+
+ bool success;
+ string reply;
+ if (!data || !response || [response statusCode] != 200) {
+ success = false;
+ reply = "";
+
+ // if data is nil, we probably logged an error in uploadThread
+ if (data != nil && response != nil) {
+ ostringstream message;
+ message << "Crash report submission failed: server returned status "
+ << [response statusCode];
+ LogMessage(message.str());
+ }
+ } else {
+ success = true;
+ LogMessage("Crash report submitted successfully");
+
+ 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];
+ [r release];
+ }
+
+ SendCompleted(success, reply);
+
+ [mProgressIndicator stopAnimation:self];
+ if (success) {
+ [self setStringFitVertically:mProgressText
+ string:Str(ST_REPORTSUBMITSUCCESS)
+ resizeWindow:YES];
+ } else {
+ [self setStringFitVertically:mProgressText
+ string:Str(ST_SUBMITFAILED)
+ resizeWindow:YES];
+ }
+ // quit after 5 seconds
+ [self performSelector:@selector(closeMeDown:) withObject:nil
+ afterDelay:5.0];
+}
+
+-(void)uploadThread:(HTTPMultipartUpload*)post
+{
+ NSAutoreleasePool* autoreleasepool = [[NSAutoreleasePool alloc] init];
+ NSError* error = nil;
+ NSData* data = [post send: &error];
+ if (error) {
+ data = nil;
+ NSString* errorDesc = [error localizedDescription];
+ string message = [errorDesc UTF8String];
+ LogMessage("Crash report submission failed: " + message);
+ }
+
+ [self performSelectorOnMainThread: @selector(uploadComplete:)
+ withObject: data
+ waitUntilDone: YES];
+
+ [autoreleasepool release];
+}
+
+// to get auto-quit when we close the window
+-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication
+{
+ return YES;
+}
+
+-(void)applicationWillTerminate:(NSNotification *)aNotification
+{
+ // since we use [NSApp terminate:] we never return to main,
+ // so do our cleanup here
+ if (!gDidTrySend)
+ DeleteDump();
+}
+
+@end
+
+@implementation TextViewWithPlaceHolder
+
+- (BOOL)becomeFirstResponder
+{
+ [self setNeedsDisplay:YES];
+ return [super becomeFirstResponder];
+}
+
+- (void)drawRect:(NSRect)rect
+{
+ [super drawRect:rect];
+ if (mPlaceHolderString && [[self string] isEqualToString:@""] &&
+ self != [[self window] firstResponder])
+ [mPlaceHolderString drawInRect:[self frame]];
+}
+
+- (BOOL)resignFirstResponder
+{
+ [self setNeedsDisplay:YES];
+ return [super resignFirstResponder];
+}
+
+- (void)setPlaceholder:(NSString*)placeholder
+{
+ NSColor* txtColor = [NSColor disabledControlTextColor];
+ NSDictionary* txtDict = [NSDictionary
+ dictionaryWithObjectsAndKeys:txtColor,
+ NSForegroundColorAttributeName, nil];
+ mPlaceHolderString = [[NSMutableAttributedString alloc]
+ initWithString:placeholder attributes:txtDict];
+ if (gRTLlayout)
+ [mPlaceHolderString setAlignment:NSRightTextAlignment
+ range:NSMakeRange(0, [placeholder length])];
+
+}
+
+- (void)insertTab:(id)sender
+{
+ // don't actually want to insert tabs, just tab to next control
+ [[self window] selectNextKeyView:sender];
+}
+
+- (void)insertBacktab:(id)sender
+{
+ [[self window] selectPreviousKeyView:sender];
+}
+
+- (void)setEnabled:(BOOL)enabled
+{
+ [self setSelectable:enabled];
+ [self setEditable:enabled];
+ if (![[self string] isEqualToString:@""]) {
+ NSAttributedString* colorString;
+ NSColor* txtColor;
+ if (enabled)
+ txtColor = [NSColor textColor];
+ else
+ txtColor = [NSColor disabledControlTextColor];
+ NSDictionary *txtDict = [NSDictionary
+ dictionaryWithObjectsAndKeys:txtColor,
+ NSForegroundColorAttributeName, nil];
+ colorString = [[NSAttributedString alloc]
+ initWithString:[self string]
+ attributes:txtDict];
+ [[self textStorage] setAttributedString: colorString];
+ [self setInsertionPointColor:txtColor];
+ [colorString release];
+ }
+}
+
+- (void)dealloc
+{
+ [mPlaceHolderString release];
+ [super dealloc];
+}
+
+@end
+
+/* === Crashreporter UI Functions === */
+
+bool UIInit()
+{
+ gMainPool = [[NSAutoreleasePool alloc] init];
+ [NSApplication sharedApplication];
+
+ if (gStrings.find("isRTL") != gStrings.end() &&
+ gStrings["isRTL"] == "yes")
+ gRTLlayout = true;
+
+ [NSBundle loadNibNamed:(gRTLlayout ? @"MainMenuRTL" : @"MainMenu")
+ owner:NSApp];
+
+ return true;
+}
+
+void UIShutdown()
+{
+ [gMainPool release];
+}
+
+void UIShowDefaultUI()
+{
+ [gUI showErrorUI: gStrings[ST_CRASHREPORTERDEFAULT]];
+ [NSApp run];
+}
+
+bool UIShowCrashUI(const StringTable& files,
+ const StringTable& queryParameters,
+ const string& sendURL,
+ const vector<string>& restartArgs)
+{
+ gRestartArgs = restartArgs;
+
+ [gUI showCrashUI: files
+ queryParameters: queryParameters
+ sendURL: sendURL];
+ [NSApp run];
+
+ return gDidTrySend;
+}
+
+void UIError_impl(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)
+{
+ NSString* tmpPath = [NSString stringWithUTF8String:gArgv[0]];
+ NSString* iniName = [tmpPath lastPathComponent];
+ iniName = [iniName stringByAppendingPathExtension:@"ini"];
+ tmpPath = [tmpPath stringByDeletingLastPathComponent];
+ tmpPath = [tmpPath stringByDeletingLastPathComponent];
+ tmpPath = [tmpPath stringByAppendingPathComponent:@"Resources"];
+ tmpPath = [tmpPath stringByAppendingPathComponent:iniName];
+ path = [tmpPath UTF8String];
+ return true;
+}
+
+bool UIGetSettingsPath(const string& vendor,
+ const string& product,
+ string& settingsPath)
+{
+ FSRef foundRef;
+ OSErr err = FSFindFolder(kUserDomain, kApplicationSupportFolderType,
+ kCreateFolder, &foundRef);
+ if (err != noErr)
+ return false;
+
+ unsigned char path[PATH_MAX];
+ FSRefMakePath(&foundRef, path, sizeof(path));
+ NSString* destPath = [NSString stringWithUTF8String:reinterpret_cast<char*>(path)];
+
+ // 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)];
+ // Thunderbird stores its profile in ~/Library/Thunderbird,
+ // but we're going to put stuff in ~/Library/Application Support/Thunderbird
+ // anyway, so we have to ensure that path exists.
+ string tempPath = [destPath UTF8String];
+ if (!UIEnsurePathExists(tempPath))
+ return false;
+
+ destPath = [destPath stringByAppendingPathComponent: @"Crash Reports"];
+
+ settingsPath = [destPath UTF8String];
+
+ 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 UIFileExists(const string& path)
+{
+ struct stat sb;
+ int ret = stat(path.c_str(), &sb);
+ if (ret == -1 || !(sb.st_mode & S_IFREG))
+ return false;
+
+ return true;
+}
+
+bool UIMoveFile(const string& file, const string& newfile)
+{
+ if (!rename(file.c_str(), newfile.c_str()))
+ return true;
+ if (errno != EXDEV)
+ return false;
+
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSString *source = [fileManager stringWithFileSystemRepresentation:file.c_str() length:file.length()];
+ NSString *dest = [fileManager stringWithFileSystemRepresentation:newfile.c_str() length:newfile.length()];
+ if (!source || !dest)
+ return false;
+
+ [fileManager moveItemAtPath:source toPath:dest error:NULL];
+ return UIFileExists(newfile);
+}
+
+bool UIDeleteFile(const string& file)
+{
+ return (unlink(file.c_str()) != -1);
+}
+
+std::ifstream* UIOpenRead(const string& filename)
+{
+ return new std::ifstream(filename.c_str(), std::ios::in);
+}
+
+std::ofstream* UIOpenWrite(const string& filename,
+ bool append, // append=false
+ bool binary) // binary=false
+{
+ std::ios_base::openmode mode = std::ios::out;
+
+ if (append) {
+ mode = mode | std::ios::app;
+ }
+
+ if (binary) {
+ mode = mode | std::ios::binary;
+ }
+
+ return new std::ofstream(filename.c_str(), mode);
+}
diff --git a/toolkit/crashreporter/client/crashreporter_unix_common.cpp b/toolkit/crashreporter/client/crashreporter_unix_common.cpp
new file mode 100644
index 000000000..f42a35616
--- /dev/null
+++ b/toolkit/crashreporter/client/crashreporter_unix_common.cpp
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "crashreporter.h"
+
+#include <algorithm>
+
+#include <dirent.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+using namespace CrashReporter;
+using std::string;
+using std::vector;
+using std::sort;
+
+struct FileData
+{
+ time_t timestamp;
+ string path;
+};
+
+static bool CompareFDTime(const FileData& fd1, const FileData& fd2)
+{
+ return fd1.timestamp > fd2.timestamp;
+}
+
+void UIPruneSavedDumps(const std::string& directory)
+{
+ DIR *dirfd = opendir(directory.c_str());
+ if (!dirfd)
+ return;
+
+ vector<FileData> dumpfiles;
+
+ while (dirent *dir = readdir(dirfd)) {
+ FileData fd;
+ fd.path = directory + '/' + dir->d_name;
+ if (fd.path.size() < 5)
+ continue;
+
+ if (fd.path.compare(fd.path.size() - 4, 4, ".dmp") != 0)
+ continue;
+
+ struct stat st;
+ if (stat(fd.path.c_str(), &st)) {
+ closedir(dirfd);
+ return;
+ }
+
+ fd.timestamp = st.st_mtime;
+
+ dumpfiles.push_back(fd);
+ }
+
+ sort(dumpfiles.begin(), dumpfiles.end(), CompareFDTime);
+
+ while (dumpfiles.size() > kSaveCount) {
+ // get the path of the oldest file
+ string path = dumpfiles[dumpfiles.size() - 1].path;
+ UIDeleteFile(path.c_str());
+
+ // s/.dmp/.extra/
+ path.replace(path.size() - 4, 4, ".extra");
+ UIDeleteFile(path.c_str());
+
+ dumpfiles.pop_back();
+ }
+}
+
+void UIRunMinidumpAnalyzer(const string& exename, const string& filename)
+{
+ // Run the minidump analyzer and wait for it to finish
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ return; // Nothing to do upon failure
+ } else if (pid == 0) {
+ execl(exename.c_str(), exename.c_str(), filename.c_str(), nullptr);
+ } else {
+ waitpid(pid, nullptr, 0);
+ }
+}
diff --git a/toolkit/crashreporter/client/crashreporter_win.cpp b/toolkit/crashreporter/client/crashreporter_win.cpp
new file mode 100644
index 000000000..57ca495ba
--- /dev/null
+++ b/toolkit/crashreporter/client/crashreporter_win.cpp
@@ -0,0 +1,1568 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifdef WIN32_LEAN_AND_MEAN
+#undef WIN32_LEAN_AND_MEAN
+#endif
+
+#include "crashreporter.h"
+
+#include <windows.h>
+#include <versionhelpers.h>
+#include <commctrl.h>
+#include <richedit.h>
+#include <shellapi.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <math.h>
+#include <set>
+#include <algorithm>
+#include "resource.h"
+#include "client/windows/sender/crash_report_sender.h"
+#include "common/windows/string_utils-inl.h"
+
+#define CRASH_REPORTER_VALUE L"Enabled"
+#define SUBMIT_REPORT_VALUE L"SubmitCrashReport"
+#define SUBMIT_REPORT_OLD L"SubmitReport"
+#define INCLUDE_URL_VALUE L"IncludeURL"
+#define EMAIL_ME_VALUE L"EmailMe"
+#define EMAIL_VALUE L"Email"
+#define MAX_EMAIL_LENGTH 1024
+
+#define SENDURL_ORIGINAL L"https://crash-reports.mozilla.com/submit"
+#define SENDURL_XPSP2 L"https://crash-reports-xpsp2.mozilla.com/submit"
+
+#define WM_UPLOADCOMPLETE WM_APP
+
+// Thanks, Windows.h :(
+#undef min
+#undef max
+
+using std::string;
+using std::wstring;
+using std::map;
+using std::vector;
+using std::set;
+using std::ios;
+using std::ifstream;
+using std::ofstream;
+
+using namespace CrashReporter;
+
+typedef struct {
+ HWND hDlg;
+ map<wstring,wstring> queryParameters;
+ map<wstring,wstring> files;
+ wstring sendURL;
+
+ wstring serverResponse;
+} SendThreadData;
+
+/*
+ * Per http://msdn2.microsoft.com/en-us/library/ms645398(VS.85).aspx
+ * "The DLGTEMPLATEEX structure is not defined in any standard header file.
+ * The structure definition is provided here to explain the format of an
+ * extended template for a dialog box.
+*/
+typedef struct {
+ WORD dlgVer;
+ WORD signature;
+ DWORD helpID;
+ DWORD exStyle;
+ // There's more to this struct, but it has weird variable-length
+ // members, and I only actually need to touch exStyle on an existing
+ // instance, so I've omitted the rest.
+} DLGTEMPLATEEX;
+
+static HANDLE gThreadHandle;
+static SendThreadData gSendData = { 0, };
+static vector<string> gRestartArgs;
+static map<wstring,wstring> gQueryParameters;
+static wstring gCrashReporterKey(L"Software\\Mozilla\\Crash Reporter");
+static wstring gURLParameter;
+static int gCheckboxPadding = 6;
+static bool gRTLlayout = false;
+
+// When vertically resizing the dialog, these items should move down
+static set<UINT> gAttachedBottom;
+
+// Default set of items for gAttachedBottom
+static const UINT kDefaultAttachedBottom[] = {
+ IDC_SUBMITREPORTCHECK,
+ IDC_VIEWREPORTBUTTON,
+ IDC_COMMENTTEXT,
+ IDC_INCLUDEURLCHECK,
+ IDC_EMAILMECHECK,
+ IDC_EMAILTEXT,
+ IDC_PROGRESSTEXT,
+ IDC_THROBBER,
+ IDC_CLOSEBUTTON,
+ IDC_RESTARTBUTTON,
+};
+
+static wstring UTF8ToWide(const string& utf8, bool *success = 0);
+static DWORD WINAPI SendThreadProc(LPVOID param);
+
+static wstring Str(const char* key)
+{
+ return UTF8ToWide(gStrings[key]);
+}
+
+/* === win32 helper functions === */
+
+static void DoInitCommonControls()
+{
+ INITCOMMONCONTROLSEX ic;
+ ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
+ ic.dwICC = ICC_PROGRESS_CLASS;
+ InitCommonControlsEx(&ic);
+ // also get the rich edit control
+ LoadLibrary(L"Msftedit.dll");
+}
+
+static bool GetBoolValue(HKEY hRegKey, LPCTSTR valueName, DWORD* value)
+{
+ DWORD type, dataSize;
+ dataSize = sizeof(DWORD);
+ if (RegQueryValueEx(hRegKey, valueName, nullptr,
+ &type, (LPBYTE)value, &dataSize) == ERROR_SUCCESS &&
+ type == REG_DWORD)
+ return true;
+
+ return false;
+}
+
+// Removes a value from HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER, if it exists.
+static void RemoveUnusedValues(const wchar_t* key, LPCTSTR valueName)
+{
+ HKEY hRegKey;
+
+ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_SET_VALUE, &hRegKey)
+ == ERROR_SUCCESS) {
+ RegDeleteValue(hRegKey, valueName);
+ RegCloseKey(hRegKey);
+ }
+
+ if (RegOpenKeyEx(HKEY_CURRENT_USER, key, 0, KEY_SET_VALUE, &hRegKey)
+ == ERROR_SUCCESS) {
+ RegDeleteValue(hRegKey, valueName);
+ RegCloseKey(hRegKey);
+ }
+}
+
+static bool CheckBoolKey(const wchar_t* key,
+ const wchar_t* valueName,
+ bool* enabled)
+{
+ /*
+ * NOTE! This code needs to stay in sync with the preference checking
+ * code in in nsExceptionHandler.cpp.
+ */
+ *enabled = false;
+ bool found = false;
+ HKEY hRegKey;
+ DWORD val;
+ // see if our reg key is set globally
+ if (RegOpenKey(HKEY_LOCAL_MACHINE, key, &hRegKey) == ERROR_SUCCESS) {
+ if (GetBoolValue(hRegKey, valueName, &val)) {
+ *enabled = (val == 1);
+ found = true;
+ }
+ RegCloseKey(hRegKey);
+ } else {
+ // look for it in user settings
+ if (RegOpenKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) {
+ if (GetBoolValue(hRegKey, valueName, &val)) {
+ *enabled = (val == 1);
+ found = true;
+ }
+ RegCloseKey(hRegKey);
+ }
+ }
+
+ return found;
+}
+
+static void SetBoolKey(const wchar_t* key, const wchar_t* value, bool enabled)
+{
+ /*
+ * NOTE! This code needs to stay in sync with the preference setting
+ * code in in nsExceptionHandler.cpp.
+ */
+ HKEY hRegKey;
+
+ // remove the old value from the registry if it exists
+ RemoveUnusedValues(key, SUBMIT_REPORT_OLD);
+
+ if (RegCreateKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) {
+ DWORD data = (enabled ? 1 : 0);
+ RegSetValueEx(hRegKey, value, 0, REG_DWORD, (LPBYTE)&data, sizeof(data));
+ RegCloseKey(hRegKey);
+ }
+}
+
+static bool GetStringValue(HKEY hRegKey, LPCTSTR valueName, wstring& value)
+{
+ DWORD type, dataSize;
+ wchar_t buf[2048];
+ dataSize = sizeof(buf);
+ if (RegQueryValueEx(hRegKey, valueName, nullptr,
+ &type, (LPBYTE)buf, &dataSize) == ERROR_SUCCESS &&
+ type == REG_SZ) {
+ value = buf;
+ return true;
+ }
+
+ return false;
+}
+
+static bool GetStringKey(const wchar_t* key,
+ const wchar_t* valueName,
+ wstring& value)
+{
+ value = L"";
+ bool found = false;
+ HKEY hRegKey;
+ // see if our reg key is set globally
+ if (RegOpenKey(HKEY_LOCAL_MACHINE, key, &hRegKey) == ERROR_SUCCESS) {
+ if (GetStringValue(hRegKey, valueName, value)) {
+ found = true;
+ }
+ RegCloseKey(hRegKey);
+ } else {
+ // look for it in user settings
+ if (RegOpenKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) {
+ if (GetStringValue(hRegKey, valueName, value)) {
+ found = true;
+ }
+ RegCloseKey(hRegKey);
+ }
+ }
+
+ return found;
+}
+
+static void SetStringKey(const wchar_t* key,
+ const wchar_t* valueName,
+ const wstring& value)
+{
+ HKEY hRegKey;
+ if (RegCreateKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) {
+ RegSetValueEx(hRegKey, valueName, 0, REG_SZ,
+ (LPBYTE)value.c_str(),
+ (value.length() + 1) * sizeof(wchar_t));
+ RegCloseKey(hRegKey);
+ }
+}
+
+static string FormatLastError()
+{
+ DWORD err = GetLastError();
+ LPWSTR s;
+ string message = "Crash report submission failed: ";
+ // odds are it's a WinInet error
+ HANDLE hInetModule = GetModuleHandle(L"WinInet.dll");
+ if(FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_FROM_HMODULE,
+ hInetModule,
+ err,
+ 0,
+ (LPWSTR)&s,
+ 0,
+ nullptr) != 0) {
+ message += WideToUTF8(s, nullptr);
+ LocalFree(s);
+ // strip off any trailing newlines
+ string::size_type n = message.find_last_not_of("\r\n");
+ if (n < message.size() - 1) {
+ message.erase(n+1);
+ }
+ }
+ else {
+ char buf[64];
+ sprintf(buf, "Unknown error, error code: 0x%08x", err);
+ message += buf;
+ }
+ return message;
+}
+
+#define TS_DRAW 2
+#define BP_CHECKBOX 3
+
+typedef HANDLE (WINAPI*OpenThemeDataPtr)(HWND hwnd, LPCWSTR pszClassList);
+typedef HRESULT (WINAPI*CloseThemeDataPtr)(HANDLE hTheme);
+typedef HRESULT (WINAPI*GetThemePartSizePtr)(HANDLE hTheme, HDC hdc, int iPartId,
+ int iStateId, RECT* prc, int ts,
+ SIZE* psz);
+typedef HRESULT (WINAPI*GetThemeContentRectPtr)(HANDLE hTheme, HDC hdc, int iPartId,
+ int iStateId, const RECT* pRect,
+ RECT* pContentRect);
+
+
+static void GetThemeSizes(HWND hwnd)
+{
+ HMODULE themeDLL = LoadLibrary(L"uxtheme.dll");
+
+ if (!themeDLL)
+ return;
+
+ OpenThemeDataPtr openTheme =
+ (OpenThemeDataPtr)GetProcAddress(themeDLL, "OpenThemeData");
+ CloseThemeDataPtr closeTheme =
+ (CloseThemeDataPtr)GetProcAddress(themeDLL, "CloseThemeData");
+ GetThemePartSizePtr getThemePartSize =
+ (GetThemePartSizePtr)GetProcAddress(themeDLL, "GetThemePartSize");
+
+ if (!openTheme || !closeTheme || !getThemePartSize) {
+ FreeLibrary(themeDLL);
+ return;
+ }
+
+ HANDLE buttonTheme = openTheme(hwnd, L"Button");
+ if (!buttonTheme) {
+ FreeLibrary(themeDLL);
+ return;
+ }
+ HDC hdc = GetDC(hwnd);
+ SIZE s;
+ getThemePartSize(buttonTheme, hdc, BP_CHECKBOX, 0, nullptr, TS_DRAW, &s);
+ gCheckboxPadding = s.cx;
+ closeTheme(buttonTheme);
+ FreeLibrary(themeDLL);
+}
+
+// Gets the position of a window relative to another window's client area
+static void GetRelativeRect(HWND hwnd, HWND hwndParent, RECT* r)
+{
+ GetWindowRect(hwnd, r);
+ MapWindowPoints(nullptr, hwndParent, (POINT*)r, 2);
+}
+
+static void SetDlgItemVisible(HWND hwndDlg, UINT item, bool visible)
+{
+ HWND hwnd = GetDlgItem(hwndDlg, item);
+
+ ShowWindow(hwnd, visible ? SW_SHOW : SW_HIDE);
+}
+
+static void SetDlgItemDisabled(HWND hwndDlg, UINT item, bool disabled)
+{
+ HWND hwnd = GetDlgItem(hwndDlg, item);
+ LONG style = GetWindowLong(hwnd, GWL_STYLE);
+ if (!disabled)
+ style |= WS_DISABLED;
+ else
+ style &= ~WS_DISABLED;
+
+ SetWindowLong(hwnd, GWL_STYLE, style);
+}
+
+/* === Crash Reporting Dialog === */
+
+static void StretchDialog(HWND hwndDlg, int ydiff)
+{
+ RECT r;
+ GetWindowRect(hwndDlg, &r);
+ r.bottom += ydiff;
+ MoveWindow(hwndDlg, r.left, r.top,
+ r.right - r.left, r.bottom - r.top, TRUE);
+}
+
+static void ReflowDialog(HWND hwndDlg, int ydiff)
+{
+ // Move items attached to the bottom down/up by as much as
+ // the window resize
+ for (set<UINT>::const_iterator item = gAttachedBottom.begin();
+ item != gAttachedBottom.end();
+ item++) {
+ RECT r;
+ HWND hwnd = GetDlgItem(hwndDlg, *item);
+ GetRelativeRect(hwnd, hwndDlg, &r);
+ r.top += ydiff;
+ r.bottom += ydiff;
+ MoveWindow(hwnd, r.left, r.top,
+ r.right - r.left, r.bottom - r.top, TRUE);
+ }
+}
+
+static DWORD WINAPI SendThreadProc(LPVOID param)
+{
+ bool finishedOk;
+ SendThreadData* td = (SendThreadData*)param;
+
+ if (td->sendURL.empty()) {
+ finishedOk = false;
+ LogMessage("No server URL, not sending report");
+ } else {
+ google_breakpad::CrashReportSender sender(L"");
+ finishedOk = (sender.SendCrashReport(td->sendURL,
+ td->queryParameters,
+ td->files,
+ &td->serverResponse)
+ == google_breakpad::RESULT_SUCCEEDED);
+ if (finishedOk) {
+ LogMessage("Crash report submitted successfully");
+ }
+ else {
+ // get an error string and print it to the log
+ //XXX: would be nice to get the HTTP status code here, filed:
+ // http://code.google.com/p/google-breakpad/issues/detail?id=220
+ LogMessage(FormatLastError());
+ }
+ }
+
+ PostMessage(td->hDlg, WM_UPLOADCOMPLETE, finishedOk ? 1 : 0, 0);
+
+ return 0;
+}
+
+static void EndCrashReporterDialog(HWND hwndDlg, int code)
+{
+ // Save the current values to the registry
+ wchar_t email[MAX_EMAIL_LENGTH];
+ GetDlgItemTextW(hwndDlg, IDC_EMAILTEXT, email,
+ sizeof(email) / sizeof(email[0]));
+ SetStringKey(gCrashReporterKey.c_str(), EMAIL_VALUE, email);
+
+ SetBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE,
+ IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK) != 0);
+ SetBoolKey(gCrashReporterKey.c_str(), EMAIL_ME_VALUE,
+ IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK) != 0);
+ SetBoolKey(gCrashReporterKey.c_str(), SUBMIT_REPORT_VALUE,
+ IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0);
+
+ EndDialog(hwndDlg, code);
+}
+
+static void MaybeResizeProgressText(HWND hwndDlg)
+{
+ HWND hwndProgress = GetDlgItem(hwndDlg, IDC_PROGRESSTEXT);
+ HDC hdc = GetDC(hwndProgress);
+ HFONT hfont = (HFONT)SendMessage(hwndProgress, WM_GETFONT, 0, 0);
+ if (hfont)
+ SelectObject(hdc, hfont);
+ SIZE size;
+ RECT rect;
+ GetRelativeRect(hwndProgress, hwndDlg, &rect);
+
+ wchar_t text[1024];
+ GetWindowText(hwndProgress, text, 1024);
+
+ if (!GetTextExtentPoint32(hdc, text, wcslen(text), &size))
+ return;
+
+ if (size.cx < (rect.right - rect.left))
+ return;
+
+ // Figure out how much we need to resize things vertically
+ // This is sort of a fudge, but it should be good enough.
+ int wantedHeight = size.cy *
+ (int)ceil((float)size.cx / (float)(rect.right - rect.left));
+ int diff = wantedHeight - (rect.bottom - rect.top);
+ if (diff <= 0)
+ return;
+
+ MoveWindow(hwndProgress, rect.left, rect.top,
+ rect.right - rect.left,
+ wantedHeight,
+ TRUE);
+
+ gAttachedBottom.clear();
+ gAttachedBottom.insert(IDC_CLOSEBUTTON);
+ gAttachedBottom.insert(IDC_RESTARTBUTTON);
+
+ StretchDialog(hwndDlg, diff);
+
+ for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) {
+ gAttachedBottom.insert(kDefaultAttachedBottom[i]);
+ }
+}
+
+static void MaybeSendReport(HWND hwndDlg)
+{
+ if (!IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK)) {
+ EndCrashReporterDialog(hwndDlg, 0);
+ return;
+ }
+
+ // disable all the form controls
+ EnableWindow(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK), false);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), false);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), false);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), false);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), false);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), false);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CLOSEBUTTON), false);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTBUTTON), false);
+
+ SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, Str(ST_REPORTDURINGSUBMIT).c_str());
+ MaybeResizeProgressText(hwndDlg);
+ // start throbber
+ // play entire AVI, and loop
+ Animate_Play(GetDlgItem(hwndDlg, IDC_THROBBER), 0, -1, -1);
+ SetDlgItemVisible(hwndDlg, IDC_THROBBER, true);
+ gThreadHandle = nullptr;
+ gSendData.hDlg = hwndDlg;
+ gSendData.queryParameters = gQueryParameters;
+
+ gThreadHandle = CreateThread(nullptr, 0, SendThreadProc, &gSendData, 0,
+ nullptr);
+}
+
+static void RestartApplication()
+{
+ wstring cmdLine;
+
+ for (unsigned int i = 0; i < gRestartArgs.size(); i++) {
+ cmdLine += L"\"" + UTF8ToWide(gRestartArgs[i]) + L"\" ";
+ }
+
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_SHOWNORMAL;
+ ZeroMemory(&pi, sizeof(pi));
+
+ if (CreateProcess(nullptr, (LPWSTR)cmdLine.c_str(), nullptr, nullptr, FALSE,
+ 0, nullptr, nullptr, &si, &pi)) {
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+}
+
+static void ShowReportInfo(HWND hwndDlg)
+{
+ wstring description;
+
+ for (map<wstring,wstring>::const_iterator i = gQueryParameters.begin();
+ i != gQueryParameters.end();
+ i++) {
+ description += i->first;
+ description += L": ";
+ description += i->second;
+ description += L"\n";
+ }
+
+ description += L"\n";
+ description += Str(ST_EXTRAREPORTINFO);
+
+ SetDlgItemText(hwndDlg, IDC_VIEWREPORTTEXT, description.c_str());
+}
+
+static void UpdateURL(HWND hwndDlg)
+{
+ if (IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK)) {
+ gQueryParameters[L"URL"] = gURLParameter;
+ } else {
+ gQueryParameters.erase(L"URL");
+ }
+}
+
+static void UpdateEmail(HWND hwndDlg)
+{
+ if (IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK)) {
+ wchar_t email[MAX_EMAIL_LENGTH];
+ GetDlgItemTextW(hwndDlg, IDC_EMAILTEXT, email,
+ sizeof(email) / sizeof(email[0]));
+ gQueryParameters[L"Email"] = email;
+ if (IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK))
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), true);
+ } else {
+ gQueryParameters.erase(L"Email");
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), false);
+ }
+}
+
+static void UpdateComment(HWND hwndDlg)
+{
+ wchar_t comment[MAX_COMMENT_LENGTH + 1];
+ GetDlgItemTextW(hwndDlg, IDC_COMMENTTEXT, comment,
+ sizeof(comment) / sizeof(comment[0]));
+ if (wcslen(comment) > 0)
+ gQueryParameters[L"Comments"] = comment;
+ else
+ gQueryParameters.erase(L"Comments");
+}
+
+/*
+ * Dialog procedure for the "view report" dialog.
+ */
+static BOOL CALLBACK ViewReportDialogProc(HWND hwndDlg, UINT message,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (message) {
+ case WM_INITDIALOG: {
+ SetWindowText(hwndDlg, Str(ST_VIEWREPORTTITLE).c_str());
+ SetDlgItemText(hwndDlg, IDOK, Str(ST_OK).c_str());
+ SendDlgItemMessage(hwndDlg, IDC_VIEWREPORTTEXT,
+ EM_SETTARGETDEVICE, (WPARAM)nullptr, 0);
+ ShowReportInfo(hwndDlg);
+ SetFocus(GetDlgItem(hwndDlg, IDOK));
+ return FALSE;
+ }
+
+ case WM_COMMAND: {
+ if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDOK)
+ EndDialog(hwndDlg, 0);
+ return FALSE;
+ }
+ }
+ return FALSE;
+}
+
+// Return the number of bytes this string will take encoded
+// in UTF-8
+static inline int BytesInUTF8(wchar_t* str)
+{
+ // Just count size of buffer for UTF-8, minus one
+ // (we don't need to count the null terminator)
+ return WideCharToMultiByte(CP_UTF8, 0, str, -1,
+ nullptr, 0, nullptr, nullptr) - 1;
+}
+
+// Calculate the length of the text in this edit control (in bytes,
+// in the UTF-8 encoding) after replacing the current selection
+// with |insert|.
+static int NewTextLength(HWND hwndEdit, wchar_t* insert)
+{
+ wchar_t current[MAX_COMMENT_LENGTH + 1];
+
+ GetWindowText(hwndEdit, current, MAX_COMMENT_LENGTH + 1);
+ DWORD selStart, selEnd;
+ SendMessage(hwndEdit, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd);
+
+ int selectionLength = 0;
+ if (selEnd - selStart > 0) {
+ wchar_t selection[MAX_COMMENT_LENGTH + 1];
+ google_breakpad::WindowsStringUtils::safe_wcsncpy(selection,
+ MAX_COMMENT_LENGTH + 1,
+ current + selStart,
+ selEnd - selStart);
+ selection[selEnd - selStart] = '\0';
+ selectionLength = BytesInUTF8(selection);
+ }
+
+ // current string length + replacement text length
+ // - replaced selection length
+ return BytesInUTF8(current) + BytesInUTF8(insert) - selectionLength;
+}
+
+// Window procedure for subclassing edit controls
+static LRESULT CALLBACK EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+ LPARAM lParam)
+{
+ static WNDPROC super = nullptr;
+
+ if (super == nullptr)
+ super = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+
+ switch (uMsg) {
+ case WM_PAINT: {
+ HDC hdc;
+ PAINTSTRUCT ps;
+ RECT r;
+ wchar_t windowText[1024];
+
+ GetWindowText(hwnd, windowText, 1024);
+ // if the control contains text or is focused, draw it normally
+ if (GetFocus() == hwnd || windowText[0] != '\0')
+ return CallWindowProc(super, hwnd, uMsg, wParam, lParam);
+
+ GetClientRect(hwnd, &r);
+ hdc = BeginPaint(hwnd, &ps);
+ FillRect(hdc, &r, GetSysColorBrush(IsWindowEnabled(hwnd)
+ ? COLOR_WINDOW : COLOR_BTNFACE));
+ SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT));
+ SelectObject(hdc, (HFONT)GetStockObject(DEFAULT_GUI_FONT));
+ SetBkMode(hdc, TRANSPARENT);
+ wchar_t* txt = (wchar_t*)GetProp(hwnd, L"PROP_GRAYTEXT");
+ // Get the actual edit control rect
+ CallWindowProc(super, hwnd, EM_GETRECT, 0, (LPARAM)&r);
+ UINT format = DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK | DT_INTERNAL;
+ if (gRTLlayout)
+ format |= DT_RIGHT;
+ if (txt)
+ DrawText(hdc, txt, wcslen(txt), &r, format);
+ EndPaint(hwnd, &ps);
+ return 0;
+ }
+
+ // We handle WM_CHAR and WM_PASTE to limit the comment box to 500
+ // bytes in UTF-8.
+ case WM_CHAR: {
+ // Leave accelerator keys and non-printing chars (except LF) alone
+ if (wParam & (1<<24) || wParam & (1<<29) ||
+ (wParam < ' ' && wParam != '\n'))
+ break;
+
+ wchar_t ch[2] = { (wchar_t)wParam, 0 };
+ if (NewTextLength(hwnd, ch) > MAX_COMMENT_LENGTH)
+ return 0;
+
+ break;
+ }
+
+ case WM_PASTE: {
+ if (IsClipboardFormatAvailable(CF_UNICODETEXT) &&
+ OpenClipboard(hwnd)) {
+ HGLOBAL hg = GetClipboardData(CF_UNICODETEXT);
+ wchar_t* pastedText = (wchar_t*)GlobalLock(hg);
+ int newSize = 0;
+
+ if (pastedText)
+ newSize = NewTextLength(hwnd, pastedText);
+
+ GlobalUnlock(hg);
+ CloseClipboard();
+
+ if (newSize > MAX_COMMENT_LENGTH)
+ return 0;
+ }
+ break;
+ }
+
+ case WM_SETFOCUS:
+ case WM_KILLFOCUS: {
+ RECT r;
+ GetClientRect(hwnd, &r);
+ InvalidateRect(hwnd, &r, TRUE);
+ break;
+ }
+
+ case WM_DESTROY: {
+ // cleanup our property
+ HGLOBAL hData = RemoveProp(hwnd, L"PROP_GRAYTEXT");
+ if (hData)
+ GlobalFree(hData);
+ }
+ }
+
+ return CallWindowProc(super, hwnd, uMsg, wParam, lParam);
+}
+
+// Resize a control to fit this text
+static int ResizeControl(HWND hwndButton, RECT& rect, wstring text,
+ bool shiftLeft, int userDefinedPadding)
+{
+ HDC hdc = GetDC(hwndButton);
+ HFONT hfont = (HFONT)SendMessage(hwndButton, WM_GETFONT, 0, 0);
+ if (hfont)
+ SelectObject(hdc, hfont);
+ SIZE size, oldSize;
+ int sizeDiff = 0;
+
+ wchar_t oldText[1024];
+ GetWindowText(hwndButton, oldText, 1024);
+
+ if (GetTextExtentPoint32(hdc, text.c_str(), text.length(), &size)
+ // default text on the button
+ && GetTextExtentPoint32(hdc, oldText, wcslen(oldText), &oldSize)) {
+ /*
+ Expand control widths to accomidate wider text strings. For most
+ controls (including buttons) the text padding is defined by the
+ dialog's rc file. Some controls (such as checkboxes) have padding
+ that extends to the end of the dialog, in which case we ignore the
+ rc padding and rely on a user defined value passed in through
+ userDefinedPadding.
+ */
+ int textIncrease = size.cx - oldSize.cx;
+ if (textIncrease < 0)
+ return 0;
+ int existingTextPadding;
+ if (userDefinedPadding == 0)
+ existingTextPadding = (rect.right - rect.left) - oldSize.cx;
+ else
+ existingTextPadding = userDefinedPadding;
+ sizeDiff = textIncrease + existingTextPadding;
+
+ if (shiftLeft) {
+ // shift left by the amount the button should grow
+ rect.left -= sizeDiff;
+ }
+ else {
+ // grow right instead
+ rect.right += sizeDiff;
+ }
+ MoveWindow(hwndButton, rect.left, rect.top,
+ rect.right - rect.left,
+ rect.bottom - rect.top,
+ TRUE);
+ }
+ return sizeDiff;
+}
+
+// The window was resized horizontally, so widen some of our
+// controls to make use of the space
+static void StretchControlsToFit(HWND hwndDlg)
+{
+ int controls[] = {
+ IDC_DESCRIPTIONTEXT,
+ IDC_SUBMITREPORTCHECK,
+ IDC_COMMENTTEXT,
+ IDC_INCLUDEURLCHECK,
+ IDC_EMAILMECHECK,
+ IDC_EMAILTEXT,
+ IDC_PROGRESSTEXT
+ };
+
+ RECT dlgRect;
+ GetClientRect(hwndDlg, &dlgRect);
+
+ for (int i=0; i<sizeof(controls)/sizeof(controls[0]); i++) {
+ RECT r;
+ HWND hwndControl = GetDlgItem(hwndDlg, controls[i]);
+ GetRelativeRect(hwndControl, hwndDlg, &r);
+ // 6 pixel spacing on the right
+ if (r.right + 6 != dlgRect.right) {
+ r.right = dlgRect.right - 6;
+ MoveWindow(hwndControl, r.left, r.top,
+ r.right - r.left,
+ r.bottom - r.top,
+ TRUE);
+ }
+ }
+}
+
+static void SubmitReportChecked(HWND hwndDlg)
+{
+ bool enabled = (IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT),
+ enabled && (IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK)
+ != 0));
+ SetDlgItemVisible(hwndDlg, IDC_PROGRESSTEXT, enabled);
+}
+
+static INT_PTR DialogBoxParamMaybeRTL(UINT idd, HWND hwndParent,
+ DLGPROC dlgProc, LPARAM param)
+{
+ INT_PTR rv = 0;
+ if (gRTLlayout) {
+ // We need to toggle the WS_EX_LAYOUTRTL style flag on the dialog
+ // template.
+ HRSRC hDialogRC = FindResource(nullptr, MAKEINTRESOURCE(idd),
+ RT_DIALOG);
+ HGLOBAL hDlgTemplate = LoadResource(nullptr, hDialogRC);
+ DLGTEMPLATEEX* pDlgTemplate = (DLGTEMPLATEEX*)LockResource(hDlgTemplate);
+ unsigned long sizeDlg = SizeofResource(nullptr, hDialogRC);
+ HGLOBAL hMyDlgTemplate = GlobalAlloc(GPTR, sizeDlg);
+ DLGTEMPLATEEX* pMyDlgTemplate =
+ (DLGTEMPLATEEX*)GlobalLock(hMyDlgTemplate);
+ memcpy(pMyDlgTemplate, pDlgTemplate, sizeDlg);
+
+ pMyDlgTemplate->exStyle |= WS_EX_LAYOUTRTL;
+
+ rv = DialogBoxIndirectParam(nullptr, (LPCDLGTEMPLATE)pMyDlgTemplate,
+ hwndParent, dlgProc, param);
+ GlobalUnlock(hMyDlgTemplate);
+ GlobalFree(hMyDlgTemplate);
+ }
+ else {
+ rv = DialogBoxParam(nullptr, MAKEINTRESOURCE(idd), hwndParent,
+ dlgProc, param);
+ }
+
+ return rv;
+}
+
+
+static BOOL CALLBACK CrashReporterDialogProc(HWND hwndDlg, UINT message,
+ WPARAM wParam, LPARAM lParam)
+{
+ static int sHeight = 0;
+
+ bool success;
+ bool enabled;
+
+ switch (message) {
+ case WM_INITDIALOG: {
+ GetThemeSizes(hwndDlg);
+ RECT r;
+ GetClientRect(hwndDlg, &r);
+ sHeight = r.bottom - r.top;
+
+ SetWindowText(hwndDlg, Str(ST_CRASHREPORTERTITLE).c_str());
+ HICON hIcon = LoadIcon(GetModuleHandle(nullptr),
+ MAKEINTRESOURCE(IDI_MAINICON));
+ SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
+ SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
+
+ // resize the "View Report" button based on the string length
+ RECT rect;
+ HWND hwnd = GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON);
+ GetRelativeRect(hwnd, hwndDlg, &rect);
+ ResizeControl(hwnd, rect, Str(ST_VIEWREPORT), false, 0);
+ SetDlgItemText(hwndDlg, IDC_VIEWREPORTBUTTON, Str(ST_VIEWREPORT).c_str());
+
+ hwnd = GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK);
+ GetRelativeRect(hwnd, hwndDlg, &rect);
+ long maxdiff = ResizeControl(hwnd, rect, Str(ST_CHECKSUBMIT), false,
+ gCheckboxPadding);
+ SetDlgItemText(hwndDlg, IDC_SUBMITREPORTCHECK,
+ Str(ST_CHECKSUBMIT).c_str());
+
+ if (!CheckBoolKey(gCrashReporterKey.c_str(),
+ SUBMIT_REPORT_VALUE, &enabled))
+ enabled = ShouldEnableSending();
+
+ CheckDlgButton(hwndDlg, IDC_SUBMITREPORTCHECK, enabled ? BST_CHECKED
+ : BST_UNCHECKED);
+ SubmitReportChecked(hwndDlg);
+
+ HWND hwndComment = GetDlgItem(hwndDlg, IDC_COMMENTTEXT);
+ WNDPROC OldWndProc = (WNDPROC)SetWindowLongPtr(hwndComment,
+ GWLP_WNDPROC,
+ (LONG_PTR)EditSubclassProc);
+
+ // Subclass comment edit control to get placeholder text
+ SetWindowLongPtr(hwndComment, GWLP_USERDATA, (LONG_PTR)OldWndProc);
+ wstring commentGrayText = Str(ST_COMMENTGRAYTEXT);
+ wchar_t* hMem = (wchar_t*)GlobalAlloc(GPTR, (commentGrayText.length() + 1)*sizeof(wchar_t));
+ wcscpy(hMem, commentGrayText.c_str());
+ SetProp(hwndComment, L"PROP_GRAYTEXT", hMem);
+
+ hwnd = GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK);
+ GetRelativeRect(hwnd, hwndDlg, &rect);
+ long diff = ResizeControl(hwnd, rect, Str(ST_CHECKURL), false,
+ gCheckboxPadding);
+ maxdiff = std::max(diff, maxdiff);
+ SetDlgItemText(hwndDlg, IDC_INCLUDEURLCHECK, Str(ST_CHECKURL).c_str());
+
+ // want this on by default
+ if (CheckBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE, &enabled) &&
+ !enabled) {
+ CheckDlgButton(hwndDlg, IDC_INCLUDEURLCHECK, BST_UNCHECKED);
+ } else {
+ CheckDlgButton(hwndDlg, IDC_INCLUDEURLCHECK, BST_CHECKED);
+ }
+
+ hwnd = GetDlgItem(hwndDlg, IDC_EMAILMECHECK);
+ GetRelativeRect(hwnd, hwndDlg, &rect);
+ diff = ResizeControl(hwnd, rect, Str(ST_CHECKEMAIL), false,
+ gCheckboxPadding);
+ maxdiff = std::max(diff, maxdiff);
+ SetDlgItemText(hwndDlg, IDC_EMAILMECHECK, Str(ST_CHECKEMAIL).c_str());
+
+ if (CheckBoolKey(gCrashReporterKey.c_str(), EMAIL_ME_VALUE, &enabled) &&
+ enabled) {
+ CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_CHECKED);
+ } else {
+ CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_UNCHECKED);
+ }
+
+ wstring email;
+ if (GetStringKey(gCrashReporterKey.c_str(), EMAIL_VALUE, email)) {
+ SetDlgItemText(hwndDlg, IDC_EMAILTEXT, email.c_str());
+ }
+
+ // Subclass email edit control to get placeholder text
+ HWND hwndEmail = GetDlgItem(hwndDlg, IDC_EMAILTEXT);
+ OldWndProc = (WNDPROC)SetWindowLongPtr(hwndEmail,
+ GWLP_WNDPROC,
+ (LONG_PTR)EditSubclassProc);
+ SetWindowLongPtr(hwndEmail, GWLP_USERDATA, (LONG_PTR)OldWndProc);
+ wstring emailGrayText = Str(ST_EMAILGRAYTEXT);
+ hMem = (wchar_t*)GlobalAlloc(GPTR, (emailGrayText.length() + 1)*sizeof(wchar_t));
+ wcscpy(hMem, emailGrayText.c_str());
+ SetProp(hwndEmail, L"PROP_GRAYTEXT", hMem);
+
+ SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, Str(ST_REPORTPRESUBMIT).c_str());
+
+ RECT closeRect;
+ HWND hwndClose = GetDlgItem(hwndDlg, IDC_CLOSEBUTTON);
+ GetRelativeRect(hwndClose, hwndDlg, &closeRect);
+
+ RECT restartRect;
+ HWND hwndRestart = GetDlgItem(hwndDlg, IDC_RESTARTBUTTON);
+ GetRelativeRect(hwndRestart, hwndDlg, &restartRect);
+
+ // set the close button text and shift the buttons around
+ // since the size may need to change
+ int sizeDiff = ResizeControl(hwndClose, closeRect, Str(ST_QUIT),
+ true, 0);
+ restartRect.left -= sizeDiff;
+ restartRect.right -= sizeDiff;
+ SetDlgItemText(hwndDlg, IDC_CLOSEBUTTON, Str(ST_QUIT).c_str());
+
+ if (gRestartArgs.size() > 0) {
+ // Resize restart button to fit text
+ ResizeControl(hwndRestart, restartRect, Str(ST_RESTART), true, 0);
+ SetDlgItemText(hwndDlg, IDC_RESTARTBUTTON, Str(ST_RESTART).c_str());
+ } else {
+ // No restart arguments, so just hide the restart button
+ SetDlgItemVisible(hwndDlg, IDC_RESTARTBUTTON, false);
+ }
+ // See if we need to widen the window
+ // Leave 6 pixels on either side + 6 pixels between the buttons
+ int neededSize = closeRect.right - closeRect.left +
+ restartRect.right - restartRect.left + 6 * 3;
+ GetClientRect(hwndDlg, &r);
+ // We may already have resized one of the checkboxes above
+ maxdiff = std::max(maxdiff, neededSize - (r.right - r.left));
+
+ if (maxdiff > 0) {
+ // widen window
+ GetWindowRect(hwndDlg, &r);
+ r.right += maxdiff;
+ MoveWindow(hwndDlg, r.left, r.top,
+ r.right - r.left, r.bottom - r.top, TRUE);
+ // shift both buttons right
+ if (restartRect.left + maxdiff < 6)
+ maxdiff += 6;
+ closeRect.left += maxdiff;
+ closeRect.right += maxdiff;
+ restartRect.left += maxdiff;
+ restartRect.right += maxdiff;
+ MoveWindow(hwndClose, closeRect.left, closeRect.top,
+ closeRect.right - closeRect.left,
+ closeRect.bottom - closeRect.top,
+ TRUE);
+ StretchControlsToFit(hwndDlg);
+ }
+ // need to move the restart button regardless
+ MoveWindow(hwndRestart, restartRect.left, restartRect.top,
+ restartRect.right - restartRect.left,
+ restartRect.bottom - restartRect.top,
+ TRUE);
+
+ // Resize the description text last, in case the window was resized
+ // before this.
+ SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT,
+ EM_SETEVENTMASK, (WPARAM)nullptr,
+ ENM_REQUESTRESIZE);
+
+ wstring description = Str(ST_CRASHREPORTERHEADER);
+ description += L"\n\n";
+ description += Str(ST_CRASHREPORTERDESCRIPTION);
+ SetDlgItemText(hwndDlg, IDC_DESCRIPTIONTEXT, description.c_str());
+
+
+ // Make the title bold.
+ CHARFORMAT fmt = { 0, };
+ fmt.cbSize = sizeof(fmt);
+ fmt.dwMask = CFM_BOLD;
+ fmt.dwEffects = CFE_BOLD;
+ SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL,
+ 0, Str(ST_CRASHREPORTERHEADER).length());
+ SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETCHARFORMAT,
+ SCF_SELECTION, (LPARAM)&fmt);
+ SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL, 0, 0);
+ // Force redraw.
+ SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT,
+ EM_SETTARGETDEVICE, (WPARAM)nullptr, 0);
+ // Force resize.
+ SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT,
+ EM_REQUESTRESIZE, 0, 0);
+
+ // if no URL was given, hide the URL checkbox
+ if (gQueryParameters.find(L"URL") == gQueryParameters.end()) {
+ RECT urlCheckRect, emailCheckRect;
+ GetWindowRect(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), &urlCheckRect);
+ GetWindowRect(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), &emailCheckRect);
+
+ SetDlgItemVisible(hwndDlg, IDC_INCLUDEURLCHECK, false);
+
+ gAttachedBottom.erase(IDC_VIEWREPORTBUTTON);
+ gAttachedBottom.erase(IDC_SUBMITREPORTCHECK);
+ gAttachedBottom.erase(IDC_COMMENTTEXT);
+
+ StretchDialog(hwndDlg, urlCheckRect.top - emailCheckRect.top);
+
+ gAttachedBottom.insert(IDC_VIEWREPORTBUTTON);
+ gAttachedBottom.insert(IDC_SUBMITREPORTCHECK);
+ gAttachedBottom.insert(IDC_COMMENTTEXT);
+ }
+
+ MaybeResizeProgressText(hwndDlg);
+
+ // Open the AVI resource for the throbber
+ Animate_Open(GetDlgItem(hwndDlg, IDC_THROBBER),
+ MAKEINTRESOURCE(IDR_THROBBER));
+
+ UpdateURL(hwndDlg);
+ UpdateEmail(hwndDlg);
+
+ SetFocus(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK));
+ return FALSE;
+ }
+ case WM_SIZE: {
+ ReflowDialog(hwndDlg, HIWORD(lParam) - sHeight);
+ sHeight = HIWORD(lParam);
+ InvalidateRect(hwndDlg, nullptr, TRUE);
+ return FALSE;
+ }
+ case WM_NOTIFY: {
+ NMHDR* notification = reinterpret_cast<NMHDR*>(lParam);
+ if (notification->code == EN_REQUESTRESIZE) {
+ // Resizing the rich edit control to fit the description text.
+ REQRESIZE* reqresize = reinterpret_cast<REQRESIZE*>(lParam);
+ RECT newSize = reqresize->rc;
+ RECT oldSize;
+ GetRelativeRect(notification->hwndFrom, hwndDlg, &oldSize);
+
+ // resize the text box as requested
+ MoveWindow(notification->hwndFrom, newSize.left, newSize.top,
+ newSize.right - newSize.left, newSize.bottom - newSize.top,
+ TRUE);
+
+ // Resize the dialog to fit (the WM_SIZE handler will move the controls)
+ StretchDialog(hwndDlg, newSize.bottom - oldSize.bottom);
+ }
+ return FALSE;
+ }
+ case WM_COMMAND: {
+ if (HIWORD(wParam) == BN_CLICKED) {
+ switch(LOWORD(wParam)) {
+ case IDC_VIEWREPORTBUTTON:
+ DialogBoxParamMaybeRTL(IDD_VIEWREPORTDIALOG, hwndDlg,
+ (DLGPROC)ViewReportDialogProc, 0);
+ break;
+ case IDC_SUBMITREPORTCHECK:
+ SubmitReportChecked(hwndDlg);
+ break;
+ case IDC_INCLUDEURLCHECK:
+ UpdateURL(hwndDlg);
+ break;
+ case IDC_EMAILMECHECK:
+ UpdateEmail(hwndDlg);
+ break;
+ case IDC_CLOSEBUTTON:
+ MaybeSendReport(hwndDlg);
+ break;
+ case IDC_RESTARTBUTTON:
+ RestartApplication();
+ MaybeSendReport(hwndDlg);
+ break;
+ }
+ } else if (HIWORD(wParam) == EN_CHANGE) {
+ switch(LOWORD(wParam)) {
+ case IDC_EMAILTEXT:
+ UpdateEmail(hwndDlg);
+ break;
+ case IDC_COMMENTTEXT:
+ UpdateComment(hwndDlg);
+ }
+ }
+
+ return FALSE;
+ }
+ case WM_UPLOADCOMPLETE: {
+ WaitForSingleObject(gThreadHandle, INFINITE);
+ success = (wParam == 1);
+ SendCompleted(success, WideToUTF8(gSendData.serverResponse));
+ // hide throbber
+ Animate_Stop(GetDlgItem(hwndDlg, IDC_THROBBER));
+ SetDlgItemVisible(hwndDlg, IDC_THROBBER, false);
+
+ SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT,
+ success ?
+ Str(ST_REPORTSUBMITSUCCESS).c_str() :
+ Str(ST_SUBMITFAILED).c_str());
+ MaybeResizeProgressText(hwndDlg);
+ // close dialog after 5 seconds
+ SetTimer(hwndDlg, 0, 5000, nullptr);
+ //
+ return TRUE;
+ }
+
+ case WM_LBUTTONDOWN: {
+ HWND hwndEmail = GetDlgItem(hwndDlg, IDC_EMAILTEXT);
+ POINT p = { LOWORD(lParam), HIWORD(lParam) };
+ // if the email edit control is clicked, enable it,
+ // check the email checkbox, and focus the email edit control
+ if (ChildWindowFromPoint(hwndDlg, p) == hwndEmail &&
+ IsWindowEnabled(GetDlgItem(hwndDlg, IDC_RESTARTBUTTON)) &&
+ !IsWindowEnabled(hwndEmail) &&
+ IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0) {
+ CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_CHECKED);
+ UpdateEmail(hwndDlg);
+ SetFocus(hwndEmail);
+ }
+ break;
+ }
+
+ case WM_TIMER: {
+ // The "1" gets used down in UIShowCrashUI to indicate that we at least
+ // tried to send the report.
+ EndCrashReporterDialog(hwndDlg, 1);
+ return FALSE;
+ }
+
+ case WM_CLOSE: {
+ EndCrashReporterDialog(hwndDlg, 0);
+ return FALSE;
+ }
+ }
+ return FALSE;
+}
+
+static wstring UTF8ToWide(const string& utf8, bool *success)
+{
+ wchar_t* buffer = nullptr;
+ int buffer_size = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(),
+ -1, nullptr, 0);
+ if(buffer_size == 0) {
+ if (success)
+ *success = false;
+ return L"";
+ }
+
+ buffer = new wchar_t[buffer_size];
+ if(buffer == nullptr) {
+ if (success)
+ *success = false;
+ return L"";
+ }
+
+ MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(),
+ -1, buffer, buffer_size);
+ wstring str = buffer;
+ delete [] buffer;
+
+ if (success)
+ *success = true;
+
+ return str;
+}
+
+static string WideToMBCP(const wstring& wide,
+ unsigned int cp,
+ bool* success = nullptr)
+{
+ char* buffer = nullptr;
+ int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(),
+ -1, nullptr, 0, nullptr, nullptr);
+ if(buffer_size == 0) {
+ if (success)
+ *success = false;
+ return "";
+ }
+
+ buffer = new char[buffer_size];
+ if(buffer == nullptr) {
+ if (success)
+ *success = false;
+ return "";
+ }
+
+ WideCharToMultiByte(cp, 0, wide.c_str(),
+ -1, buffer, buffer_size, nullptr, nullptr);
+ string mb = buffer;
+ delete [] buffer;
+
+ if (success)
+ *success = true;
+
+ return mb;
+}
+
+string WideToUTF8(const wstring& wide, bool* success)
+{
+ return WideToMBCP(wide, CP_UTF8, success);
+}
+
+/* === Crashreporter UI Functions === */
+
+bool UIInit()
+{
+ for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) {
+ gAttachedBottom.insert(kDefaultAttachedBottom[i]);
+ }
+
+ DoInitCommonControls();
+
+ return true;
+}
+
+void UIShutdown()
+{
+}
+
+void UIShowDefaultUI()
+{
+ MessageBox(nullptr, Str(ST_CRASHREPORTERDEFAULT).c_str(),
+ L"Crash Reporter",
+ MB_OK | MB_ICONSTOP);
+}
+
+static bool CanUseMainCrashReportServer()
+{
+ // Any NT from 6.0 and above is fine.
+ if (IsWindowsVersionOrGreater(6, 0, 0)) {
+ return true;
+ }
+
+ // On NT 5 servers, we need Server 2003 SP2.
+ if (IsWindowsServer()) {
+ return IsWindowsVersionOrGreater(5, 2, 2);
+ }
+
+ // Otherwise we have an NT 5 client.
+ // We need exactly XP SP3 (version 5.1 SP3 but not version 5.2).
+ return (IsWindowsVersionOrGreater(5, 1, 3) &&
+ !IsWindowsVersionOrGreater(5, 2, 0));
+}
+
+bool UIShowCrashUI(const StringTable& files,
+ const StringTable& queryParameters,
+ const string& sendURL,
+ const vector<string>& restartArgs)
+{
+ gSendData.hDlg = nullptr;
+ gSendData.sendURL = UTF8ToWide(sendURL);
+
+ // Older Windows don't support the crash report server's crypto.
+ // This is a hack to use an alternate server.
+ if (!CanUseMainCrashReportServer() &&
+ gSendData.sendURL.find(SENDURL_ORIGINAL) == 0) {
+ gSendData.sendURL.replace(0, ARRAYSIZE(SENDURL_ORIGINAL) - 1,
+ SENDURL_XPSP2);
+ }
+
+ for (StringTable::const_iterator i = files.begin();
+ i != files.end();
+ i++) {
+ gSendData.files[UTF8ToWide(i->first)] = UTF8ToWide(i->second);
+ }
+
+ for (StringTable::const_iterator i = queryParameters.begin();
+ i != queryParameters.end();
+ i++) {
+ gQueryParameters[UTF8ToWide(i->first)] = UTF8ToWide(i->second);
+ }
+
+ if (gQueryParameters.find(L"Vendor") != gQueryParameters.end()) {
+ gCrashReporterKey = L"Software\\";
+ if (!gQueryParameters[L"Vendor"].empty()) {
+ gCrashReporterKey += gQueryParameters[L"Vendor"] + L"\\";
+ }
+ gCrashReporterKey += gQueryParameters[L"ProductName"] + L"\\Crash Reporter";
+ }
+
+ if (gQueryParameters.find(L"URL") != gQueryParameters.end())
+ gURLParameter = gQueryParameters[L"URL"];
+
+ gRestartArgs = restartArgs;
+
+ if (gStrings.find("isRTL") != gStrings.end() &&
+ gStrings["isRTL"] == "yes")
+ gRTLlayout = true;
+
+ return 1 == DialogBoxParamMaybeRTL(IDD_SENDDIALOG, nullptr,
+ (DLGPROC)CrashReporterDialogProc, 0);
+}
+
+void UIError_impl(const string& message)
+{
+ wstring title = Str(ST_CRASHREPORTERTITLE);
+ if (title.empty())
+ title = L"Crash Reporter Error";
+
+ MessageBox(nullptr, UTF8ToWide(message).c_str(), title.c_str(),
+ MB_OK | MB_ICONSTOP);
+}
+
+bool UIGetIniPath(string& path)
+{
+ wchar_t fileName[MAX_PATH];
+ if (GetModuleFileName(nullptr, 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];
+ HRESULT hRes = SHGetFolderPath(nullptr,
+ CSIDL_APPDATA,
+ nullptr,
+ 0,
+ path);
+ if (FAILED(hRes)) {
+ // This provides a fallback for getting the path to APPDATA by querying the
+ // registry when the call to SHGetFolderPath is unable to provide this path
+ // (Bug 513958).
+ HKEY key;
+ DWORD type, size, dwRes;
+ dwRes = ::RegOpenKeyExW(HKEY_CURRENT_USER,
+ L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders",
+ 0,
+ KEY_READ,
+ &key);
+ if (dwRes != ERROR_SUCCESS)
+ return false;
+
+ dwRes = RegQueryValueExW(key,
+ L"AppData",
+ nullptr,
+ &type,
+ (LPBYTE)&path,
+ &size);
+ ::RegCloseKey(key);
+ // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the
+ // buffer size must not equal 0, and the buffer size be a multiple of 2.
+ if (dwRes != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0)
+ return false;
+ }
+
+ if (!vendor.empty()) {
+ PathAppend(path, UTF8ToWide(vendor).c_str());
+ }
+ PathAppend(path, UTF8ToWide(product).c_str());
+ PathAppend(path, L"Crash Reports");
+ settings_path = WideToUTF8(path);
+ return true;
+}
+
+bool UIEnsurePathExists(const string& path)
+{
+ if (CreateDirectory(UTF8ToWide(path).c_str(), nullptr) == 0) {
+ if (GetLastError() != ERROR_ALREADY_EXISTS)
+ return false;
+ }
+
+ return true;
+}
+
+bool UIFileExists(const string& path)
+{
+ DWORD attrs = GetFileAttributes(UTF8ToWide(path).c_str());
+ return (attrs != INVALID_FILE_ATTRIBUTES);
+}
+
+bool UIMoveFile(const string& oldfile, const string& newfile)
+{
+ if (oldfile == newfile)
+ return true;
+
+ return MoveFile(UTF8ToWide(oldfile).c_str(), UTF8ToWide(newfile).c_str())
+ == TRUE;
+}
+
+bool UIDeleteFile(const string& oldfile)
+{
+ return DeleteFile(UTF8ToWide(oldfile).c_str()) == TRUE;
+}
+
+ifstream* UIOpenRead(const string& filename)
+{
+ // adapted from breakpad's src/common/windows/http_upload.cc
+
+#if defined(_MSC_VER)
+ ifstream* file = new ifstream();
+ file->open(UTF8ToWide(filename).c_str(), ios::in);
+#else // GCC
+ ifstream* file = new ifstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(),
+ ios::in);
+#endif // _MSC_VER
+
+ return file;
+}
+
+ofstream* UIOpenWrite(const string& filename,
+ bool append, // append=false
+ bool binary) // binary=false
+{
+ // adapted from breakpad's src/common/windows/http_upload.cc
+ std::ios_base::openmode mode = ios::out;
+ if (append) {
+ mode = mode | ios::app;
+ }
+ if (binary) {
+ mode = mode | ios::binary;
+ }
+
+#if defined(_MSC_VER)
+ ofstream* file = new ofstream();
+ file->open(UTF8ToWide(filename).c_str(), mode);
+#else // GCC
+ ofstream* file = new ofstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(),
+ mode);
+#endif // _MSC_VER
+
+ return file;
+}
+
+struct FileData
+{
+ FILETIME timestamp;
+ wstring path;
+};
+
+static bool CompareFDTime(const FileData& fd1, const FileData& fd2)
+{
+ return CompareFileTime(&fd1.timestamp, &fd2.timestamp) > 0;
+}
+
+void UIPruneSavedDumps(const std::string& directory)
+{
+ wstring wdirectory = UTF8ToWide(directory);
+
+ WIN32_FIND_DATA fdata;
+ wstring findpath = wdirectory + L"\\*.dmp";
+ HANDLE dirlist = FindFirstFile(findpath.c_str(), &fdata);
+ if (dirlist == INVALID_HANDLE_VALUE)
+ return;
+
+ vector<FileData> dumpfiles;
+
+ for (BOOL ok = true; ok; ok = FindNextFile(dirlist, &fdata)) {
+ FileData fd = {fdata.ftLastWriteTime, wdirectory + L"\\" + fdata.cFileName};
+ dumpfiles.push_back(fd);
+ }
+
+ sort(dumpfiles.begin(), dumpfiles.end(), CompareFDTime);
+
+ while (dumpfiles.size() > kSaveCount) {
+ // get the path of the oldest file
+ wstring path = (--dumpfiles.end())->path;
+ DeleteFile(path.c_str());
+
+ // s/.dmp/.extra/
+ path.replace(path.size() - 4, 4, L".extra");
+ DeleteFile(path.c_str());
+
+ dumpfiles.pop_back();
+ }
+}
+
+void UIRunMinidumpAnalyzer(const string& exename, const string& filename)
+{
+ wstring cmdLine;
+
+ cmdLine += L"\"" + UTF8ToWide(exename) + L"\" ";
+ cmdLine += L"\"" + UTF8ToWide(filename) + L"\" ";
+
+ STARTUPINFO si = {};
+ PROCESS_INFORMATION pi = {};
+
+ si.cb = sizeof(si);
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_SHOWNORMAL;
+
+ if (CreateProcess(nullptr, (LPWSTR)cmdLine.c_str(), nullptr, nullptr, FALSE,
+ 0, nullptr, nullptr, &si, &pi)) {
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+}
diff --git a/toolkit/crashreporter/client/macbuild/Contents/Info.plist b/toolkit/crashreporter/client/macbuild/Contents/Info.plist
new file mode 100644
index 000000000..299581f52
--- /dev/null
+++ b/toolkit/crashreporter/client/macbuild/Contents/Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!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>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleDisplayName</key>
+ <string>crashreporter</string>
+ <key>CFBundleExecutable</key>
+ <string>crashreporter</string>
+ <key>CFBundleIconFile</key>
+ <string>crashreporter.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.mozilla.crashreporter</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>crashreporter</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>LSHasLocalizedDisplayName</key>
+ <true/>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/toolkit/crashreporter/client/macbuild/Contents/PkgInfo b/toolkit/crashreporter/client/macbuild/Contents/PkgInfo
new file mode 100644
index 000000000..cae6d0a58
--- /dev/null
+++ b/toolkit/crashreporter/client/macbuild/Contents/PkgInfo
@@ -0,0 +1,2 @@
+APPL????
+
diff --git a/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
new file mode 100644
index 000000000..6fe086e35
--- /dev/null
+++ b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
@@ -0,0 +1,8 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Localized versions of Info.plist keys */
+
+CFBundleName = "Crash Reporter";
+CFBundleDisplayName = "Crash Reporter";
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
new file mode 100644
index 000000000..e31ff0bfb
--- /dev/null
+++ b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBClasses</key>
+ <array>
+ <dict>
+ <key>ACTIONS</key>
+ <dict>
+ <key>closeClicked</key>
+ <string>id</string>
+ <key>emailMeClicked</key>
+ <string>id</string>
+ <key>includeURLClicked</key>
+ <string>id</string>
+ <key>restartClicked</key>
+ <string>id</string>
+ <key>submitReportClicked</key>
+ <string>id</string>
+ <key>viewReportClicked</key>
+ <string>id</string>
+ <key>viewReportOkClicked</key>
+ <string>id</string>
+ </dict>
+ <key>CLASS</key>
+ <string>CrashReporterUI</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>OUTLETS</key>
+ <dict>
+ <key>mCloseButton</key>
+ <string>NSButton</string>
+ <key>mCommentScrollView</key>
+ <string>NSScrollView</string>
+ <key>mCommentText</key>
+ <string>TextViewWithPlaceHolder</string>
+ <key>mDescriptionLabel</key>
+ <string>NSTextField</string>
+ <key>mEmailMeButton</key>
+ <string>NSButton</string>
+ <key>mEmailText</key>
+ <string>NSTextField</string>
+ <key>mErrorCloseButton</key>
+ <string>NSButton</string>
+ <key>mErrorHeaderLabel</key>
+ <string>NSTextField</string>
+ <key>mErrorLabel</key>
+ <string>NSTextField</string>
+ <key>mErrorView</key>
+ <string>NSView</string>
+ <key>mHeaderLabel</key>
+ <string>NSTextField</string>
+ <key>mIncludeURLButton</key>
+ <string>NSButton</string>
+ <key>mProgressIndicator</key>
+ <string>NSProgressIndicator</string>
+ <key>mProgressText</key>
+ <string>NSTextField</string>
+ <key>mRestartButton</key>
+ <string>NSButton</string>
+ <key>mSubmitReportButton</key>
+ <string>NSButton</string>
+ <key>mViewReportButton</key>
+ <string>NSButton</string>
+ <key>mViewReportOkButton</key>
+ <string>NSButton</string>
+ <key>mViewReportTextView</key>
+ <string>NSTextView</string>
+ <key>mViewReportWindow</key>
+ <string>NSWindow</string>
+ <key>mWindow</key>
+ <string>NSWindow</string>
+ </dict>
+ <key>SUPERCLASS</key>
+ <string>NSObject</string>
+ </dict>
+ <dict>
+ <key>ACTIONS</key>
+ <dict>
+ <key>insertTab</key>
+ <string>id</string>
+ </dict>
+ <key>CLASS</key>
+ <string>TextViewWithPlaceHolder</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>SUPERCLASS</key>
+ <string>NSTextView</string>
+ </dict>
+ <dict>
+ <key>CLASS</key>
+ <string>FirstResponder</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>SUPERCLASS</key>
+ <string>NSObject</string>
+ </dict>
+ </array>
+ <key>IBVersion</key>
+ <string>1</string>
+</dict>
+</plist>
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
new file mode 100644
index 000000000..517349ffc
--- /dev/null
+++ b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBFramework Version</key>
+ <string>629</string>
+ <key>IBOldestOS</key>
+ <integer>5</integer>
+ <key>IBOpenObjects</key>
+ <array>
+ <integer>2</integer>
+ </array>
+ <key>IBSystem Version</key>
+ <string>9C7010</string>
+ <key>targetFramework</key>
+ <string>IBCocoaFramework</string>
+</dict>
+</plist>
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
new file mode 100644
index 000000000..bfdcccb74
--- /dev/null
+++ b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
Binary files differ
diff --git a/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenuRTL.nib/classes.nib b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenuRTL.nib/classes.nib
new file mode 100644
index 000000000..e31ff0bfb
--- /dev/null
+++ b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenuRTL.nib/classes.nib
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBClasses</key>
+ <array>
+ <dict>
+ <key>ACTIONS</key>
+ <dict>
+ <key>closeClicked</key>
+ <string>id</string>
+ <key>emailMeClicked</key>
+ <string>id</string>
+ <key>includeURLClicked</key>
+ <string>id</string>
+ <key>restartClicked</key>
+ <string>id</string>
+ <key>submitReportClicked</key>
+ <string>id</string>
+ <key>viewReportClicked</key>
+ <string>id</string>
+ <key>viewReportOkClicked</key>
+ <string>id</string>
+ </dict>
+ <key>CLASS</key>
+ <string>CrashReporterUI</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>OUTLETS</key>
+ <dict>
+ <key>mCloseButton</key>
+ <string>NSButton</string>
+ <key>mCommentScrollView</key>
+ <string>NSScrollView</string>
+ <key>mCommentText</key>
+ <string>TextViewWithPlaceHolder</string>
+ <key>mDescriptionLabel</key>
+ <string>NSTextField</string>
+ <key>mEmailMeButton</key>
+ <string>NSButton</string>
+ <key>mEmailText</key>
+ <string>NSTextField</string>
+ <key>mErrorCloseButton</key>
+ <string>NSButton</string>
+ <key>mErrorHeaderLabel</key>
+ <string>NSTextField</string>
+ <key>mErrorLabel</key>
+ <string>NSTextField</string>
+ <key>mErrorView</key>
+ <string>NSView</string>
+ <key>mHeaderLabel</key>
+ <string>NSTextField</string>
+ <key>mIncludeURLButton</key>
+ <string>NSButton</string>
+ <key>mProgressIndicator</key>
+ <string>NSProgressIndicator</string>
+ <key>mProgressText</key>
+ <string>NSTextField</string>
+ <key>mRestartButton</key>
+ <string>NSButton</string>
+ <key>mSubmitReportButton</key>
+ <string>NSButton</string>
+ <key>mViewReportButton</key>
+ <string>NSButton</string>
+ <key>mViewReportOkButton</key>
+ <string>NSButton</string>
+ <key>mViewReportTextView</key>
+ <string>NSTextView</string>
+ <key>mViewReportWindow</key>
+ <string>NSWindow</string>
+ <key>mWindow</key>
+ <string>NSWindow</string>
+ </dict>
+ <key>SUPERCLASS</key>
+ <string>NSObject</string>
+ </dict>
+ <dict>
+ <key>ACTIONS</key>
+ <dict>
+ <key>insertTab</key>
+ <string>id</string>
+ </dict>
+ <key>CLASS</key>
+ <string>TextViewWithPlaceHolder</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>SUPERCLASS</key>
+ <string>NSTextView</string>
+ </dict>
+ <dict>
+ <key>CLASS</key>
+ <string>FirstResponder</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>SUPERCLASS</key>
+ <string>NSObject</string>
+ </dict>
+ </array>
+ <key>IBVersion</key>
+ <string>1</string>
+</dict>
+</plist>
diff --git a/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenuRTL.nib/info.nib b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenuRTL.nib/info.nib
new file mode 100644
index 000000000..4a2251aaf
--- /dev/null
+++ b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenuRTL.nib/info.nib
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBFramework Version</key>
+ <string>629</string>
+ <key>IBOldestOS</key>
+ <integer>5</integer>
+ <key>IBOpenObjects</key>
+ <array>
+ <integer>2</integer>
+ </array>
+ <key>IBSystem Version</key>
+ <string>9D34</string>
+ <key>targetFramework</key>
+ <string>IBCocoaFramework</string>
+</dict>
+</plist>
diff --git a/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenuRTL.nib/keyedobjects.nib b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenuRTL.nib/keyedobjects.nib
new file mode 100644
index 000000000..6c93849b9
--- /dev/null
+++ b/toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenuRTL.nib/keyedobjects.nib
Binary files differ
diff --git a/toolkit/crashreporter/client/macbuild/Contents/Resources/crashreporter.icns b/toolkit/crashreporter/client/macbuild/Contents/Resources/crashreporter.icns
new file mode 100644
index 000000000..341cd05a4
--- /dev/null
+++ b/toolkit/crashreporter/client/macbuild/Contents/Resources/crashreporter.icns
Binary files differ
diff --git a/toolkit/crashreporter/client/moz.build b/toolkit/crashreporter/client/moz.build
new file mode 100644
index 000000000..456c794af
--- /dev/null
+++ b/toolkit/crashreporter/client/moz.build
@@ -0,0 +1,78 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['OS_TARGET'] != 'Android':
+ Program('crashreporter')
+
+ UNIFIED_SOURCES += [
+ 'crashreporter.cpp',
+ ]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ UNIFIED_SOURCES += [
+ 'crashreporter_win.cpp',
+ ]
+ DEFINES['UNICODE'] = True
+ DEFINES['_UNICODE'] = True
+ USE_LIBS += [
+ 'google_breakpad_libxul_s',
+ ]
+ OS_LIBS += [
+ 'comctl32',
+ 'shell32',
+ 'wininet',
+ 'shlwapi',
+ ]
+elif CONFIG['OS_ARCH'] == 'Darwin':
+ UNIFIED_SOURCES += [
+ 'crashreporter_osx.mm',
+ 'crashreporter_unix_common.cpp',
+ ]
+ LOCAL_INCLUDES += [
+ '../google-breakpad/src/common/mac',
+ ]
+ OS_LIBS += ['-framework Cocoa']
+ USE_LIBS += [
+ 'breakpad_common_s',
+ 'breakpad_mac_common_s',
+ ]
+elif CONFIG['OS_ARCH'] == 'SunOS':
+ SOURCES += [
+ 'crashreporter_linux.cpp',
+ 'crashreporter_unix.cpp',
+ ]
+ USE_LIBS += [
+ 'breakpad_solaris_common_s',
+ ]
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ UNIFIED_SOURCES += [
+ 'crashreporter_gtk_common.cpp',
+ 'crashreporter_linux.cpp',
+ 'crashreporter_unix_common.cpp'
+ ]
+ USE_LIBS += [
+ 'breakpad_linux_common_s',
+ ]
+ OS_LIBS += CONFIG['TK_LIBS']
+ OS_LIBS += CONFIG['MOZ_GTHREAD_LIBS']
+ CXXFLAGS += CONFIG['TK_CFLAGS']
+ CXXFLAGS += CONFIG['MOZ_GTHREAD_CFLAGS']
+
+if CONFIG['OS_ARCH'] == 'Linux' or CONFIG['OS_ARCH'] == 'SunOS':
+ FINAL_TARGET_FILES += [
+ '/toolkit/themes/windows/global/throbber/Throbber-small.gif',
+ ]
+
+DEFINES['BIN_SUFFIX'] = '"%s"' % CONFIG['BIN_SUFFIX']
+
+RCINCLUDE = 'crashreporter.rc'
+
+# Don't use the STL wrappers in the crashreporter clients; they don't
+# link with -lmozalloc, and it really doesn't matter here anyway.
+DISABLE_STL_WRAPPING = True
+
+include('/toolkit/crashreporter/crashreporter.mozbuild')
diff --git a/toolkit/crashreporter/client/resource.h b/toolkit/crashreporter/client/resource.h
new file mode 100644
index 000000000..d736b0367
--- /dev/null
+++ b/toolkit/crashreporter/client/resource.h
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by crashreporter.rc
+//
+#define IDD_SENDDIALOG 102
+#define IDR_THROBBER 103
+#define IDD_VIEWREPORTDIALOG 104
+#define IDI_MAINICON 105
+#define IDC_PROGRESS 1003
+#define IDC_DESCRIPTIONTEXT 1004
+#define IDC_CLOSEBUTTON 1005
+#define IDC_VIEWREPORTBUTTON 1006
+#define IDC_SUBMITREPORTCHECK 1007
+#define IDC_EMAILMECHECK 1008
+#define IDC_EMAILTEXT 1009
+#define IDC_INCLUDEURLCHECK 1010
+#define IDC_COMMENTTEXT 1011
+#define IDC_RESTARTBUTTON 1012
+#define IDC_DESCRIPTIONLABEL 1013
+#define IDC_PROGRESSTEXT 1014
+#define IDC_THROBBER 1015
+#define IDC_VIEWREPORTTEXT 1016
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 106
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1017
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif