Bug 784145 - When submitting hang reports, submit the browser report as a field of the plugin report instead of as a completely separate report. r=ted

This commit is contained in:
Georg Fritzsche 2012-09-08 19:20:59 +02:00
parent bd4e1f0a4b
commit 40a04e4be6
14 changed files with 317 additions and 295 deletions

View File

@ -39,14 +39,13 @@ CrashReporterParent::RecvAddLibraryMappings(const InfallibleTArray<Mapping>& map
return true; return true;
} }
bool void
CrashReporterParent::RecvAnnotateCrashReport(const nsCString& key, CrashReporterParent::AnnotateCrashReport(const nsCString& key,
const nsCString& data) const nsCString& data)
{ {
#ifdef MOZ_CRASHREPORTER #ifdef MOZ_CRASHREPORTER
mNotes.Put(key, data); mNotes.Put(key, data);
#endif #endif
return true;
} }
bool bool
@ -82,22 +81,6 @@ CrashReporterParent::SetChildData(const NativeThreadId& tid,
} }
#ifdef MOZ_CRASHREPORTER #ifdef MOZ_CRASHREPORTER
bool
CrashReporterParent::GenerateHangCrashReport(const AnnotationTable* processNotes)
{
if (mChildDumpID.IsEmpty())
return false;
GenerateChildData(processNotes);
CrashReporter::AnnotationTable notes;
notes.Init(4);
notes.Put(nsDependentCString("HangID"), NS_ConvertUTF16toUTF8(mHangID));
if (!CrashReporter::AppendExtraData(mParentDumpID, notes))
NS_WARNING("problem appending parent data to .extra");
return true;
}
bool bool
CrashReporterParent::GenerateCrashReportForMinidump(nsIFile* minidump, CrashReporterParent::GenerateCrashReportForMinidump(nsIFile* minidump,
const AnnotationTable* processNotes) const AnnotationTable* processNotes)

View File

@ -34,13 +34,6 @@ public:
bool bool
GeneratePairedMinidump(Toplevel* t); GeneratePairedMinidump(Toplevel* t);
/* Attempt to create a bare-bones crash report for a hang, along with extra
process-specific annotations present in the given AnnotationTable. Returns
true if successful, false otherwise.
*/
bool
GenerateHangCrashReport(const AnnotationTable* processNotes);
/* Attempt to create a bare-bones crash report, along with extra process- /* Attempt to create a bare-bones crash report, along with extra process-
specific annotations present in the given AnnotationTable. Returns true if specific annotations present in the given AnnotationTable. Returns true if
successful, false otherwise. successful, false otherwise.
@ -49,6 +42,12 @@ public:
bool bool
GenerateCrashReport(Toplevel* t, const AnnotationTable* processNotes); GenerateCrashReport(Toplevel* t, const AnnotationTable* processNotes);
/**
* Add the .extra data for an existing crash report.
*/
bool
GenerateChildData(const AnnotationTable* processNotes);
bool bool
GenerateCrashReportForMinidump(nsIFile* minidump, GenerateCrashReportForMinidump(nsIFile* minidump,
const AnnotationTable* processNotes); const AnnotationTable* processNotes);
@ -63,18 +62,6 @@ public:
void void
SetChildData(const NativeThreadId& id, const uint32_t& processType); SetChildData(const NativeThreadId& id, const uint32_t& processType);
/* Returns the shared hang ID of a parent/child paired minidump.
GeneratePairedMinidump must be called first.
*/
const nsString& HangID() {
return mHangID;
}
/* Returns the ID of the parent minidump.
GeneratePairedMinidump must be called first.
*/
const nsString& ParentDumpID() {
return mParentDumpID;
}
/* Returns the ID of the child minidump. /* Returns the ID of the child minidump.
GeneratePairedMinidump or GenerateCrashReport must be called first. GeneratePairedMinidump or GenerateCrashReport must be called first.
*/ */
@ -82,26 +69,27 @@ public:
return mChildDumpID; return mChildDumpID;
} }
void
AnnotateCrashReport(const nsCString& key, const nsCString& data);
protected: protected:
virtual void ActorDestroy(ActorDestroyReason why); virtual void ActorDestroy(ActorDestroyReason why);
virtual bool virtual bool
RecvAddLibraryMappings(const InfallibleTArray<Mapping>& m); RecvAddLibraryMappings(const InfallibleTArray<Mapping>& m);
virtual bool virtual bool
RecvAnnotateCrashReport(const nsCString& key, const nsCString& data); RecvAnnotateCrashReport(const nsCString& key, const nsCString& data) {
AnnotateCrashReport(key, data);
return true;
}
virtual bool virtual bool
RecvAppendAppNotes(const nsCString& data); RecvAppendAppNotes(const nsCString& data);
#ifdef MOZ_CRASHREPORTER #ifdef MOZ_CRASHREPORTER
bool
GenerateChildData(const AnnotationTable* processNotes);
AnnotationTable mNotes; AnnotationTable mNotes;
#endif #endif
nsCString mAppNotes; nsCString mAppNotes;
nsString mHangID;
nsString mChildDumpID; nsString mChildDumpID;
nsString mParentDumpID;
NativeThreadId mMainThread; NativeThreadId mMainThread;
time_t mStartTime; time_t mStartTime;
uint32_t mProcessType; uint32_t mProcessType;
@ -120,14 +108,10 @@ CrashReporterParent::GeneratePairedMinidump(Toplevel* t)
child = t->OtherProcess(); child = t->OtherProcess();
#endif #endif
nsCOMPtr<nsIFile> childDump; nsCOMPtr<nsIFile> childDump;
nsCOMPtr<nsIFile> parentDump;
if (CrashReporter::CreatePairedMinidumps(child, if (CrashReporter::CreatePairedMinidumps(child,
mMainThread, mMainThread,
&mHangID, getter_AddRefs(childDump)) &&
getter_AddRefs(childDump), CrashReporter::GetIDFromMinidump(childDump, mChildDumpID)) {
getter_AddRefs(parentDump)) &&
CrashReporter::GetIDFromMinidump(childDump, mChildDumpID) &&
CrashReporter::GetIDFromMinidump(parentDump, mParentDumpID)) {
return true; return true;
} }
return false; return false;

