Bug 1393800 - Have mochitests expecting crashes wait for the crashes to be recorded before clean up; r?ted.mielczarek
This patch includes a bunch of somewhat related fixes, these are:
- Ensuring that when a mochitest calls SimpleTest.expectChildProcessCrash()
the harness will wait for the crashes to be recorded before deleting the
dump files. This involves a message round-trip between the content and
parent process so to minimize its performance impact on all the non-crashing
tests it is done only when required.
- As an additional optimization, the SimpleTest harness will not send a
message to the content process anymore whenever it receives an
ipc:content-shutdown event, instead it does it only for abnormal shutdowns.
- Manually fixing remaining mochitests causing crashes to wait for crashes to
be recorded before finishing and deleting the dump files.
- Modifying BrowserTestUtils.crashBrowser() so that it optionally does not
delete the dump files, this is useful for tests that submit their dumps and
thus delete them on their own.
MozReview-Commit-ID: 4SLJ8BjJ18n
--- a/browser/base/content/test/plugins/browser_pluginCrashReportNonDeterminism.js
+++ b/browser/base/content/test/plugins/browser_pluginCrashReportNonDeterminism.js
@@ -1,11 +1,13 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
+Cu.import("resource://gre/modules/PromiseUtils.jsm");
+
/**
* With e10s, plugins must run in their own process. This means we have
* three processes at a minimum when we're running a plugin:
*
* 1) The main browser, or "chrome" process
* 2) The content process hosting the plugin instance
* 3) The plugin process
*
@@ -74,61 +76,66 @@ function preparePlugin(browser, pluginFa
});
return plugin.runID;
}).then((runID) => {
browser.messageManager.sendAsyncMessage("BrowserPlugins:Test:ClearCrashData");
return runID;
});
}
-add_task(async function setup() {
- // Bypass click-to-play
- setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
+// Bypass click-to-play
+setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
+
+// Deferred promise object used by the test to wait for the crash handler
+let crashDeferred = null;
- // Clear out any minidumps we create from plugins - we really don't care
- // about them.
- let crashObserver = (subject, topic, data) => {
- if (topic != "plugin-crashed") {
- return;
- }
+// Clear out any minidumps we create from plugins - we really don't care
+// about them.
+let crashObserver = (subject, topic, data) => {
+ if (topic != "plugin-crashed") {
+ return;
+ }
- let propBag = subject.QueryInterface(Ci.nsIPropertyBag2);
- let minidumpID = propBag.getPropertyAsAString("pluginDumpID");
+ let propBag = subject.QueryInterface(Ci.nsIPropertyBag2);
+ let minidumpID = propBag.getPropertyAsAString("pluginDumpID");
- Services.crashmanager.ensureCrashIsPresent(minidumpID).then(() => {
- let minidumpDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
- minidumpDir.append("minidumps");
+ Services.crashmanager.ensureCrashIsPresent(minidumpID).then(() => {
+ let minidumpDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ minidumpDir.append("minidumps");
- let pluginDumpFile = minidumpDir.clone();
- pluginDumpFile.append(minidumpID + ".dmp");
+ let pluginDumpFile = minidumpDir.clone();
+ pluginDumpFile.append(minidumpID + ".dmp");
- let extraFile = minidumpDir.clone();
- extraFile.append(minidumpID + ".extra");
+ let extraFile = minidumpDir.clone();
+ extraFile.append(minidumpID + ".extra");
- ok(pluginDumpFile.exists(), "Found minidump");
- ok(extraFile.exists(), "Found extra file");
+ ok(pluginDumpFile.exists(), "Found minidump");
+ ok(extraFile.exists(), "Found extra file");
- pluginDumpFile.remove(false);
- extraFile.remove(false);
- });
- };
+ pluginDumpFile.remove(false);
+ extraFile.remove(false);
+ crashDeferred.resolve();
+ });
+};
- Services.obs.addObserver(crashObserver, "plugin-crashed");
- // plugins.testmode will make BrowserPlugins:Test:ClearCrashData work.
- Services.prefs.setBoolPref("plugins.testmode", true);
- registerCleanupFunction(() => {
- Services.prefs.clearUserPref("plugins.testmode");
- Services.obs.removeObserver(crashObserver, "plugin-crashed");
- });
+Services.obs.addObserver(crashObserver, "plugin-crashed");
+// plugins.testmode will make BrowserPlugins:Test:ClearCrashData work.
+Services.prefs.setBoolPref("plugins.testmode", true);
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("plugins.testmode");
+ Services.obs.removeObserver(crashObserver, "plugin-crashed");
});
/**
* In this case, the chrome process hears about the crash first.
*/
add_task(async function testChromeHearsPluginCrashFirst() {
+ // Setup the crash observer promise
+ crashDeferred = PromiseUtils.defer();
+
// Open a remote window so that we can run this test even if e10s is not
// enabled by default.
let win = await BrowserTestUtils.openNewBrowserWindow({remote: true});
let browser = win.gBrowser.selectedBrowser;
browser.loadURI(CRASH_URL);
await BrowserTestUtils.browserLoaded(browser);
@@ -178,22 +185,26 @@ add_task(async function testChromeHearsP
cancelable: true,
});
plugin.dispatchEvent(event);
Assert.equal(statusDiv.getAttribute("status"), "please",
"Should have been showing crash report UI");
});
await BrowserTestUtils.closeWindow(win);
+ await crashDeferred.promise;
});
/**
* In this case, the content process hears about the crash first.
*/
add_task(async function testContentHearsCrashFirst() {
+ // Setup the crash observer promise
+ crashDeferred = PromiseUtils.defer();
+
// Open a remote window so that we can run this test even if e10s is not
// enabled by default.
let win = await BrowserTestUtils.openNewBrowserWindow({remote: true});
let browser = win.gBrowser.selectedBrowser;
browser.loadURI(CRASH_URL);
await BrowserTestUtils.browserLoaded(browser);
@@ -248,9 +259,10 @@ add_task(async function testContentHears
.getAnonymousElementByAttribute(plugin, "anonid",
"submitStatus");
Assert.equal(statusDiv.getAttribute("status"), "please",
"Should have been showing crash report UI");
});
await BrowserTestUtils.closeWindow(win);
+ await crashDeferred.promise;
});
--- a/browser/base/content/test/tabcrashed/browser_clearEmail.js
+++ b/browser/base/content/test/tabcrashed/browser_clearEmail.js
@@ -29,17 +29,19 @@ add_task(async function test_clear_email
let originalEmail = prefs.getCharPref("email");
// Pretend that we stored an email address from the previous
// crash
prefs.setCharPref("email", EMAIL);
prefs.setBoolPref("emailMe", true);
let tab = gBrowser.getTabForBrowser(browser);
- await BrowserTestUtils.crashBrowser(browser);
+ await BrowserTestUtils.crashBrowser(browser,
+ /* shouldShowTabCrashPage */ true,
+ /* shouldClearMinidumps */ false);
let doc = browser.contentDocument;
// Since about:tabcrashed will run in the parent process, we can safely
// manipulate its DOM nodes directly
let emailMe = doc.getElementById("emailMe");
emailMe.checked = false;
let crashReport = promiseCrashReport({
--- a/dom/ipc/tests/chrome.ini
+++ b/dom/ipc/tests/chrome.ini
@@ -1,8 +1,7 @@
[DEFAULT]
skip-if = os == 'android'
support-files =
process_error.xul
- process_error_contentscript.js
[test_process_error.xul]
skip-if = !crashreporter
--- a/dom/ipc/tests/process_error.xul
+++ b/dom/ipc/tests/process_error.xul
@@ -2,40 +2,36 @@
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
orient="vertical">
<browser id="thebrowser" type="content" remote="true" />
<script type="application/javascript"><![CDATA[
Components.utils.import("resource://gre/modules/Services.jsm");
+ Components.utils.import("resource://testing-common/BrowserTestUtils.jsm");
const ok = window.opener.wrappedJSObject.ok;
const is = window.opener.wrappedJSObject.is;
const done = window.opener.wrappedJSObject.done;
const SimpleTest = window.opener.wrappedJSObject.SimpleTest;
function crashObserver(subject, topic, data) {
is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
ok(subject instanceof Components.interfaces.nsIPropertyBag2,
'Subject implements nsIPropertyBag2.');
- var waitCrash = Promise.resolve();
var dumpID;
if ('nsICrashReporter' in Components.interfaces) {
dumpID = subject.getPropertyAsAString('dumpID');
ok(dumpID, "dumpID is present and not an empty string");
- waitCrash = Services.crashmanager.ensureCrashIsPresent(dumpID);
}
Services.obs.removeObserver(crashObserver, 'ipc:content-shutdown');
- waitCrash.then(done);
+ done();
}
Services.obs.addObserver(crashObserver, 'ipc:content-shutdown');
- document.getElementById('thebrowser')
- .QueryInterface(Components.interfaces.nsIFrameLoaderOwner)
- .frameLoader.messageManager
- .loadFrameScript('chrome://mochitests/content/chrome/dom/ipc/tests/process_error_contentscript.js', true);
+ BrowserTestUtils.crashBrowser(document.getElementById('thebrowser'));
]]></script>
</window>
deleted file mode 100644
--- a/dom/ipc/tests/process_error_contentscript.js
+++ /dev/null
@@ -1,7 +0,0 @@
-Components.utils.import("resource://gre/modules/ctypes.jsm");
-
-privateNoteIntentionalCrash();
-
-var zero = new ctypes.intptr_t(8);
-var badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
-var crash = badptr.contents;
--- a/dom/plugins/test/mochitest/hang_test.js
+++ b/dom/plugins/test/mochitest/hang_test.js
@@ -37,24 +37,20 @@ var testObserver = {
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");
let cpuUsage = parseFloat(extraData["PluginCpuUsage"]);
if (this.idleHang) {
ok(cpuUsage == 0, "plugin cpu usage is 0%");
@@ -98,12 +94,10 @@ function onPluginCrashed(aEvent) {
// allow either true or false here.
ok("submittedCrashReport" in aEvent, "submittedCrashReport is a property of event");
is(typeof aEvent.submittedCrashReport, "boolean", "submittedCrashReport is correct type");
var os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.removeObserver(testObserver, "plugin-crashed");
- Services.crashmanager.ensureCrashIsPresent(aEvent.pluginDumpID).then(() => {
- SimpleTest.finish();
- });
+ SimpleTest.finish();
}
--- a/dom/plugins/test/mochitest/test_busy_hang.xul
+++ b/dom/plugins/test/mochitest/test_busy_hang.xul
@@ -14,18 +14,16 @@
<script type="application/javascript">
setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
</script>
<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()">
<embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
</body>
<script class="testbody" type="application/javascript">
<![CDATA[
-Components.utils.import("resource://gre/modules/Services.jsm");
-
SimpleTest.waitForExplicitFinish();
SimpleTest.expectChildProcessCrash();
function runTests() {
// Default plugin hang timeout is too high for mochitests
var prefs = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
var timeoutPref = "dom.ipc.plugins.timeoutSecs";
--- a/dom/plugins/test/mochitest/test_crash_notify.xul
+++ b/dom/plugins/test/mochitest/test_crash_notify.xul
@@ -10,24 +10,26 @@
<script type="application/javascript">
setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
</script>
<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()">
<embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
</body>
<script class="testbody" type="application/javascript">
<![CDATA[
-Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/PromiseUtils.jsm");
SimpleTest.waitForExplicitFinish();
SimpleTest.expectChildProcessCrash();
var success = false;
var observerFired = false;
+var observerDeferred = PromiseUtils.defer();
+var eventListenerDeferred = PromiseUtils.defer();
var testObserver = {
observe: function(subject, topic, data) {
observerFired = true;
ok(true, "Observer fired");
is(topic, "plugin-crashed", "Checking correct topic");
is(data, null, "Checking null data");
ok((subject instanceof Components.interfaces.nsIPropertyBag2), "got Propbag");
@@ -41,16 +43,18 @@ var testObserver = {
let profD = directoryService.get("ProfD", Components.interfaces.nsIFile);
profD.append("minidumps");
let dumpFile = profD.clone();
dumpFile.append(id + ".dmp");
ok(dumpFile.exists(), "minidump exists");
let extraFile = profD.clone();
extraFile.append(id + ".extra");
ok(extraFile.exists(), "extra file exists");
+
+ observerDeferred.resolve();
},
QueryInterface: function(iid) {
if (iid.equals(Components.interfaces.nsIObserver) ||
iid.equals(Components.interfaces.nsISupportsWeakReference) ||
iid.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_NOINTERFACE;
@@ -79,30 +83,35 @@ function onPluginCrashed(aEvent) {
// allow either true or false here.
ok("submittedCrashReport" in aEvent, "submittedCrashReport is a property of event");
is(typeof aEvent.submittedCrashReport, "boolean", "submittedCrashReport is correct type");
var os = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
os.removeObserver(testObserver, "plugin-crashed");
- Services.crashmanager.ensureCrashIsPresent(aEvent.pluginDumpID).then(() => {
- SimpleTest.finish();
- });
+ eventListenerDeferred.resolve();
}
function runTests() {
var os = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
os.addObserver(testObserver, "plugin-crashed", true);
document.addEventListener("PluginCrashed", onPluginCrashed, false);
var pluginElement = document.getElementById("plugin1");
try {
pluginElement.crash();
} catch (e) {
}
+
+ Promise.all([
+ observerDeferred.promise,
+ eventListenerDeferred.promise
+ ]).then(() => {
+ SimpleTest.finish();
+ });
}
]]>
</script>
</window>
--- a/dom/plugins/test/mochitest/test_idle_hang.xul
+++ b/dom/plugins/test/mochitest/test_idle_hang.xul
@@ -14,18 +14,16 @@
<script type="application/javascript">
getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
</script>
<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()">
<embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
</body>
<script class="testbody" type="application/javascript">
<![CDATA[
-Components.utils.import("resource://gre/modules/Services.jsm");
-
SimpleTest.waitForExplicitFinish();
SimpleTest.expectChildProcessCrash();
function runTests() {
// Default plugin hang timeout is too high for mochitests
var prefs = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
var timeoutPref = "dom.ipc.plugins.timeoutSecs";
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -997,22 +997,25 @@ this.BrowserTestUtils = {
* Resolves with the data from the .extra file (the crash annotations).
*
* @param (Browser) browser
* A remote <xul:browser> element. Must not be null.
* @param (bool) shouldShowTabCrashPage
* True if it is expected that the tab crashed page will be shown
* for this browser. If so, the Promise will only resolve once the
* tab crash page has loaded.
+ * @param (bool) shouldClearMinidumps
+ * True if the minidumps left behind by the crash should be removed.
*
* @returns (Promise)
* @resolves An Object with key-value pairs representing the data from the
* crash report's extra file (if applicable).
*/
- async crashBrowser(browser, shouldShowTabCrashPage=true) {
+ async crashBrowser(browser, shouldShowTabCrashPage=true,
+ shouldClearMinidumps=true) {
let extra = {};
let KeyValueParser = {};
if (AppConstants.MOZ_CRASHREPORTER) {
Cu.import("resource://gre/modules/KeyValueParser.jsm", KeyValueParser);
}
if (!browser.isRemoteBrowser) {
throw new Error("<xul:browser> needs to be remote in order to crash");
@@ -1103,18 +1106,20 @@ this.BrowserTestUtils = {
dump(`\nNo .extra file for dumpID: ${dumpID}\n`);
if (AppConstants.MOZ_CRASHREPORTER) {
extra = KeyValueParser.parseKeyValuePairsFromFile(extrafile);
} else {
dump('\nCrashReporter not enabled - will not return any extra data\n');
}
}
- removeFile(minidumpDirectory, dumpID + '.dmp');
- removeFile(minidumpDirectory, dumpID + '.extra');
+ if (shouldClearMinidumps) {
+ removeFile(minidumpDirectory, dumpID + '.dmp');
+ removeFile(minidumpDirectory, dumpID + '.extra');
+ }
});
}
removalPromise.then(() => {
Services.obs.removeObserver(observer, 'ipc:content-shutdown');
dump("\nCrash cleaned up\n");
// There might be other ipc:content-shutdown handlers that need to
// run before we want to continue, so we'll resolve on the next tick
--- a/testing/mochitest/tests/SimpleTest/TestRunner.js
+++ b/testing/mochitest/tests/SimpleTest/TestRunner.js
@@ -633,18 +633,23 @@ TestRunner.testFinished = function(tests
}
TestRunner.updateUI([{ result: false }]);
}
});
TestRunner._makeIframe(interstitialURL, 0);
}
SpecialPowers.executeAfterFlushingMessageQueue(function() {
- cleanUpCrashDumpFiles();
- SpecialPowers.flushPermissions(function () { SpecialPowers.flushPrefEnv(runNextTest); });
+ SpecialPowers.waitForCrashes(TestRunner._expectingProcessCrash)
+ .then(() => {
+ cleanUpCrashDumpFiles();
+ SpecialPowers.flushPermissions(function () {
+ SpecialPowers.flushPrefEnv(runNextTest);
+ });
+ });
});
};
TestRunner.testUnloaded = function() {
// If we're in a debug build, check assertion counts. This code is
// similar to the code in Tester_nextTest in browser-test.js used
// for browser-chrome mochitests.
if (SpecialPowers.isDebugBuild) {
--- a/testing/specialpowers/content/SpecialPowersObserver.jsm
+++ b/testing/specialpowers/content/SpecialPowersObserver.jsm
@@ -65,16 +65,17 @@ SpecialPowersObserver.prototype.observe
break;
}
};
SpecialPowersObserver.prototype._loadFrameScript = function() {
if (!this._isFrameScriptLoaded) {
// Register for any messages our API needs us to handle
this._messageManager.addMessageListener("SPPrefService", this);
+ this._messageManager.addMessageListener("SPProcessCrashManagerWait", this);
this._messageManager.addMessageListener("SPProcessCrashService", this);
this._messageManager.addMessageListener("SPPingService", this);
this._messageManager.addMessageListener("SpecialPowers.Quit", this);
this._messageManager.addMessageListener("SpecialPowers.Focus", this);
this._messageManager.addMessageListener("SpecialPowers.CreateFiles", this);
this._messageManager.addMessageListener("SpecialPowers.RemoveFiles", this);
this._messageManager.addMessageListener("SPPermissionManager", this);
this._messageManager.addMessageListener("SPObserverService", this);
@@ -135,16 +136,17 @@ SpecialPowersObserver.prototype.uninit =
obs.removeObserver(this, "http-on-modify-request");
this._registerObservers._topics.forEach((element) => {
obs.removeObserver(this._registerObservers, element);
});
this._removeProcessCrashObservers();
if (this._isFrameScriptLoaded) {
this._messageManager.removeMessageListener("SPPrefService", this);
+ this._messageManager.removeMessageListener("SPProcessCrashManagerWait", this);
this._messageManager.removeMessageListener("SPProcessCrashService", this);
this._messageManager.removeMessageListener("SPPingService", this);
this._messageManager.removeMessageListener("SpecialPowers.Quit", this);
this._messageManager.removeMessageListener("SpecialPowers.Focus", this);
this._messageManager.removeMessageListener("SpecialPowers.CreateFiles", this);
this._messageManager.removeMessageListener("SpecialPowers.RemoveFiles", this);
this._messageManager.removeMessageListener("SPPermissionManager", this);
this._messageManager.removeMessageListener("SPObserverService", this);
--- a/testing/specialpowers/content/SpecialPowersObserverAPI.js
+++ b/testing/specialpowers/content/SpecialPowersObserverAPI.js
@@ -112,16 +112,20 @@ SpecialPowersObserverAPI.prototype = {
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
+ if (!aSubject.hasKey("abnormal")) {
+ return; // This is a normal shutdown, ignore it
+ }
+
addDumpIDToMessage("dumpID");
}
this._sendAsyncMessage("SPProcessCrashService", message);
break;
}
},
_getCrashDumpDir() {
@@ -388,16 +392,27 @@ SpecialPowersObserverAPI.prototype = {
case "delete-pending-crash-dump-files":
return this._deletePendingCrashDumpFiles();
default:
throw new SpecialPowersError("Invalid operation for SPProcessCrashService");
}
return undefined; // See comment at the beginning of this function.
}
+ case "SPProcessCrashManagerWait": {
+ let promises = aMessage.json.crashIds.map((crashId) => {
+ return Services.crashmanager.ensureCrashIsPresent(crashId);
+ });
+
+ Promise.all(promises).then(() => {
+ this._sendReply(aMessage, "SPProcessCrashManagerWait", {});
+ });
+ return undefined; // See comment at the beginning of this function.
+ }
+
case "SPPermissionManager": {
let msg = aMessage.json;
let principal = msg.principal;
switch (msg.op) {
case "add":
Services.perms.addFromPrincipal(principal, msg.type, msg.permission, msg.expireType, msg.expireTime);
break;
--- a/testing/specialpowers/content/specialpowers.js
+++ b/testing/specialpowers/content/specialpowers.js
@@ -44,16 +44,17 @@ function SpecialPowers(window) {
"SPRequestResetCoverageCounters"];
this.SP_ASYNC_MESSAGES = ["SpecialPowers.Focus",
"SpecialPowers.Quit",
"SpecialPowers.CreateFiles",
"SpecialPowers.RemoveFiles",
"SPPingService",
"SPLoadExtension",
+ "SPProcessCrashManagerWait",
"SPStartupExtension",
"SPUnloadExtension",
"SPExtensionMessage"];
addMessageListener("SPPingService", this._messageListener);
addMessageListener("SpecialPowers.FilesCreated", this._messageListener);
addMessageListener("SpecialPowers.FilesError", this._messageListener);
let self = this;
Services.obs.addObserver(function onInnerWindowDestroyed(subject, topic, data) {
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -665,16 +665,41 @@ SpecialPowersAPI.prototype = {
getDOMWindowUtils(aWindow) {
if (aWindow == this.window.get() && this.DOMWindowUtils != null)
return this.DOMWindowUtils;
return bindDOMWindowUtils(aWindow);
},
+ waitForCrashes(aExpectingProcessCrash) {
+ return new Promise((resolve, reject) => {
+ if (!aExpectingProcessCrash) {
+ resolve();
+ }
+
+ var crashIds = this._encounteredCrashDumpFiles.filter((filename) => {
+ return ((filename.length === 40) && filename.endsWith(".dmp"));
+ }).map((id) => {
+ return id.slice(0, -4); // Strip the .dmp extension to get the ID
+ });
+
+ let self = this;
+ function messageListener(msg) {
+ self._removeMessageListener("SPProcessCrashManagerWait", messageListener);
+ resolve();
+ }
+
+ this._addMessageListener("SPProcessCrashManagerWait", messageListener);
+ this._sendAsyncMessage("SPProcessCrashManagerWait", {
+ crashIds
+ });
+ });
+ },
+
removeExpectedCrashDumpFiles(aExpectingProcessCrash) {
var success = true;
if (aExpectingProcessCrash) {
var message = {
op: "delete-crash-dump-files",
filenames: this._encounteredCrashDumpFiles
};
if (!this._sendSyncMessage("SPProcessCrashService", message)[0]) {