зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1759175 pt8 - Remove the old crash reporter code r=gsvelto
Differential Revision: https://phabricator.services.mozilla.com/D199638
This commit is contained in:
Родитель
6558ce4dac
Коммит
2ba27b6f14
|
@ -13,7 +13,7 @@ members = [
|
|||
"security/manager/ssl/osclientcerts",
|
||||
"testing/geckodriver",
|
||||
"toolkit/components/uniffi-bindgen-gecko-js",
|
||||
"toolkit/crashreporter/client-rust/app",
|
||||
"toolkit/crashreporter/client/app",
|
||||
"toolkit/crashreporter/mozwer-rust",
|
||||
"toolkit/crashreporter/rust_minidump_writer_linux",
|
||||
"toolkit/library/gtest/rust",
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
# 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
|
||||
$(call py_action,preprocessor crashreporter.app/Contents/Resources/English.lproj/InfoPlist.strings,-Fsubstitution --output-encoding utf-16 -DAPP_NAME='$(MOZ_APP_DISPLAYNAME)' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in -o $(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
|
Двоичные данные
toolkit/crashreporter/client/Throbber-small.avi
Двоичные данные
toolkit/crashreporter/client/Throbber-small.avi
Двоичный файл не отображается.
Двоичные данные
toolkit/crashreporter/client/Throbber-small.gif
Двоичные данные
toolkit/crashreporter/client/Throbber-small.gif
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 825 B |
|
@ -1,36 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-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/. */
|
||||
|
||||
//! The embedded Info.plist file.
|
||||
|
||||
const DATA: &[u8] = br#"<?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>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>crashreporter</string>
|
||||
<string>Crash Reporter</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>
|
||||
<string>Crash Reporter</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>NSRequiresAquaSystemAppearance</key>
|
||||
<false/>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>LSUIElement</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>"#;
|
||||
|
||||
const N: usize = DATA.len();
|
||||
|
||||
const PTR: *const [u8; N] = DATA.as_ptr() as *const [u8; N];
|
||||
|
||||
#[used]
|
||||
#[link_section = "__TEXT,__info_plist"]
|
||||
// # Safety
|
||||
// The array pointer is created from `DATA` (a slice pointer) with `DATA.len()` as the length.
|
||||
static PLIST: [u8; N] = unsafe { *PTR };
|
|
@ -1,847 +0,0 @@
|
|||
/* -*- 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 <iomanip>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <ctime>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#ifdef XP_LINUX
|
||||
# include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#include "json/json.h"
|
||||
#include "nss.h"
|
||||
#include "sechash.h"
|
||||
|
||||
using std::ifstream;
|
||||
using std::ios;
|
||||
using std::istream;
|
||||
using std::istringstream;
|
||||
using std::ofstream;
|
||||
using std::ostream;
|
||||
using std::ostringstream;
|
||||
using std::string;
|
||||
using std::unique_ptr;
|
||||
using std::vector;
|
||||
|
||||
namespace CrashReporter {
|
||||
|
||||
StringTable gStrings;
|
||||
Json::Value gData;
|
||||
string gSettingsPath;
|
||||
string gEventsPath;
|
||||
string gPingPath;
|
||||
int gArgc;
|
||||
char** gArgv;
|
||||
bool gAutoSubmit;
|
||||
|
||||
enum SubmissionResult { Succeeded, Failed };
|
||||
|
||||
static unique_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) {
|
||||
if (gAutoSubmit) {
|
||||
return;
|
||||
}
|
||||
|
||||
string errorMessage;
|
||||
if (!gStrings[ST_CRASHREPORTERERROR].empty()) {
|
||||
char buf[2048];
|
||||
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;
|
||||
}
|
||||
|
||||
bool ReadStrings(istream& in, StringTable& strings, bool unescape) {
|
||||
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, ios::in);
|
||||
bool success = false;
|
||||
if (f->is_open()) {
|
||||
success = ReadStrings(*f, strings, unescape);
|
||||
f->close();
|
||||
}
|
||||
|
||||
delete f;
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool ReadExtraFile(const string& aExtraDataPath, Json::Value& aExtra) {
|
||||
bool success = false;
|
||||
ifstream* f = UIOpenRead(aExtraDataPath, ios::in);
|
||||
if (f->is_open()) {
|
||||
Json::CharReaderBuilder builder;
|
||||
success = parseFromStream(builder, *f, &aExtra, nullptr);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
static bool ReadEventFile(const string& aPath, string& aEventVersion,
|
||||
string& aTime, string& aUuid, Json::Value& aData) {
|
||||
bool res = false;
|
||||
ifstream* f = UIOpenRead(aPath, ios::binary);
|
||||
|
||||
if (f->is_open()) {
|
||||
std::getline(*f, aEventVersion, '\n');
|
||||
res = f->good();
|
||||
std::getline(*f, aTime, '\n');
|
||||
res &= f->good();
|
||||
std::getline(*f, aUuid, '\n');
|
||||
res &= f->good();
|
||||
|
||||
if (res) {
|
||||
Json::CharReaderBuilder builder;
|
||||
res = parseFromStream(builder, *f, &aData, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
delete f;
|
||||
return res;
|
||||
}
|
||||
|
||||
static void OverwriteEventFile(const string& aPath, const string& aEventVersion,
|
||||
const string& aTime, const string& aUuid,
|
||||
const Json::Value& aData) {
|
||||
ofstream* f = UIOpenWrite(aPath, ios::binary | ios::trunc);
|
||||
if (f->is_open()) {
|
||||
f->write(aEventVersion.c_str(), aEventVersion.length()) << '\n';
|
||||
f->write(aTime.c_str(), aTime.length()) << '\n';
|
||||
f->write(aUuid.c_str(), aUuid.length()) << '\n';
|
||||
|
||||
Json::StreamWriterBuilder builder;
|
||||
builder["indentation"] = "";
|
||||
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
|
||||
writer->write(aData, f);
|
||||
*f << "\n";
|
||||
}
|
||||
|
||||
delete f;
|
||||
}
|
||||
|
||||
static void UpdateEventFile(const Json::Value& aExtraData, const string& aHash,
|
||||
const string& aPingUuid) {
|
||||
if (gEventsPath.empty()) {
|
||||
// If there is no path for finding the crash event, skip this step.
|
||||
return;
|
||||
}
|
||||
|
||||
string localId = CrashReporter::GetDumpLocalID();
|
||||
string path = gEventsPath + UI_DIR_SEPARATOR + localId;
|
||||
string eventVersion;
|
||||
string crashTime;
|
||||
string crashUuid;
|
||||
Json::Value eventData;
|
||||
|
||||
if (!ReadEventFile(path, eventVersion, crashTime, crashUuid, eventData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aHash.empty()) {
|
||||
eventData["MinidumpSha256Hash"] = aHash;
|
||||
}
|
||||
|
||||
if (!aPingUuid.empty()) {
|
||||
eventData["CrashPingUUID"] = aPingUuid;
|
||||
}
|
||||
|
||||
if (aExtraData.isMember("StackTraces")) {
|
||||
eventData["StackTraces"] = aExtraData["StackTraces"];
|
||||
}
|
||||
|
||||
OverwriteEventFile(path, eventVersion, crashTime, crashUuid, eventData);
|
||||
}
|
||||
|
||||
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 = CrashReporter::GetDumpLocalID();
|
||||
string fpath = gEventsPath + UI_DIR_SEPARATOR + localId + "-submission";
|
||||
ofstream* f = UIOpenWrite(fpath, ios::binary);
|
||||
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 << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
static void OpenLogFile() {
|
||||
string logPath = gSettingsPath + UI_DIR_SEPARATOR + "submit.log";
|
||||
gLogStream.reset(UIOpenWrite(logPath, ios::app));
|
||||
}
|
||||
|
||||
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, ios::trunc);
|
||||
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, ios::trunc);
|
||||
if (!file->is_open()) {
|
||||
delete file;
|
||||
return false;
|
||||
}
|
||||
|
||||
char buf[1024];
|
||||
snprintf(buf, 1024, gStrings["CrashID"].c_str(),
|
||||
responseItems["CrashID"].c_str());
|
||||
*file << buf << "\n";
|
||||
|
||||
if (responseItems.find("ViewURL") != responseItems.end()) {
|
||||
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, "");
|
||||
}
|
||||
}
|
||||
|
||||
static string ComputeDumpHash() {
|
||||
#ifdef XP_LINUX
|
||||
// On Linux we rely on the system-provided libcurl which uses nss so we have
|
||||
// to also use the system-provided nss instead of the ones we have bundled.
|
||||
const char* libnssNames[] = {
|
||||
"libnss3.so",
|
||||
# ifndef HAVE_64BIT_BUILD
|
||||
// 32-bit versions on 64-bit hosts
|
||||
"/usr/lib32/libnss3.so",
|
||||
# endif
|
||||
};
|
||||
void* lib = nullptr;
|
||||
|
||||
for (const char* libname : libnssNames) {
|
||||
lib = dlopen(libname, RTLD_NOW);
|
||||
|
||||
if (lib) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!lib) {
|
||||
return "";
|
||||
}
|
||||
|
||||
SECStatus (*NSS_Initialize)(const char*, const char*, const char*,
|
||||
const char*, PRUint32);
|
||||
HASHContext* (*HASH_Create)(HASH_HashType);
|
||||
void (*HASH_Destroy)(HASHContext*);
|
||||
void (*HASH_Begin)(HASHContext*);
|
||||
void (*HASH_Update)(HASHContext*, const unsigned char*, unsigned int);
|
||||
void (*HASH_End)(HASHContext*, unsigned char*, unsigned int*, unsigned int);
|
||||
|
||||
*(void**)(&NSS_Initialize) = dlsym(lib, "NSS_Initialize");
|
||||
*(void**)(&HASH_Create) = dlsym(lib, "HASH_Create");
|
||||
*(void**)(&HASH_Destroy) = dlsym(lib, "HASH_Destroy");
|
||||
*(void**)(&HASH_Begin) = dlsym(lib, "HASH_Begin");
|
||||
*(void**)(&HASH_Update) = dlsym(lib, "HASH_Update");
|
||||
*(void**)(&HASH_End) = dlsym(lib, "HASH_End");
|
||||
|
||||
if (!HASH_Create || !HASH_Destroy || !HASH_Begin || !HASH_Update ||
|
||||
!HASH_End) {
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
// Minimal NSS initialization so we can use the hash functions
|
||||
const PRUint32 kNssFlags = NSS_INIT_READONLY | NSS_INIT_NOROOTINIT |
|
||||
NSS_INIT_NOMODDB | NSS_INIT_NOCERTDB;
|
||||
if (NSS_Initialize(nullptr, "", "", "", kNssFlags) != SECSuccess) {
|
||||
return "";
|
||||
}
|
||||
|
||||
HASHContext* hashContext = HASH_Create(HASH_AlgSHA256);
|
||||
|
||||
if (!hashContext) {
|
||||
return "";
|
||||
}
|
||||
|
||||
HASH_Begin(hashContext);
|
||||
|
||||
ifstream* f = UIOpenRead(gReporterDumpFile, ios::binary);
|
||||
bool error = false;
|
||||
|
||||
// Read the minidump contents
|
||||
if (f->is_open()) {
|
||||
uint8_t buff[4096];
|
||||
|
||||
do {
|
||||
f->read((char*)buff, sizeof(buff));
|
||||
|
||||
if (f->bad()) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
HASH_Update(hashContext, buff, f->gcount());
|
||||
} while (!f->eof());
|
||||
|
||||
f->close();
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
|
||||
delete f;
|
||||
|
||||
// Finalize the hash computation
|
||||
uint8_t result[SHA256_LENGTH];
|
||||
uint32_t resultLen = 0;
|
||||
|
||||
HASH_End(hashContext, result, &resultLen, SHA256_LENGTH);
|
||||
|
||||
if (resultLen != SHA256_LENGTH) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
HASH_Destroy(hashContext);
|
||||
|
||||
if (!error) {
|
||||
ostringstream hash;
|
||||
|
||||
for (size_t i = 0; i < SHA256_LENGTH; i++) {
|
||||
hash << std::setw(2) << std::setfill('0') << std::hex
|
||||
<< static_cast<unsigned int>(result[i]);
|
||||
}
|
||||
|
||||
return hash.str();
|
||||
}
|
||||
return ""; // If we encountered an error, return an empty hash
|
||||
}
|
||||
|
||||
string GetDumpLocalID() {
|
||||
string localId = Basename(gReporterDumpFile);
|
||||
string::size_type dot = localId.rfind('.');
|
||||
|
||||
if (dot == string::npos) return "";
|
||||
|
||||
return localId.substr(0, dot);
|
||||
}
|
||||
|
||||
string GetProgramPath(const string& exename) {
|
||||
string path = gArgv[0];
|
||||
size_t pos = path.rfind(UI_CRASH_REPORTER_FILENAME BIN_SUFFIX);
|
||||
path.erase(pos);
|
||||
#ifdef XP_MACOSX
|
||||
// On macOS the crash reporter client is shipped as an application bundle
|
||||
// contained within Firefox' main application bundle. So when it's invoked
|
||||
// its current working directory looks like:
|
||||
// Firefox.app/Contents/MacOS/crashreporter.app/Contents/MacOS/
|
||||
// The other applications we ship with Firefox are stored in the main bundle
|
||||
// (Firefox.app/Contents/MacOS/) so we we need to go back three directories
|
||||
// to reach them.
|
||||
path.append("../../../");
|
||||
#endif // XP_MACOSX
|
||||
path.append(exename + BIN_SUFFIX);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
} // namespace CrashReporter
|
||||
|
||||
using namespace CrashReporter;
|
||||
|
||||
Json::Value kEmptyJsonString("");
|
||||
|
||||
void RewriteStrings(Json::Value& aExtraData) {
|
||||
// rewrite some UI strings with the values from the query parameters
|
||||
string product = aExtraData.get("ProductName", kEmptyJsonString).asString();
|
||||
Json::Value mozilla("Mozilla");
|
||||
string vendor = aExtraData.get("Vendor", mozilla).asString();
|
||||
|
||||
char buf[4096];
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), gStrings[ST_CRASHREPORTERDESCRIPTION].c_str(),
|
||||
product.c_str());
|
||||
gStrings[ST_CRASHREPORTERDESCRIPTION] = buf;
|
||||
|
||||
snprintf(buf, sizeof(buf), gStrings[ST_CHECKSUBMIT].c_str(), vendor.c_str());
|
||||
gStrings[ST_CHECKSUBMIT] = buf;
|
||||
|
||||
snprintf(buf, sizeof(buf), gStrings[ST_RESTART].c_str(), product.c_str());
|
||||
gStrings[ST_RESTART] = buf;
|
||||
|
||||
snprintf(buf, sizeof(buf), gStrings[ST_QUIT].c_str(), product.c_str());
|
||||
gStrings[ST_QUIT] = buf;
|
||||
|
||||
snprintf(buf, sizeof(buf), gStrings[ST_ERROR_ENDOFLIFE].c_str(),
|
||||
product.c_str());
|
||||
gStrings[ST_ERROR_ENDOFLIFE] = buf;
|
||||
}
|
||||
|
||||
bool CheckEndOfLifed(const Json::Value& aVersion) {
|
||||
if (!aVersion.isString()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string reportPath =
|
||||
gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" + aVersion.asString();
|
||||
return UIFileExists(reportPath);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
gArgc = argc;
|
||||
gArgv = argv;
|
||||
|
||||
string autoSubmitEnv = UIGetEnv("MOZ_CRASHREPORTER_AUTO_SUBMIT");
|
||||
gAutoSubmit = !autoSubmitEnv.empty();
|
||||
|
||||
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
|
||||
if (!gAutoSubmit) {
|
||||
UIShowDefaultUI();
|
||||
}
|
||||
} else {
|
||||
// Start by running minidump analyzer to gather stack traces.
|
||||
string reporterDumpFile = gReporterDumpFile;
|
||||
vector<string> args = {reporterDumpFile};
|
||||
string dumpAllThreadsEnv = UIGetEnv("MOZ_CRASHREPORTER_DUMP_ALL_THREADS");
|
||||
if (!dumpAllThreadsEnv.empty()) {
|
||||
args.insert(args.begin(), "--full");
|
||||
}
|
||||
UIRunProgram(CrashReporter::GetProgramPath(UI_MINIDUMP_ANALYZER_FILENAME),
|
||||
args,
|
||||
/* wait */ true);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
Json::Value extraData;
|
||||
if (!ReadExtraFile(gExtraFile, extraData)) {
|
||||
UIError(gStrings[ST_ERROR_EXTRAFILEREAD]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!extraData.isMember("ProductName")) {
|
||||
UIError(gStrings[ST_ERROR_NOPRODUCTNAME]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// There is enough information in the extra file to rewrite strings
|
||||
// to be product specific
|
||||
RewriteStrings(extraData);
|
||||
|
||||
if (!extraData.isMember("ServerURL")) {
|
||||
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.
|
||||
gSettingsPath = UIGetEnv("MOZ_CRASHREPORTER_DATA_DIRECTORY");
|
||||
if (gSettingsPath.empty()) {
|
||||
string product =
|
||||
extraData.get("ProductName", kEmptyJsonString).asString();
|
||||
string vendor = extraData.get("Vendor", kEmptyJsonString).asString();
|
||||
|
||||
if (!UIGetSettingsPath(vendor, product, gSettingsPath)) {
|
||||
gSettingsPath.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (gSettingsPath.empty() || !UIEnsurePathExists(gSettingsPath)) {
|
||||
UIError(gStrings[ST_ERROR_NOSETTINGSPATH]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
OpenLogFile();
|
||||
|
||||
gEventsPath = UIGetEnv("MOZ_CRASHREPORTER_EVENTS_DIRECTORY");
|
||||
gPingPath = UIGetEnv("MOZ_CRASHREPORTER_PING_DIRECTORY");
|
||||
|
||||
// Assemble and send the crash ping
|
||||
string hash = ComputeDumpHash();
|
||||
|
||||
string pingUuid;
|
||||
SendCrashPing(extraData, hash, pingUuid, gPingPath);
|
||||
UpdateEventFile(extraData, hash, pingUuid);
|
||||
|
||||
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 = extraData.get("ServerURL", kEmptyJsonString).asString();
|
||||
// we don't need to actually send these
|
||||
extraData.removeMember("ServerURL");
|
||||
extraData.removeMember("StackTraces");
|
||||
|
||||
extraData["SubmittedFrom"] = "Client";
|
||||
extraData["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;
|
||||
|
||||
if (!extraData.isMember("WindowsErrorReporting")) {
|
||||
// We relaunch the application associated with the client, but only when
|
||||
// we encountered a crash caught by the exception handler. Crashes handled
|
||||
// by WER are prevented from directly restarting the application.
|
||||
string programPath = GetProgramPath(MOZ_APP_NAME);
|
||||
#ifndef XP_WIN
|
||||
const char* moz_app_launcher = getenv("MOZ_APP_LAUNCHER");
|
||||
if (moz_app_launcher) {
|
||||
programPath = moz_app_launcher;
|
||||
}
|
||||
#endif // XP_WIN
|
||||
|
||||
restartArgs.push_back(programPath);
|
||||
|
||||
ostringstream paramName;
|
||||
int i = 1;
|
||||
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 (extraData.isMember("Version") &&
|
||||
CheckEndOfLifed(extraData["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, extraData, 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
|
|
@ -1,42 +0,0 @@
|
|||
<?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>
|
||||
<ms_asmv3:application xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<ms_asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
||||
<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>
|
||||
</ms_asmv3:windowsSettings>
|
||||
</ms_asmv3:application>
|
||||
<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}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
|
@ -1,158 +0,0 @@
|
|||
/* 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 <fstream>
|
||||
|
||||
#define MAX_COMMENT_LENGTH 10000
|
||||
|
||||
#if defined(XP_WIN)
|
||||
|
||||
# include <windows.h>
|
||||
|
||||
# define UI_DIR_SEPARATOR "\\"
|
||||
|
||||
std::string WideToUTF8(const std::wstring& wide, bool* success = 0);
|
||||
|
||||
#else
|
||||
|
||||
# define UI_DIR_SEPARATOR "/"
|
||||
|
||||
#endif
|
||||
|
||||
#include "json/json.h"
|
||||
|
||||
#define UI_CRASH_REPORTER_FILENAME "crashreporter"
|
||||
#define UI_MINIDUMP_ANALYZER_FILENAME "minidump-analyzer"
|
||||
#define UI_PING_SENDER_FILENAME "pingsender"
|
||||
|
||||
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_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 and ping.cpp
|
||||
//=============================================================================
|
||||
|
||||
namespace CrashReporter {
|
||||
extern StringTable gStrings;
|
||||
extern std::string gSettingsPath;
|
||||
extern std::string gEventsPath;
|
||||
extern int gArgc;
|
||||
extern char** gArgv;
|
||||
extern bool gAutoSubmit;
|
||||
|
||||
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);
|
||||
void LogMessage(const std::string& message);
|
||||
void DeleteDump();
|
||||
|
||||
std::string GetDumpLocalID();
|
||||
std::string GetProgramPath(const std::string& exename);
|
||||
|
||||
// Telemetry ping
|
||||
bool SendCrashPing(Json::Value& extra, const std::string& hash,
|
||||
std::string& pingUuid, const std::string& pingDir);
|
||||
|
||||
static const unsigned int kSaveCount = 10;
|
||||
} // namespace CrashReporter
|
||||
|
||||
//=============================================================================
|
||||
// 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 Json::Value& 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::ios_base::openmode mode);
|
||||
std::ofstream* UIOpenWrite(const std::string& filename,
|
||||
std::ios_base::openmode mode);
|
||||
void UIPruneSavedDumps(const std::string& directory);
|
||||
|
||||
// Run the program specified by exename, passing it the parameters in arg.
|
||||
// If wait is true, wait for the program to terminate execution before
|
||||
// returning. Returns true if the program was launched correctly, false
|
||||
// otherwise.
|
||||
bool UIRunProgram(const std::string& exename,
|
||||
const std::vector<std::string>& args, bool wait = false);
|
||||
|
||||
// Read the environment variable specified by name
|
||||
std::string UIGetEnv(const std::string& name);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif
|
Двоичные данные
toolkit/crashreporter/client/crashreporter.ico
Двоичные данные
toolkit/crashreporter/client/crashreporter.ico
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 25 KiB |
|
@ -1,143 +0,0 @@
|
|||
/* 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
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 "",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
|
||||
|
|
@ -1,361 +0,0 @@
|
|||
/* -*- 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 <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;
|
||||
Json::Value gQueryParameters;
|
||||
string gHttpProxy;
|
||||
string gAuth;
|
||||
string gCACertificateFile;
|
||||
string gSendURL;
|
||||
string gURLParameter;
|
||||
vector<string> gRestartArgs;
|
||||
GThread* gSendThreadID;
|
||||
|
||||
// From crashreporter_linux.cpp
|
||||
void SendReport();
|
||||
void DisableGUIAndSendReport();
|
||||
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) {
|
||||
free(argv);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
(void)execv(argv[0], argv);
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
free(argv);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Quit the app, used as a timeout callback
|
||||
gboolean CloseApp(gpointer data) {
|
||||
if (!gAutoSubmit) {
|
||||
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;
|
||||
}
|
||||
|
||||
#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().
|
||||
}
|
||||
|
||||
gpointer SendThread(gpointer args) {
|
||||
Json::StreamWriterBuilder builder;
|
||||
builder["indentation"] = "";
|
||||
string parameters(writeString(builder, gQueryParameters));
|
||||
|
||||
string response, error;
|
||||
long response_code;
|
||||
|
||||
bool success = google_breakpad::HTTPUpload::SendRequest(
|
||||
gSendURL, parameters, 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);
|
||||
|
||||
if (!gAutoSubmit) {
|
||||
// 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;
|
||||
DisableGUIAndSendReport();
|
||||
} 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.removeMember("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 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);
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/* 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>
|
||||
|
||||
#include "json/json.h"
|
||||
|
||||
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 Json::Value gQueryParameters;
|
||||
extern std::string gHttpProxy;
|
||||
extern std::string gAuth;
|
||||
extern std::string gCACertificateFile;
|
||||
extern std::string gSendURL;
|
||||
extern std::string gURLParameter;
|
||||
|
||||
void LoadProxyinfo();
|
||||
gboolean CloseApp(gpointer data);
|
||||
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);
|
||||
void SaveSettings(void);
|
||||
|
||||
#endif // CRASHREPORTER_GTK_COMMON_H__
|
|
@ -1,525 +0,0 @@
|
|||
/* -*- 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::ios;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
using namespace CrashReporter;
|
||||
|
||||
static GtkWidget* gViewReportButton = 0;
|
||||
static GtkWidget* gCommentTextLabel = 0;
|
||||
static GtkWidget* gCommentText = 0;
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
bool includeURL = true;
|
||||
bool submitReport = true;
|
||||
StringTable settings;
|
||||
if (ReadStringsFromFile(gSettingsPath + "/" + kIniFile, settings, true)) {
|
||||
if (settings.find("IncludeURL") != settings.end()) {
|
||||
includeURL = settings["IncludeURL"][0] != '0';
|
||||
}
|
||||
if (settings.find("SubmitReport") != settings.end()) {
|
||||
submitReport = settings["SubmitReport"][0] != '0';
|
||||
}
|
||||
}
|
||||
|
||||
if (gIncludeURLCheck) {
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gIncludeURLCheck),
|
||||
includeURL);
|
||||
}
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gSubmitReportCheck),
|
||||
submitReport);
|
||||
}
|
||||
|
||||
static string Escape(const string& str) {
|
||||
string ret;
|
||||
for (auto c : str) {
|
||||
if (c == '\\') {
|
||||
ret += "\\\\";
|
||||
} else if (c == '\n') {
|
||||
ret += "\\n";
|
||||
} else if (c == '\t') {
|
||||
ret += "\\t";
|
||||
} else {
|
||||
ret.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool WriteStrings(std::ostream& out, const string& header,
|
||||
StringTable& strings, bool escape) {
|
||||
out << "[" << header << "]\n";
|
||||
for (const auto& iter : strings) {
|
||||
out << iter.first << "=";
|
||||
if (escape) {
|
||||
out << Escape(iter.second);
|
||||
} else {
|
||||
out << iter.second;
|
||||
}
|
||||
|
||||
out << '\n';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool WriteStringsToFile(const string& path, const string& header,
|
||||
StringTable& strings, bool escape) {
|
||||
std::ofstream* f = UIOpenWrite(path, ios::trunc);
|
||||
bool success = false;
|
||||
if (f->is_open()) {
|
||||
success = WriteStrings(*f, header, strings, escape);
|
||||
f->close();
|
||||
}
|
||||
|
||||
delete f;
|
||||
return success;
|
||||
}
|
||||
|
||||
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 (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() {
|
||||
LoadProxyinfo();
|
||||
|
||||
// spawn a thread to do the sending
|
||||
gSendThreadID = g_thread_create(SendThread, nullptr, TRUE, nullptr);
|
||||
}
|
||||
|
||||
void DisableGUIAndSendReport() {
|
||||
// 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(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());
|
||||
|
||||
SendReport();
|
||||
}
|
||||
|
||||
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 (Json::ValueConstIterator iter = gQueryParameters.begin();
|
||||
iter != gQueryParameters.end(); ++iter) {
|
||||
gtk_text_buffer_insert(buffer, &end, iter.name().c_str(),
|
||||
iter.name().length());
|
||||
gtk_text_buffer_insert(buffer, &end, ": ", -1);
|
||||
string value;
|
||||
if (iter->isString()) {
|
||||
value = iter->asString();
|
||||
} else {
|
||||
Json::StreamWriterBuilder builder;
|
||||
builder["indentation"] = "";
|
||||
value = writeString(builder, *iter);
|
||||
}
|
||||
gtk_text_buffer_insert(buffer, &end, value.c_str(), value.length());
|
||||
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_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_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);
|
||||
gtk_widget_set_vexpand(scrolled, TRUE);
|
||||
|
||||
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.removeMember("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,
|
||||
>k_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;
|
||||
}
|
||||
|
||||
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 Json::Value& queryParameters,
|
||||
const string& sendURL, const vector<string>& restartArgs) {
|
||||
gFiles = files;
|
||||
gQueryParameters = queryParameters;
|
||||
gSendURL = sendURL;
|
||||
gRestartArgs = restartArgs;
|
||||
if (gQueryParameters.isMember("URL")) {
|
||||
gURLParameter = gQueryParameters["URL"].asString();
|
||||
}
|
||||
|
||||
if (gAutoSubmit) {
|
||||
SendReport();
|
||||
CloseApp(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
gtk_label_set_max_width_chars(GTK_LABEL(descriptionLabel),
|
||||
LABEL_MAX_CHAR_WIDTH);
|
||||
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);
|
||||
gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(scrolled),
|
||||
100);
|
||||
|
||||
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_CHAR);
|
||||
gtk_widget_set_size_request(GTK_WIDGET(gCommentText), -1, 100);
|
||||
|
||||
if (gQueryParameters.isMember("URL")) {
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
gtk_label_set_max_width_chars(GTK_LABEL(gProgressLabel),
|
||||
LABEL_MAX_CHAR_WIDTH);
|
||||
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.empty()) {
|
||||
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();
|
||||
|
||||
UpdateSubmit();
|
||||
|
||||
UpdateHintText(gCommentText, FALSE, &gCommentFieldHint,
|
||||
gStrings[ST_COMMENTGRAYTEXT].c_str());
|
||||
|
||||
gtk_widget_show_all(gWindow);
|
||||
// stick this here to avoid the show_all above...
|
||||
gtk_widget_hide(gThrobber);
|
||||
|
||||
gtk_main();
|
||||
|
||||
return gDidTrySend;
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
/* -*- 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"
|
||||
#include "json/json.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 Json::Value&)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;
|
||||
|
||||
- (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
|
|
@ -1,805 +0,0 @@
|
|||
/* -*- 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::ostringstream;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
using namespace CrashReporter;
|
||||
|
||||
static NSAutoreleasePool* gMainPool;
|
||||
static CrashReporterUI* gUI = 0;
|
||||
static StringTable gFiles;
|
||||
static Json::Value 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,
|
||||
#elif defined(__aarch64__)
|
||||
CPU_TYPE_ARM64,
|
||||
#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"]];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
}
|
||||
|
||||
- (void)showCrashUI:(const StringTable&)files
|
||||
queryParameters:(const Json::Value&)queryParameters
|
||||
sendURL:(const string&)sendURL {
|
||||
gFiles = files;
|
||||
gQueryParameters = queryParameters;
|
||||
gSendURL = sendURL;
|
||||
|
||||
if (gAutoSubmit) {
|
||||
gDidTrySend = true;
|
||||
[self sendReport];
|
||||
return;
|
||||
}
|
||||
|
||||
[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)];
|
||||
[mViewReportOkButton setTitle:Str(ST_OK)];
|
||||
|
||||
[mCommentText setPlaceholder:Str(ST_COMMENTGRAYTEXT)];
|
||||
if (gRTLlayout) [mCommentText toggleBaseWritingDirection:self];
|
||||
|
||||
if (gQueryParameters.isMember("URL")) {
|
||||
// save the URL value in case the checkbox gets unchecked
|
||||
gURLParameter = gQueryParameters["URL"].asString();
|
||||
} 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 = YES;
|
||||
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
|
||||
if (nil != [userDefaults objectForKey:@"submitReport"]) {
|
||||
submitChecked = [userDefaults boolForKey:@"submitReport"];
|
||||
} else {
|
||||
[userDefaults setBool:submitChecked forKey:@"submitReport"];
|
||||
}
|
||||
[mSubmitReportButton setState:(submitChecked ? NSOnState : NSOffState)];
|
||||
|
||||
// load default state of include URL checkbox
|
||||
BOOL includeChecked = YES;
|
||||
if (nil != [userDefaults objectForKey:@"IncludeURL"]) {
|
||||
includeChecked = [userDefaults boolForKey:@"IncludeURL"];
|
||||
} else {
|
||||
[userDefaults setBool:includeChecked forKey:@"IncludeURL"];
|
||||
}
|
||||
[mIncludeURLButton setState:(includeChecked ? 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 = @{
|
||||
NSFontAttributeName :
|
||||
[NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]],
|
||||
NSForegroundColorAttributeName : NSColor.textColor,
|
||||
};
|
||||
NSDictionary* normalAttr = @{
|
||||
NSFontAttributeName :
|
||||
[NSFont systemFontOfSize:[NSFont smallSystemFontSize]],
|
||||
NSForegroundColorAttributeName : NSColor.textColor,
|
||||
};
|
||||
|
||||
[mViewReportTextView setString:@""];
|
||||
for (Json::ValueConstIterator iter = gQueryParameters.begin();
|
||||
iter != gQueryParameters.end(); ++iter) {
|
||||
NSAttributedString* key =
|
||||
[[NSAttributedString alloc] initWithString:NSSTR(iter.name() + ": ")
|
||||
attributes:boldAttr];
|
||||
string str;
|
||||
if (iter->isString()) {
|
||||
str = iter->asString();
|
||||
} else {
|
||||
Json::StreamWriterBuilder builder;
|
||||
builder["indentation"] = "";
|
||||
str = writeString(builder, *iter);
|
||||
}
|
||||
NSAttributedString* value =
|
||||
[[NSAttributedString alloc] initWithString:NSSTR(str + "\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];
|
||||
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
|
||||
[userDefaults setBool:([mIncludeURLButton state] == NSOnState)
|
||||
forKey:@"IncludeURL"];
|
||||
[userDefaults synchronize];
|
||||
}
|
||||
|
||||
- (void)textDidChange:(NSNotification*)aNotification {
|
||||
// update comment parameter
|
||||
if ([[[mCommentText textStorage] mutableString] length] > 0)
|
||||
gQueryParameters["Comments"] =
|
||||
[[[mCommentText textStorage] mutableString] UTF8String];
|
||||
else
|
||||
gQueryParameters.removeMember("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};
|
||||
|
||||
for (auto checkbox : checkboxes) {
|
||||
NSRect frame = [checkbox frame];
|
||||
[checkbox sizeToFit];
|
||||
if (gRTLlayout) {
|
||||
// sizeToFit will keep the left side fixed, so realign
|
||||
float oldWidth = frame.size.width;
|
||||
frame = [checkbox frame];
|
||||
frame.origin.x += oldWidth - frame.size.width;
|
||||
[checkbox setFrame:frame];
|
||||
}
|
||||
// keep existing spacing on left side, + 20 px spare on right
|
||||
float neededWidth =
|
||||
frame.origin.x + checkbox.intrinsicContentSize.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,
|
||||
mProgressIndicator, mProgressText};
|
||||
for (auto view : views) {
|
||||
[view 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];
|
||||
[mCommentText setEnabled:enabled];
|
||||
[mCommentScrollView setHasVerticalScroller:enabled];
|
||||
}
|
||||
|
||||
- (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.removeMember("URL");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateEmail {
|
||||
// In order to remove the email fields, we have to edit the .nib files which
|
||||
// we can't do with current xcode so we make them hidden; updating the
|
||||
// crashreporter interface for mac is covered in bug #1696164
|
||||
[mEmailMeButton setHidden:YES];
|
||||
[mEmailText setHidden:YES];
|
||||
}
|
||||
|
||||
- (void)sendReport {
|
||||
if (![self setupPost]) {
|
||||
LogMessage("Crash report submission failed: could not set up POST data");
|
||||
|
||||
if (gAutoSubmit) {
|
||||
[NSApp terminate:self];
|
||||
}
|
||||
|
||||
[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;
|
||||
|
||||
for (StringTable::const_iterator i = gFiles.begin(); i != gFiles.end(); i++) {
|
||||
[mPost addFileAtPath:NSSTR(i->second) name:NSSTR(i->first)];
|
||||
}
|
||||
|
||||
Json::StreamWriterBuilder builder;
|
||||
builder["indentation"] = "";
|
||||
string output = writeString(builder, gQueryParameters).append("\r\n");
|
||||
NSMutableString* parameters =
|
||||
[[NSMutableString alloc] initWithUTF8String:output.c_str()];
|
||||
|
||||
[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);
|
||||
|
||||
if (gAutoSubmit) {
|
||||
[NSApp terminate:self];
|
||||
}
|
||||
|
||||
[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:NSTextAlignmentRight
|
||||
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;
|
||||
|
||||
if (gAutoSubmit) {
|
||||
gUI = [[CrashReporterUI alloc] init];
|
||||
} else {
|
||||
[[NSBundle mainBundle]
|
||||
loadNibNamed:(gRTLlayout ? @"MainMenuRTL" : @"MainMenu")
|
||||
owner:NSApp
|
||||
topLevelObjects:nil];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UIShutdown() { [gMainPool release]; }
|
||||
|
||||
void UIShowDefaultUI() {
|
||||
[gUI showErrorUI:gStrings[ST_CRASHREPORTERDEFAULT]];
|
||||
[NSApp run];
|
||||
}
|
||||
|
||||
bool UIShowCrashUI(const StringTable& files, const Json::Value& 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) {
|
||||
NSArray* paths = NSSearchPathForDirectoriesInDomains(
|
||||
NSApplicationSupportDirectory, NSUserDomainMask, YES);
|
||||
NSString* destPath = [paths firstObject];
|
||||
|
||||
// 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 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);
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
/* 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 <sys/wait.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace CrashReporter;
|
||||
using std::ios_base;
|
||||
using std::sort;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
struct FileData {
|
||||
time_t timestamp;
|
||||
string path;
|
||||
};
|
||||
|
||||
static bool CompareFDTime(const FileData& fd1, const FileData& fd2) {
|
||||
return fd1.timestamp > fd2.timestamp;
|
||||
}
|
||||
|
||||
void UIPruneSavedDumps(const 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);
|
||||
}
|
||||
|
||||
closedir(dirfd);
|
||||
|
||||
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);
|
||||
|
||||
// s/.dmp/.extra/
|
||||
path.replace(path.size() - 4, 4, ".extra");
|
||||
UIDeleteFile(path);
|
||||
|
||||
dumpfiles.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
bool UIRunProgram(const string& exename, const vector<string>& args,
|
||||
bool wait) {
|
||||
pid_t pid = fork();
|
||||
|
||||
if (pid == -1) {
|
||||
return false;
|
||||
} else if (pid == 0) {
|
||||
// Child
|
||||
size_t argvLen = args.size() + 2;
|
||||
vector<char*> argv(argvLen);
|
||||
|
||||
argv[0] = const_cast<char*>(exename.c_str());
|
||||
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
argv[i + 1] = const_cast<char*>(args[i].c_str());
|
||||
}
|
||||
|
||||
argv[argvLen - 1] = nullptr;
|
||||
|
||||
// Run the program
|
||||
int rv = execv(exename.c_str(), argv.data());
|
||||
|
||||
if (rv == -1) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
// Parent
|
||||
if (wait) {
|
||||
waitpid(pid, nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
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 UIDeleteFile(const string& file) { return (unlink(file.c_str()) != -1); }
|
||||
|
||||
std::ifstream* UIOpenRead(const string& filename, ios_base::openmode mode) {
|
||||
return new std::ifstream(filename.c_str(), mode);
|
||||
}
|
||||
|
||||
std::ofstream* UIOpenWrite(const string& filename, ios_base::openmode mode) {
|
||||
return new std::ofstream(filename.c_str(), mode);
|
||||
}
|
||||
|
||||
string UIGetEnv(const string& name) {
|
||||
const char* var = getenv(name.c_str());
|
||||
if (var && *var) {
|
||||
return var;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,2 +0,0 @@
|
|||
APPL????
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
/* 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 = "@APP_NAME@ Crash Reporter";
|
|
@ -1,100 +0,0 @@
|
|||
<?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>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>
|
|
@ -1,18 +0,0 @@
|
|||
<?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>
|
Двоичные данные
toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
сгенерированный
Двоичные данные
toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
сгенерированный
Двоичный файл не отображается.
|
@ -1,100 +0,0 @@
|
|||
<?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>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>
|
|
@ -1,18 +0,0 @@
|
|||
<?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>
|
Двоичные данные
toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenuRTL.nib/keyedobjects.nib
сгенерированный
Двоичные данные
toolkit/crashreporter/client/macbuild/Contents/Resources/English.lproj/MainMenuRTL.nib/keyedobjects.nib
сгенерированный
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -1,97 +0,0 @@
|
|||
# -*- 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 += [
|
||||
"../CrashAnnotations.cpp",
|
||||
"crashreporter.cpp",
|
||||
"ping.cpp",
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
"/toolkit/components/jsoncpp/include",
|
||||
]
|
||||
|
||||
USE_LIBS += [
|
||||
"jsoncpp",
|
||||
]
|
||||
|
||||
if CONFIG["OS_ARCH"] == "WINNT":
|
||||
UNIFIED_SOURCES += [
|
||||
"crashreporter_win.cpp",
|
||||
]
|
||||
include("/toolkit/crashreporter/breakpad-client/windows/sender/objs.mozbuild")
|
||||
SOURCES += objs_sender
|
||||
SOURCES += [
|
||||
"../google-breakpad/src/common/windows/http_upload.cc",
|
||||
]
|
||||
DEFINES["UNICODE"] = True
|
||||
DEFINES["_UNICODE"] = True
|
||||
USE_LIBS += [
|
||||
"nss",
|
||||
]
|
||||
OS_LIBS += [
|
||||
"advapi32",
|
||||
"comctl32",
|
||||
"gdi32",
|
||||
"ole32",
|
||||
"shell32",
|
||||
"wininet",
|
||||
"shlwapi",
|
||||
"user32",
|
||||
]
|
||||
elif CONFIG["OS_ARCH"] == "Darwin":
|
||||
UNIFIED_SOURCES += [
|
||||
"../google-breakpad/src/common/mac/HTTPMultipartUpload.m",
|
||||
"crashreporter_osx.mm",
|
||||
"crashreporter_unix_common.cpp",
|
||||
]
|
||||
LOCAL_INCLUDES += [
|
||||
"../google-breakpad/src/common/mac",
|
||||
]
|
||||
OS_LIBS += ["-framework Cocoa"]
|
||||
USE_LIBS += [
|
||||
"nss",
|
||||
]
|
||||
LDFLAGS += ["-Wl,-rpath,@executable_path/../../../"]
|
||||
elif CONFIG["OS_ARCH"] == "SunOS":
|
||||
SOURCES += [
|
||||
"crashreporter_linux.cpp",
|
||||
"crashreporter_unix.cpp",
|
||||
]
|
||||
USE_LIBS += [
|
||||
"breakpad_solaris_common_s",
|
||||
]
|
||||
|
||||
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
|
||||
UNIFIED_SOURCES += [
|
||||
"../google-breakpad/src/common/linux/http_upload.cc",
|
||||
"crashreporter_gtk_common.cpp",
|
||||
"crashreporter_linux.cpp",
|
||||
"crashreporter_unix_common.cpp",
|
||||
]
|
||||
OS_LIBS += CONFIG["MOZ_GTK3_LIBS"]
|
||||
OS_LIBS += CONFIG["MOZ_GTHREAD_LIBS"]
|
||||
CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
|
||||
CXXFLAGS += CONFIG["MOZ_GTHREAD_CFLAGS"]
|
||||
|
||||
if CONFIG["OS_ARCH"] == "Linux" or CONFIG["OS_ARCH"] == "SunOS":
|
||||
FINAL_TARGET_FILES += [
|
||||
"Throbber-small.gif",
|
||||
]
|
||||
|
||||
DEFINES["MOZ_APP_NAME"] = '"%s"' % CONFIG["MOZ_APP_NAME"]
|
||||
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.
|
||||
DisableStlWrapping()
|
||||
|
||||
include("/toolkit/crashreporter/crashreporter.mozbuild")
|
|
@ -1,324 +0,0 @@
|
|||
/* -*- 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 <cstring>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
|
||||
#if defined(XP_LINUX)
|
||||
# include <fcntl.h>
|
||||
# include <unistd.h>
|
||||
# include <sys/stat.h>
|
||||
#elif defined(XP_MACOSX)
|
||||
# include <CoreFoundation/CoreFoundation.h>
|
||||
#elif defined(XP_WIN)
|
||||
# include <objbase.h>
|
||||
#endif
|
||||
|
||||
#include "json/json.h"
|
||||
|
||||
#include "CrashAnnotations.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace CrashReporter {
|
||||
|
||||
struct UUID {
|
||||
uint32_t m0;
|
||||
uint16_t m1;
|
||||
uint16_t m2;
|
||||
uint8_t m3[8];
|
||||
};
|
||||
|
||||
// Generates an UUID; the code here is mostly copied from nsUUIDGenerator.cpp
|
||||
static string GenerateUUID() {
|
||||
UUID id = {};
|
||||
|
||||
#if defined(XP_WIN) // Windows
|
||||
HRESULT hr = CoCreateGuid((GUID*)&id);
|
||||
if (FAILED(hr)) {
|
||||
return "";
|
||||
}
|
||||
#elif defined(XP_MACOSX) // MacOS X
|
||||
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
|
||||
if (!uuid) {
|
||||
return "";
|
||||
}
|
||||
|
||||
CFUUIDBytes bytes = CFUUIDGetUUIDBytes(uuid);
|
||||
memcpy(&id, &bytes, sizeof(UUID));
|
||||
|
||||
CFRelease(uuid);
|
||||
#elif defined(HAVE_ARC4RANDOM_BUF) // Android, BSD, ...
|
||||
arc4random_buf(&id, sizeof(UUID));
|
||||
#else // Linux
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
|
||||
if (fd == -1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (read(fd, &id, sizeof(UUID)) != sizeof(UUID)) {
|
||||
close(fd);
|
||||
return "";
|
||||
}
|
||||
|
||||
close(fd);
|
||||
#endif
|
||||
|
||||
/* Put in the version */
|
||||
id.m2 &= 0x0fff;
|
||||
id.m2 |= 0x4000;
|
||||
|
||||
/* Put in the variant */
|
||||
id.m3[0] &= 0x3f;
|
||||
id.m3[0] |= 0x80;
|
||||
|
||||
const char* kUUIDFormatString =
|
||||
"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x";
|
||||
const size_t kUUIDFormatStringLength = 36;
|
||||
char str[kUUIDFormatStringLength + 1] = {'\0'};
|
||||
|
||||
int num = snprintf(str, kUUIDFormatStringLength + 1, kUUIDFormatString, id.m0,
|
||||
id.m1, id.m2, id.m3[0], id.m3[1], id.m3[2], id.m3[3],
|
||||
id.m3[4], id.m3[5], id.m3[6], id.m3[7]);
|
||||
|
||||
if (num != kUUIDFormatStringLength) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
const char kISO8601Date[] = "%F";
|
||||
const char kISO8601DateHours[] = "%FT%H:00:00.000Z";
|
||||
|
||||
// Return the current date as a string in the specified format, the following
|
||||
// constants are provided:
|
||||
// - kISO8601Date, the ISO 8601 date format, YYYY-MM-DD
|
||||
// - kISO8601DateHours, the ISO 8601 full date format, YYYY-MM-DDTHH:00:00.000Z
|
||||
static string CurrentDate(string format) {
|
||||
time_t now;
|
||||
time(&now);
|
||||
char buf[64]; // This should be plenty
|
||||
strftime(buf, sizeof buf, format.c_str(), gmtime(&now));
|
||||
return buf;
|
||||
}
|
||||
|
||||
const char kTelemetryClientId[] = "TelemetryClientId";
|
||||
const char kTelemetryUrl[] = "TelemetryServerURL";
|
||||
const char kTelemetrySessionId[] = "TelemetrySessionId";
|
||||
const int kTelemetryVersion = 4;
|
||||
|
||||
// Create the payload.metadata node of the crash ping using fields extracted
|
||||
// from the .extra file
|
||||
static Json::Value CreateMetadataNode(const Json::Value& aExtra) {
|
||||
Json::Value node;
|
||||
|
||||
for (Json::ValueConstIterator iter = aExtra.begin(); iter != aExtra.end();
|
||||
++iter) {
|
||||
Annotation annotation;
|
||||
|
||||
if (AnnotationFromString(annotation, iter.memberName())) {
|
||||
if (IsAnnotationAllowedForPing(annotation)) {
|
||||
node[iter.memberName()] = *iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
// Create the payload node of the crash ping
|
||||
static Json::Value CreatePayloadNode(const Json::Value& aExtra,
|
||||
const string& aHash,
|
||||
const string& aSessionId) {
|
||||
Json::Value payload;
|
||||
|
||||
payload["sessionId"] = aSessionId;
|
||||
payload["version"] = 1;
|
||||
payload["crashDate"] = CurrentDate(kISO8601Date);
|
||||
payload["crashTime"] = CurrentDate(kISO8601DateHours);
|
||||
payload["hasCrashEnvironment"] = true;
|
||||
payload["crashId"] = CrashReporter::GetDumpLocalID();
|
||||
payload["minidumpSha256Hash"] = aHash;
|
||||
payload["processType"] = "main"; // This is always a main crash
|
||||
if (aExtra.isMember("StackTraces")) {
|
||||
payload["stackTraces"] = aExtra["StackTraces"];
|
||||
}
|
||||
|
||||
// Assemble the payload metadata
|
||||
payload["metadata"] = CreateMetadataNode(aExtra);
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
// Create the application node of the crash ping
|
||||
static Json::Value CreateApplicationNode(
|
||||
const string& aVendor, const string& aName, const string& aVersion,
|
||||
const string& aDisplayVersion, const string& aPlatformVersion,
|
||||
const string& aChannel, const string& aBuildId, const string& aArchitecture,
|
||||
const string& aXpcomAbi) {
|
||||
Json::Value application;
|
||||
|
||||
application["vendor"] = aVendor;
|
||||
application["name"] = aName;
|
||||
application["buildId"] = aBuildId;
|
||||
application["displayVersion"] = aDisplayVersion;
|
||||
application["platformVersion"] = aPlatformVersion;
|
||||
application["version"] = aVersion;
|
||||
application["channel"] = aChannel;
|
||||
if (!aArchitecture.empty()) {
|
||||
application["architecture"] = aArchitecture;
|
||||
}
|
||||
if (!aXpcomAbi.empty()) {
|
||||
application["xpcomAbi"] = aXpcomAbi;
|
||||
}
|
||||
|
||||
return application;
|
||||
}
|
||||
|
||||
// Create the root node of the crash ping
|
||||
static Json::Value CreateRootNode(
|
||||
const Json::Value& aExtra, const string& aUuid, const string& aHash,
|
||||
const string& aClientId, const string& aSessionId, const string& aName,
|
||||
const string& aVersion, const string& aChannel, const string& aBuildId) {
|
||||
Json::Value root;
|
||||
root["type"] = "crash"; // This is a crash ping
|
||||
root["id"] = aUuid;
|
||||
root["version"] = kTelemetryVersion;
|
||||
root["creationDate"] = CurrentDate(kISO8601DateHours);
|
||||
root["clientId"] = aClientId;
|
||||
|
||||
// Parse the telemetry environment
|
||||
Json::Value environment;
|
||||
Json::Reader reader;
|
||||
string architecture;
|
||||
string xpcomAbi;
|
||||
string displayVersion;
|
||||
string platformVersion;
|
||||
|
||||
if (reader.parse(aExtra["TelemetryEnvironment"].asString(), environment,
|
||||
/* collectComments */ false)) {
|
||||
if (environment.isMember("build") && environment["build"].isObject()) {
|
||||
Json::Value build = environment["build"];
|
||||
if (build.isMember("architecture") && build["architecture"].isString()) {
|
||||
architecture = build["architecture"].asString();
|
||||
}
|
||||
if (build.isMember("xpcomAbi") && build["xpcomAbi"].isString()) {
|
||||
xpcomAbi = build["xpcomAbi"].asString();
|
||||
}
|
||||
if (build.isMember("displayVersion") &&
|
||||
build["displayVersion"].isString()) {
|
||||
displayVersion = build["displayVersion"].asString();
|
||||
}
|
||||
if (build.isMember("platformVersion") &&
|
||||
build["platformVersion"].isString()) {
|
||||
platformVersion = build["platformVersion"].asString();
|
||||
}
|
||||
}
|
||||
|
||||
root["environment"] = environment;
|
||||
}
|
||||
|
||||
root["payload"] = CreatePayloadNode(aExtra, aHash, aSessionId);
|
||||
root["application"] = CreateApplicationNode(
|
||||
aExtra["Vendor"].asString(), aName, aVersion, displayVersion,
|
||||
platformVersion, aChannel, aBuildId, architecture, xpcomAbi);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
// Generates the URL used to submit the crash ping, see TelemetrySend.sys.mjs
|
||||
string GenerateSubmissionUrl(const string& aUrl, const string& aId,
|
||||
const string& aName, const string& aVersion,
|
||||
const string& aChannel, const string& aBuildId) {
|
||||
return aUrl + "/submit/telemetry/" + aId + "/crash/" + aName + "/" +
|
||||
aVersion + "/" + aChannel + "/" + aBuildId +
|
||||
"?v=" + std::to_string(kTelemetryVersion);
|
||||
}
|
||||
|
||||
// Write out the ping into the specified file.
|
||||
//
|
||||
// Returns true if the ping was written out successfully, false otherwise.
|
||||
static bool WritePing(const string& aPath, const string& aPing) {
|
||||
std::ofstream* f = UIOpenWrite(aPath, std::ios::trunc);
|
||||
bool success = false;
|
||||
|
||||
if (f->is_open()) {
|
||||
*f << aPing;
|
||||
f->close();
|
||||
success = f->good();
|
||||
}
|
||||
|
||||
delete f;
|
||||
return success;
|
||||
}
|
||||
|
||||
// Assembles the crash ping using the JSON data extracted from the .extra file
|
||||
// and sends it using the crash sender. All the telemetry specific data but the
|
||||
// environment will be stripped from the annotations so that it won't be sent
|
||||
// together with the crash report.
|
||||
//
|
||||
// Note that the crash ping sender is invoked in a fire-and-forget way so this
|
||||
// won't block waiting for the ping to be delivered.
|
||||
//
|
||||
// Returns true if the ping was assembled and handed over to the pingsender
|
||||
// correctly, also populates the aPingUuid parameter with the ping UUID. Returns
|
||||
// false otherwise and leaves the aPingUuid parameter unmodified.
|
||||
bool SendCrashPing(Json::Value& aExtra, const string& aHash, string& aPingUuid,
|
||||
const string& pingDir) {
|
||||
// Remove the telemetry-related data from the crash annotations
|
||||
Json::Value value;
|
||||
aExtra.removeMember(kTelemetryClientId, &value);
|
||||
string clientId = value.asString();
|
||||
aExtra.removeMember(kTelemetryUrl, &value);
|
||||
string serverUrl = value.asString();
|
||||
aExtra.removeMember(kTelemetrySessionId, &value);
|
||||
string sessionId = value.asString();
|
||||
|
||||
if (clientId.empty() || serverUrl.empty() || sessionId.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string buildId = aExtra["BuildID"].asString();
|
||||
string channel = aExtra["ReleaseChannel"].asString();
|
||||
string name = aExtra["ProductName"].asString();
|
||||
string version = aExtra["Version"].asString();
|
||||
string uuid = GenerateUUID();
|
||||
string url =
|
||||
GenerateSubmissionUrl(serverUrl, uuid, name, version, channel, buildId);
|
||||
|
||||
if (serverUrl.empty() || uuid.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Json::Value root = CreateRootNode(aExtra, uuid, aHash, clientId, sessionId,
|
||||
name, version, channel, buildId);
|
||||
|
||||
// Write out the result to the pending pings directory
|
||||
Json::StreamWriterBuilder builder;
|
||||
builder["indentation"] = "";
|
||||
string ping = Json::writeString(builder, root);
|
||||
string pingPath = pingDir + UI_DIR_SEPARATOR + uuid + ".json";
|
||||
|
||||
if (!WritePing(pingPath, ping)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hand over the ping to the sender
|
||||
std::vector<string> args = {url, pingPath};
|
||||
if (UIRunProgram(CrashReporter::GetProgramPath(UI_PING_SENDER_FILENAME),
|
||||
args)) {
|
||||
aPingUuid = uuid;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace CrashReporter
|
|
@ -1,35 +0,0 @@
|
|||
/* 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_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
|
|
@ -62,7 +62,7 @@ if CONFIG["MOZ_CRASHREPORTER"]:
|
|||
DIRS += ["minidump-analyzer"]
|
||||
|
||||
DIRS += [
|
||||
"client-rust/app",
|
||||
"client/app",
|
||||
"mozannotation_client",
|
||||
"mozannotation_server",
|
||||
]
|
||||
|
|
Загрузка…
Ссылка в новой задаче