View File

@ -174,28 +174,24 @@ PluginModuleParent::WriteExtraDataForMinidump(AnnotationTable& notes)
CrashReporterParent* crashReporter = CrashReporter(); CrashReporterParent* crashReporter = CrashReporter();
if (crashReporter) { if (crashReporter) {
const nsString& hangID = crashReporter->HangID();
if (!hangID.IsEmpty()) {
notes.Put(CS("HangID"), NS_ConvertUTF16toUTF8(hangID));
#ifdef XP_WIN #ifdef XP_WIN
if (mPluginCpuUsageOnHang.Length() > 0) { if (mPluginCpuUsageOnHang.Length() > 0) {
notes.Put(CS("NumberOfProcessors"), notes.Put(CS("NumberOfProcessors"),
nsPrintfCString("%d", PR_GetNumberOfProcessors())); nsPrintfCString("%d", PR_GetNumberOfProcessors()));
nsCString cpuUsageStr; nsCString cpuUsageStr;
cpuUsageStr.AppendFloat(std::ceil(mPluginCpuUsageOnHang[0] * 100) / 100); cpuUsageStr.AppendFloat(std::ceil(mPluginCpuUsageOnHang[0] * 100) / 100);
notes.Put(CS("PluginCpuUsage"), cpuUsageStr); notes.Put(CS("PluginCpuUsage"), cpuUsageStr);
#ifdef MOZ_CRASHREPORTER_INJECTOR #ifdef MOZ_CRASHREPORTER_INJECTOR
for (uint32_t i=1; i<mPluginCpuUsageOnHang.Length(); ++i) { for (uint32_t i=1; i<mPluginCpuUsageOnHang.Length(); ++i) {
nsCString tempStr; nsCString tempStr;
tempStr.AppendFloat(std::ceil(mPluginCpuUsageOnHang[i] * 100) / 100); tempStr.AppendFloat(std::ceil(mPluginCpuUsageOnHang[i] * 100) / 100);
notes.Put(nsPrintfCString("CpuUsageFlashProcess%d", i), tempStr); notes.Put(nsPrintfCString("CpuUsageFlashProcess%d", i), tempStr);
}
#endif
} }
#endif #endif
} }
#endif
} }
} }
#endif // MOZ_CRASHREPORTER #endif // MOZ_CRASHREPORTER
@ -297,14 +293,19 @@ PluginModuleParent::ShouldContinueFromReplyTimeout()
{ {
#ifdef MOZ_CRASHREPORTER #ifdef MOZ_CRASHREPORTER
CrashReporterParent* crashReporter = CrashReporter(); CrashReporterParent* crashReporter = CrashReporter();
crashReporter->AnnotateCrashReport(NS_LITERAL_CSTRING("PluginHang"),
NS_LITERAL_CSTRING("1"));
if (crashReporter->GeneratePairedMinidump(this)) { if (crashReporter->GeneratePairedMinidump(this)) {
mBrowserDumpID = crashReporter->ParentDumpID();
mPluginDumpID = crashReporter->ChildDumpID(); mPluginDumpID = crashReporter->ChildDumpID();
PLUGIN_LOG_DEBUG( PLUGIN_LOG_DEBUG(
("generated paired browser/plugin minidumps: %s/%s (ID=%s)", ("generated paired browser/plugin minidumps: %s)",
NS_ConvertUTF16toUTF8(mBrowserDumpID).get(), NS_ConvertUTF16toUTF8(mPluginDumpID).get()));
NS_ConvertUTF16toUTF8(mPluginDumpID).get(),
NS_ConvertUTF16toUTF8(crashReporter->HangID()).get())); crashReporter->AnnotateCrashReport(
NS_LITERAL_CSTRING("additional_minidumps"),
NS_LITERAL_CSTRING("browser"));
// TODO: collect Flash minidumps here
} else { } else {
NS_WARNING("failed to capture paired minidumps from hang"); NS_WARNING("failed to capture paired minidumps from hang");
} }
@ -377,9 +378,9 @@ PluginModuleParent::ProcessFirstMinidump()
AnnotationTable notes; AnnotationTable notes;
notes.Init(4); notes.Init(4);
WriteExtraDataForMinidump(notes); WriteExtraDataForMinidump(notes);
if (!mPluginDumpID.IsEmpty() && !mBrowserDumpID.IsEmpty()) { if (!mPluginDumpID.IsEmpty()) {
crashReporter->GenerateHangCrashReport(&notes); crashReporter->GenerateChildData(&notes);
return; return;
} }

View File

@ -1,47 +1,12 @@
Components.utils.import("resource://gre/modules/KeyValueParser.jsm");
const Cc = Components.classes; const Cc = Components.classes;
const Ci = Components.interfaces; const Ci = Components.interfaces;
var success = false; var success = false;
var observerFired = false; var observerFired = false;
function parseKeyValuePairs(text) {
var lines = text.split('\n');
var data = {};
for (let i = 0; i < lines.length; i++) {
if (lines[i] == '')
continue;
// can't just .split() because the value might contain = characters
let eq = lines[i].indexOf('=');
if (eq != -1) {
let [key, value] = [lines[i].substring(0, eq),
lines[i].substring(eq + 1)];
if (key && value)
data[key] = value.replace(/\\n/g, "\n").replace(/\\\\/g, "\\");
}
}
return data;
}
function parseKeyValuePairsFromFile(file) {
var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
fstream.init(file, -1, 0, 0);
var is = Cc["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Ci.nsIConverterInputStream);
is.init(fstream, "UTF-8", 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
var str = {};
var contents = '';
while (is.readString(4096, str) != 0) {
contents += str.value;
}
is.close();
fstream.close();
return parseKeyValuePairs(contents);
}
var testObserver = { var testObserver = {
idleHang: true, idleHang: true,
@ -55,10 +20,8 @@ var testObserver = {
var pluginId = subject.getPropertyAsAString("pluginDumpID"); var pluginId = subject.getPropertyAsAString("pluginDumpID");
isnot(pluginId, "", "got a non-empty plugin crash id"); isnot(pluginId, "", "got a non-empty plugin crash id");
var browserId = subject.getPropertyAsAString("browserDumpID");
isnot(browserId, "", "got a non-empty browser crash id"); // check plugin dump and extra files
// check dump and extra files
let directoryService = let directoryService =
Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
let profD = directoryService.get("ProfD", Ci.nsIFile); let profD = directoryService.get("ProfD", Ci.nsIFile);
@ -66,18 +29,31 @@ var testObserver = {
let pluginDumpFile = profD.clone(); let pluginDumpFile = profD.clone();
pluginDumpFile.append(pluginId + ".dmp"); pluginDumpFile.append(pluginId + ".dmp");
ok(pluginDumpFile.exists(), "plugin minidump exists"); ok(pluginDumpFile.exists(), "plugin minidump exists");
let browserDumpFile = profD.clone();
browserDumpFile.append(browserId + ".dmp");
ok(browserDumpFile.exists(), "browser minidump exists");
let pluginExtraFile = profD.clone(); let pluginExtraFile = profD.clone();
pluginExtraFile.append(pluginId + ".extra"); pluginExtraFile.append(pluginId + ".extra");
ok(pluginExtraFile.exists(), "plugin extra file exists"); ok(pluginExtraFile.exists(), "plugin extra file exists");
let browserExtraFile = profD.clone();
browserExtraFile.append(browserId + ".extra");
ok(pluginExtraFile.exists(), "browser extra file exists");
// check cpu usage field
let extraData = parseKeyValuePairsFromFile(pluginExtraFile); let extraData = parseKeyValuePairsFromFile(pluginExtraFile);
// check additional dumps
ok("additional_minidumps" in extraData, "got field for additional minidumps");
let additionalDumps = extraData.additional_minidumps.split(',');
ok(additionalDumps.indexOf('browser') >= 0, "browser in additional_minidumps");
let additionalDumpFiles = [];
for (let name of additionalDumps) {
let file = profD.clone();
file.append(pluginId + "-" + name + ".dmp");
ok(file.exists(), "additional dump '"+name+"' exists");
if (file.exists()) {
additionalDumpFiles.push(file);
}
}
// check cpu usage field
ok("PluginCpuUsage" in extraData, "got extra field for plugin cpu usage"); ok("PluginCpuUsage" in extraData, "got extra field for plugin cpu usage");
let cpuUsage = parseFloat(extraData["PluginCpuUsage"]); let cpuUsage = parseFloat(extraData["PluginCpuUsage"]);
if (this.idleHang) { if (this.idleHang) {
@ -85,16 +61,17 @@ var testObserver = {
} else { } else {
ok(cpuUsage > 0, "plugin cpu usage is >0%"); ok(cpuUsage > 0, "plugin cpu usage is >0%");
} }
// check processor count field // check processor count field
ok("NumberOfProcessors" in extraData, "got extra field for processor count"); ok("NumberOfProcessors" in extraData, "got extra field for processor count");
ok(parseInt(extraData["NumberOfProcessors"]) > 0, "number of processors is >0"); ok(parseInt(extraData["NumberOfProcessors"]) > 0, "number of processors is >0");
// cleanup, to be nice // cleanup, to be nice
pluginDumpFile.remove(false); pluginDumpFile.remove(false);
browserDumpFile.remove(false);
pluginExtraFile.remove(false); pluginExtraFile.remove(false);
browserExtraFile.remove(false); for (let file of additionalDumpFiles) {
file.remove(false);
}
}, },
QueryInterface: function(iid) { QueryInterface: function(iid) {

View File

@ -6,14 +6,15 @@
<script class="testbody" type="application/javascript"> <script class="testbody" type="application/javascript">
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
const isOSXLion = navigator.userAgent.indexOf("Mac OS X 10.7") != -1;
const isOSXMtnLion = navigator.userAgent.indexOf("Mac OS X 10.8") != -1;
if (isOSXLion || isOSXMtnLion) {
todo(false, "Can't test plugin crash notification on OS X 10.7 or 10.8, see bug 705047");
SimpleTest.finish();
}
window.frameLoaded = function frameLoaded_toCrash() { window.frameLoaded = function frameLoaded_toCrash() {
const isOSXLion = navigator.userAgent.indexOf("Mac OS X 10.7") != -1;
const isOSXMtnLion = navigator.userAgent.indexOf("Mac OS X 10.8") != -1;
if (isOSXLion || isOSXMtnLion) {
todo(false, "Can't test plugin crash notification on OS X 10.7 or 10.8, see bug 705047");
SimpleTest.finish();
return;
}
if (!SimpleTest.testPluginIsOOP()) { if (!SimpleTest.testPluginIsOOP()) {
ok(true, "Skipping this test when test plugin is not OOP."); ok(true, "Skipping this test when test plugin is not OOP.");
SimpleTest.finish(); SimpleTest.finish();

View File

@ -47,11 +47,9 @@ SpecialPowers.prototype._messageReceived = function(aMessage) {
switch (aMessage.name) { switch (aMessage.name) {
case "SPProcessCrashService": case "SPProcessCrashService":
if (aMessage.json.type == "crash-observed") { if (aMessage.json.type == "crash-observed") {
var self = this; for (let e of aMessage.json.dumpIDs) {
aMessage.json.dumpIDs.forEach(function(id) { this._encounteredCrashDumpFiles.push(e.id + "." + e.extension);
self._encounteredCrashDumpFiles.push(id + ".dmp"); }
self._encounteredCrashDumpFiles.push(id + ".extra");
});
} }
break; break;

View File

@ -54,11 +54,9 @@ ChromePowers.prototype._receiveMessage = function(aMessage) {
// Hack out register/unregister specifically for browser-chrome leaks // Hack out register/unregister specifically for browser-chrome leaks
break; break;
} else if (aMessage.type == "crash-observed") { } else if (aMessage.type == "crash-observed") {
var self = this; for (let e of msg.dumpIDs) {
msg.dumpIDs.forEach(function(id) { this._encounteredCrashDumpFiles.push(e.id + "." + e.extension);
self._encounteredCrashDumpFiles.push(id + ".dmp"); }
self._encounteredCrashDumpFiles.push(id + ".extra");
});
} }
default: default:
// All calls go here, because we need to handle SPProcessCrashService calls as well // All calls go here, because we need to handle SPProcessCrashService calls as well

View File

@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Components.utils.import("resource://gre/modules/Services.jsm");
/** /**
* Special Powers Exception - used to throw exceptions nicely * Special Powers Exception - used to throw exceptions nicely
**/ **/
@ -19,6 +21,42 @@ function SpecialPowersObserverAPI() {
this._processCrashObserversRegistered = false; this._processCrashObserversRegistered = false;
} }
function parseKeyValuePairs(text) {
var lines = text.split('\n');
var data = {};
for (let i = 0; i < lines.length; i++) {
if (lines[i] == '')
continue;
// can't just .split() because the value might contain = characters
let eq = lines[i].indexOf('=');
if (eq != -1) {
let [key, value] = [lines[i].substring(0, eq),
lines[i].substring(eq + 1)];
if (key && value)
data[key] = value.replace(/\\n/g, "\n").replace(/\\\\/g, "\\");
}
}
return data;
}
function parseKeyValuePairsFromFile(file) {
var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
fstream.init(file, -1, 0, 0);
var is = Cc["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Ci.nsIConverterInputStream);
is.init(fstream, "UTF-8", 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
var str = {};
var contents = '';
while (is.readString(4096, str) != 0) {
contents += str.value;
}
is.close();
fstream.close();
return parseKeyValuePairs(contents);
}
SpecialPowersObserverAPI.prototype = { SpecialPowersObserverAPI.prototype = {
_observe: function(aSubject, aTopic, aData) { _observe: function(aSubject, aTopic, aData) {
@ -28,15 +66,25 @@ SpecialPowersObserverAPI.prototype = {
function addDumpIDToMessage(propertyName) { function addDumpIDToMessage(propertyName) {
var id = aSubject.getPropertyAsAString(propertyName); var id = aSubject.getPropertyAsAString(propertyName);
if (id) { if (id) {
message.dumpIDs.push(id); message.dumpIDs.push({id: id, extension: "dmp"});
message.dumpIDs.push({id: id, extension: "extra"});
} }
} }
var message = { type: "crash-observed", dumpIDs: [] }; var message = { type: "crash-observed", dumpIDs: [] };
aSubject = aSubject.QueryInterface(Components.interfaces.nsIPropertyBag2); aSubject = aSubject.QueryInterface(Ci.nsIPropertyBag2);
if (aTopic == "plugin-crashed") { if (aTopic == "plugin-crashed") {
addDumpIDToMessage("pluginDumpID"); addDumpIDToMessage("pluginDumpID");
addDumpIDToMessage("browserDumpID"); addDumpIDToMessage("browserDumpID");
let pluginID = aSubject.getPropertyAsAString("pluginDumpID");
let extra = this._getExtraData(pluginID);
if (extra && ("additional_minidumps" in extra)) {
let dumpNames = extra.additional_minidumps.split(',');
for (let name of dumpNames) {
message.dumpIDs.push({id: pluginID + "-" + name, extension: "dmp"});
}
}
} else { // ipc:content-shutdown } else { // ipc:content-shutdown
addDumpIDToMessage("dumpID"); addDumpIDToMessage("dumpID");
} }
@ -47,14 +95,21 @@ SpecialPowersObserverAPI.prototype = {
_getCrashDumpDir: function() { _getCrashDumpDir: function() {
if (!this._crashDumpDir) { if (!this._crashDumpDir) {
var directoryService = Components.classes["@mozilla.org/file/directory_service;1"] this._crashDumpDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
.getService(Components.interfaces.nsIProperties);
this._crashDumpDir = directoryService.get("ProfD", Components.interfaces.nsIFile);
this._crashDumpDir.append("minidumps"); this._crashDumpDir.append("minidumps");
} }
return this._crashDumpDir; return this._crashDumpDir;
}, },
_getExtraData: function(dumpId) {
let extraFile = this._getCrashDumpDir().clone();
extraFile.append(dumpId + ".extra");
if (!extraFile.exists()) {
return null;
}
return parseKeyValuePairsFromFile(extraFile);
},
_deleteCrashDumpFiles: function(aFilenames) { _deleteCrashDumpFiles: function(aFilenames) {
var crashDumpDir = this._getCrashDumpDir(); var crashDumpDir = this._getCrashDumpDir();
if (!crashDumpDir.exists()) { if (!crashDumpDir.exists()) {
@ -83,7 +138,7 @@ SpecialPowersObserverAPI.prototype = {
var crashDumpFiles = []; var crashDumpFiles = [];
while (entries.hasMoreElements()) { while (entries.hasMoreElements()) {
var file = entries.getNext().QueryInterface(Components.interfaces.nsIFile); var file = entries.getNext().QueryInterface(Ci.nsIFile);
var path = String(file.path); var path = String(file.path);
if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) { if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) {
crashDumpFiles.push(path); crashDumpFiles.push(path);
@ -93,9 +148,7 @@ SpecialPowersObserverAPI.prototype = {
}, },
_getURI: function (url) { _getURI: function (url) {
return Components.classes["@mozilla.org/network/io-service;1"] return Services.io.newURI(url, null, null);
.getService(Components.interfaces.nsIIOService)
.newURI(url, null, null);
}, },
/** /**
@ -105,8 +158,7 @@ SpecialPowersObserverAPI.prototype = {
_receiveMessageAPI: function(aMessage) { _receiveMessageAPI: function(aMessage) {
switch(aMessage.name) { switch(aMessage.name) {
case "SPPrefService": case "SPPrefService":
var prefs = Components.classes["@mozilla.org/preferences-service;1"]. var prefs = Services.prefs;
getService(Components.interfaces.nsIPrefBranch);
var prefType = aMessage.json.prefType.toUpperCase(); var prefType = aMessage.json.prefType.toUpperCase();
var prefName = aMessage.json.prefName; var prefName = aMessage.json.prefName;
var prefValue = "prefValue" in aMessage.json ? aMessage.json.prefValue : null; var prefValue = "prefValue" in aMessage.json ? aMessage.json.prefValue : null;
@ -172,20 +224,17 @@ SpecialPowersObserverAPI.prototype = {
break; break;
case "SPPermissionManager": case "SPPermissionManager":
let perms =
Components.classes["@mozilla.org/permissionmanager;1"]
.getService(Components.interfaces.nsIPermissionManager);
let msg = aMessage.json; let msg = aMessage.json;
let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager); let secMan = Services.scriptSecurityManager;
let principal = secMan.getAppCodebasePrincipal(this._getURI(msg.url), msg.appId, msg.isInBrowserElement); let principal = secMan.getAppCodebasePrincipal(this._getURI(msg.url), msg.appId, msg.isInBrowserElement);
switch (msg.op) { switch (msg.op) {
case "add": case "add":
perms.addFromPrincipal(principal, msg.type, msg.permission); Services.perms.addFromPrincipal(principal, msg.type, msg.permission);
break; break;
case "remove": case "remove":
perms.removeFromPrincipal(principal, msg.type); Services.perms.removeFromPrincipal(principal, msg.type);
break; break;
default: default:
throw new SpecialPowersException("Invalid operation for " + throw new SpecialPowersException("Invalid operation for " +

View File

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/KeyValueParser.jsm");
let EXPORTED_SYMBOLS = [ let EXPORTED_SYMBOLS = [
"CrashSubmit" "CrashSubmit"
@ -21,42 +22,6 @@ let reportURL = null;
let strings = null; let strings = null;
let myListener = null; let myListener = null;
function parseKeyValuePairs(text) {
var lines = text.split('\n');
var data = {};
for (let i = 0; i < lines.length; i++) {
if (lines[i] == '')
continue;
// can't just .split() because the value might contain = characters
let eq = lines[i].indexOf('=');
if (eq != -1) {
let [key, value] = [lines[i].substring(0, eq),
lines[i].substring(eq + 1)];
if (key && value)
data[key] = value.replace(/\\n/g, "\n").replace(/\\\\/g, "\\");
}
}
return data;
}
function parseKeyValuePairsFromFile(file) {
var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
fstream.init(file, -1, 0, 0);
var is = Cc["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Ci.nsIConverterInputStream);
is.init(fstream, "UTF-8", 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
var str = {};
var contents = '';
while (is.readString(4096, str) != 0) {
contents += str.value;
}
is.close();
fstream.close();
return parseKeyValuePairs(contents);
}
function parseINIStrings(file) { function parseINIStrings(file) {
var factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]. var factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
getService(Ci.nsIINIParserFactory); getService(Ci.nsIINIParserFactory);
@ -159,6 +124,7 @@ function Submitter(id, submitSuccess, submitError, noThrottle) {
this.successCallback = submitSuccess; this.successCallback = submitSuccess;
this.errorCallback = submitError; this.errorCallback = submitError;
this.noThrottle = noThrottle; this.noThrottle = noThrottle;
this.additionalDumps = [];
} }
Submitter.prototype = { Submitter.prototype = {
@ -177,6 +143,9 @@ Submitter.prototype = {
try { try {
this.dump.remove(false); this.dump.remove(false);
this.extra.remove(false); this.extra.remove(false);
for (let i of this.additionalDumps) {
i.dump.remove(false);
}
} }
catch (ex) { catch (ex) {
// report an error? not much the user can do here. // report an error? not much the user can do here.
@ -193,6 +162,7 @@ Submitter.prototype = {
this.iframe = null; this.iframe = null;
this.dump = null; this.dump = null;
this.extra = null; this.extra = null;
this.additionalDumps = null;
// remove this object from the list of active submissions // remove this object from the list of active submissions
let idx = CrashSubmit._activeSubmissions.indexOf(this); let idx = CrashSubmit._activeSubmissions.indexOf(this);
if (idx != -1) if (idx != -1)
@ -221,8 +191,19 @@ Submitter.prototype = {
// tell the server not to throttle this, since it was manually submitted // tell the server not to throttle this, since it was manually submitted
formData.append("Throttleable", "0"); formData.append("Throttleable", "0");
} }
// add the minidump // add the minidumps
formData.append("upload_file_minidump", File(this.dump.path)); formData.append("upload_file_minidump", File(this.dump.path));
if (this.additionalDumps.length > 0) {
let names = [];
for (let i of this.additionalDumps) {
names.push(i.name);
formData.append("upload_file_minidump_"+i.name,
File(i.dump.path));
}
formData.append("additional_minidumps", names.join(","));
}
let self = this; let self = this;
xhr.addEventListener("readystatechange", function (aEvt) { xhr.addEventListener("readystatechange", function (aEvt) {
if (xhr.readyState == 4) { if (xhr.readyState == 4) {
@ -274,10 +255,26 @@ Submitter.prototype = {
return false; return false;
} }
let reportData = parseKeyValuePairsFromFile(extra);
let additionalDumps = [];
if ("additional_minidumps" in reportData) {
let names = extraData.additional_minidumps.split(',');
for (let name in names) {
let [dump, extra] = getPendingMiniDump(this.id + "-" + name);
if (!dump.exists()) {
this.notifyStatus(FAILED);
this.cleanup();
return false;
}
additionalDumps.push({'name': name, 'dump': dump});
}
}
this.notifyStatus(SUBMITTING); this.notifyStatus(SUBMITTING);
this.dump = dump; this.dump = dump;
this.extra = extra; this.extra = extra;
this.additionalDumps = additionalDumps;
if (!this.submitForm()) { if (!this.submitForm()) {
this.notifyStatus(FAILED); this.notifyStatus(FAILED);

View File

@ -0,0 +1,49 @@
/* 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/. */
Components.utils.import("resource://gre/modules/Services.jsm");
let EXPORTED_SYMBOLS = [
"parseKeyValuePairs",
"parseKeyValuePairsFromFile"
];
const Cc = Components.classes;
const Ci = Components.interfaces;
function parseKeyValuePairs(text) {
let lines = text.split('\n');
let data = {};
for (let i = 0; i < lines.length; i++) {
if (lines[i] == '')
continue;
// can't just .split() because the value might contain = characters
let eq = lines[i].indexOf('=');
if (eq != -1) {
let [key, value] = [lines[i].substring(0, eq),
lines[i].substring(eq + 1)];
if (key && value)
data[key] = value.replace(/\\n/g, "\n").replace(/\\\\/g, "\\");
}
}
return data;
}
function parseKeyValuePairsFromFile(file) {
let fstream = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
fstream.init(file, -1, 0, 0);
let is = Cc["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Ci.nsIConverterInputStream);
is.init(fstream, "UTF-8", 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
let str = {};
let contents = '';
while (is.readString(4096, str) != 0) {
contents += str.value;
}
is.close();
fstream.close();
return parseKeyValuePairs(contents);
}

View File

@ -98,6 +98,7 @@ FORCE_STATIC_LIB = 1
EXTRA_JS_MODULES = \ EXTRA_JS_MODULES = \
CrashSubmit.jsm \ CrashSubmit.jsm \
KeyValueParser.jsm \
$(NULL) $(NULL)
ifdef ENABLE_TESTS ifdef ENABLE_TESTS

View File

@ -2046,8 +2046,15 @@ MoveToPending(nsIFile* dumpFile, nsIFile* extraFile)
if (!GetPendingDir(getter_AddRefs(pendingDir))) if (!GetPendingDir(getter_AddRefs(pendingDir)))
return false; return false;
return NS_SUCCEEDED(dumpFile->MoveTo(pendingDir, EmptyString())) && if (NS_FAILED(dumpFile->MoveTo(pendingDir, EmptyString()))) {
NS_SUCCEEDED(extraFile->MoveTo(pendingDir, EmptyString())); return false;
}
if (extraFile && NS_FAILED(extraFile->MoveTo(pendingDir, EmptyString()))) {
return false;
}
return true;
} }
static void static void
@ -2513,11 +2520,24 @@ TakeMinidumpForChild(uint32_t childPid, nsIFile** dump, uint32_t* aSequence)
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// CreatePairedMinidumps() and helpers // CreatePairedMinidumps() and helpers
// //
struct PairedDumpContext {
nsCOMPtr<nsIFile>* minidump; void
nsCOMPtr<nsIFile>* extra; RenameAdditionalHangMinidump(nsIFile* minidump, nsIFile* childMinidump,
const Blacklist& blacklist; const nsACString& name)
}; {
nsCOMPtr<nsIFile> directory;
childMinidump->GetParent(getter_AddRefs(directory));
if (!directory)
return;
nsAutoCString leafName;
childMinidump->GetNativeLeafName(leafName);
// turn "<id>.dmp" into "<id>-<name>.dmp
leafName.Insert(NS_LITERAL_CSTRING("-") + name, leafName.Length() - 4);
minidump->MoveToNative(directory, leafName);
}
static bool static bool
PairedDumpCallback(const XP_CHAR* dump_path, PairedDumpCallback(const XP_CHAR* dump_path,
@ -2529,10 +2549,7 @@ PairedDumpCallback(const XP_CHAR* dump_path,
#endif #endif
bool succeeded) bool succeeded)
{ {
PairedDumpContext* ctx = static_cast<PairedDumpContext*>(context); nsCOMPtr<nsIFile>& minidump = *static_cast< nsCOMPtr<nsIFile>* >(context);
nsCOMPtr<nsIFile>& minidump = *ctx->minidump;
nsCOMPtr<nsIFile>& extra = *ctx->extra;
const Blacklist& blacklist = ctx->blacklist;
xpstring dump(dump_path); xpstring dump(dump_path);
dump += XP_PATH_SEPARATOR; dump += XP_PATH_SEPARATOR;
@ -2540,7 +2557,29 @@ PairedDumpCallback(const XP_CHAR* dump_path,
dump += dumpFileExtension; dump += dumpFileExtension;
CreateFileFromPath(dump, getter_AddRefs(minidump)); CreateFileFromPath(dump, getter_AddRefs(minidump));
return WriteExtraForMinidump(minidump, blacklist, getter_AddRefs(extra)); return true;
}
static bool
PairedDumpCallbackExtra(const XP_CHAR* dump_path,
const XP_CHAR* minidump_id,
void* context,
#ifdef XP_WIN32
EXCEPTION_POINTERS* /*unused*/,
MDRawAssertionInfo* /*unused*/,
#endif
bool succeeded)
{
PairedDumpCallback(dump_path, minidump_id, context,
#ifdef XP_WIN32
nullptr, nullptr,
#endif
succeeded);
nsCOMPtr<nsIFile>& minidump = *static_cast< nsCOMPtr<nsIFile>* >(context);
nsCOMPtr<nsIFile> extra;
return WriteExtraForMinidump(minidump, Blacklist(), getter_AddRefs(extra));
} }
ThreadId ThreadId
@ -2571,31 +2610,11 @@ CurrentThreadId()
bool bool
CreatePairedMinidumps(ProcessHandle childPid, CreatePairedMinidumps(ProcessHandle childPid,
ThreadId childBlamedThread, ThreadId childBlamedThread,
nsAString* pairGUID, nsIFile** childDump)
nsIFile** childDump,
nsIFile** parentDump)
{ {
if (!GetEnabled()) if (!GetEnabled())
return false; return false;
// create the UUID for the hang dump as a pair
nsresult rv;
nsCOMPtr<nsIUUIDGenerator> uuidgen =
do_GetService("@mozilla.org/uuid-generator;1", &rv);
NS_ENSURE_SUCCESS(rv, false);
nsID id;
rv = uuidgen->GenerateUUIDInPlace(&id);
NS_ENSURE_SUCCESS(rv, false);
char chars[NSID_LENGTH];
id.ToProvidedString(chars);
CopyASCIItoUTF16(chars, *pairGUID);
// trim off braces
pairGUID->Cut(0, 1);
pairGUID->Cut(pairGUID->Length()-1, 1);
#ifdef XP_MACOSX #ifdef XP_MACOSX
mach_port_t childThread = MACH_PORT_NULL; mach_port_t childThread = MACH_PORT_NULL;
thread_act_port_array_t threads_for_task; thread_act_port_array_t threads_for_task;
@ -2611,43 +2630,41 @@ CreatePairedMinidumps(ProcessHandle childPid,
// dump the child // dump the child
nsCOMPtr<nsIFile> childMinidump; nsCOMPtr<nsIFile> childMinidump;
nsCOMPtr<nsIFile> childExtra;
Blacklist childBlacklist(kSubprocessBlacklist,
ArrayLength(kSubprocessBlacklist));
PairedDumpContext childCtx =
{ &childMinidump, &childExtra, childBlacklist };
if (!google_breakpad::ExceptionHandler::WriteMinidumpForChild( if (!google_breakpad::ExceptionHandler::WriteMinidumpForChild(
childPid, childPid,
childThread, childThread,
gExceptionHandler->dump_path(), gExceptionHandler->dump_path(),
PairedDumpCallback, PairedDumpCallbackExtra,
&childCtx)) static_cast<void*>(&childMinidump)))
return false; return false;
nsCOMPtr<nsIFile> childExtra;
GetExtraFileForMinidump(childMinidump, getter_AddRefs(childExtra));
// dump the parent // dump the parent
nsCOMPtr<nsIFile> parentMinidump; nsCOMPtr<nsIFile> parentMinidump;
nsCOMPtr<nsIFile> parentExtra;
// nothing's blacklisted for this process
Blacklist parentBlacklist;
PairedDumpContext parentCtx =
{ &parentMinidump, &parentExtra, parentBlacklist };
if (!google_breakpad::ExceptionHandler::WriteMinidump( if (!google_breakpad::ExceptionHandler::WriteMinidump(
gExceptionHandler->dump_path(), gExceptionHandler->dump_path(),
true, // write exception stream true, // write exception stream
PairedDumpCallback, PairedDumpCallback,
&parentCtx)) static_cast<void*>(&parentMinidump))) {
return false;
// success childMinidump->Remove(false);
if (ShouldReport()) { childExtra->Remove(false);
MoveToPending(childMinidump, childExtra);
MoveToPending(parentMinidump, parentExtra); return false;
} }
*childDump = NULL; // success
*parentDump = NULL; RenameAdditionalHangMinidump(parentMinidump, childMinidump,
childMinidump.swap(*childDump); NS_LITERAL_CSTRING("browser"));
parentMinidump.swap(*parentDump);
if (ShouldReport()) {
MoveToPending(childMinidump, childExtra);
MoveToPending(parentMinidump, nullptr);
}
childMinidump.forget(childDump);
return true; return true;
} }

View File

@ -60,6 +60,8 @@ bool GetExtraFileForID(const nsAString& id, nsIFile** extraFile);
bool GetExtraFileForMinidump(nsIFile* minidump, nsIFile** extraFile); bool GetExtraFileForMinidump(nsIFile* minidump, nsIFile** extraFile);
bool AppendExtraData(const nsAString& id, const AnnotationTable& data); bool AppendExtraData(const nsAString& id, const AnnotationTable& data);
bool AppendExtraData(nsIFile* extraFile, const AnnotationTable& data); bool AppendExtraData(nsIFile* extraFile, const AnnotationTable& data);
void RenameAdditionalHangMinidump(nsIFile* minidump, nsIFile* childMinidump,
const nsACString& name);
#ifdef XP_WIN32 #ifdef XP_WIN32
nsresult WriteMinidumpForException(EXCEPTION_POINTERS* aExceptionInfo); nsresult WriteMinidumpForException(EXCEPTION_POINTERS* aExceptionInfo);
@ -103,18 +105,19 @@ typedef int ThreadId;
// hoops for us. // hoops for us.
ThreadId CurrentThreadId(); ThreadId CurrentThreadId();
// Create new minidumps that are snapshots of the state of this parent // Create a hang report with two minidumps that are snapshots of the state
// process and |childPid|. Return true on success along with the // of this parent process and |childPid|. The "main" minidump will be the
// minidumps and a new UUID that can be used to correlate the dumps. // child process, and this parent process will have the _browser extension.
// //
// If this function fails, it's the caller's responsibility to clean // Returns true on success. If this function fails, it will attempt to delete
// up |childDump| and |parentDump|. Either or both can be created and // any files that were created.
// returned non-null on failure. //
// The .extra information created will not include an additional_minidumps
// annotation: the caller should annotate additional_minidumps with
// at least "browser" and perhaps other minidumps attached to this report.
bool CreatePairedMinidumps(ProcessHandle childPid, bool CreatePairedMinidumps(ProcessHandle childPid,
ThreadId childBlamedThread, ThreadId childBlamedThread,
nsAString* pairGUID, nsIFile** childDump);
nsIFile** childDump,
nsIFile** parentDump);
# if defined(XP_WIN32) || defined(XP_MACOSX) # if defined(XP_WIN32) || defined(XP_MACOSX)
// Parent-side API for children // Parent-side API for children

View File

@ -144,42 +144,6 @@ function do_content_crash(setup, callback)
); );
} }
// Utility functions for parsing .extra files
function parseKeyValuePairs(text) {
var lines = text.split('\n');
var data = {};
for (let i = 0; i < lines.length; i++) {
if (lines[i] == '')
continue;
// can't just .split() because the value might contain = characters
let eq = lines[i].indexOf('=');
if (eq != -1) {
let [key, value] = [lines[i].substring(0, eq),
lines[i].substring(eq + 1)];
if (key && value)
data[key] = value.replace("\\n", "\n", "g").replace("\\\\", "\\", "g");
}
}
return data;
}
function parseKeyValuePairsFromFile(file) {
var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
createInstance(Components.interfaces.nsIFileInputStream);
fstream.init(file, -1, 0, 0);
var is = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Components.interfaces.nsIConverterInputStream);
is.init(fstream, "UTF-8", 1024, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
var str = {};
var contents = '';
while (is.readString(4096, str) != 0) {
contents += str.value;
}
is.close();
fstream.close();
return parseKeyValuePairs(contents);
}
// Import binary APIs via js-ctypes. // Import binary APIs via js-ctypes.
Components.utils.import("resource://test/CrashTestUtils.jsm"); Components.utils.import("resource://test/CrashTestUtils.jsm");
Components.utils.import("resource://gre/modules/KeyValueParser.jsm");