"use strict";
const { TelemetryTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/TelemetryTestUtils.sys.mjs"
);
/* eslint-disable mozilla/no-redeclare-with-import-autofix */
const { ContentTaskUtils } = ChromeUtils.importESModule(
"resource://testing-common/ContentTaskUtils.sys.mjs"
);
/**
* Returns a Promise that resolves once a crash report has
* been submitted. This function will also test the crash
* reports extra data to see if it matches expectedExtra.
*
* @param expectedExtra (object)
* An Object whose key-value pairs will be compared
* against the key-value pairs in the extra data of the
* crash report. A test failure will occur if there is
* a mismatch.
*
* If the value of the key-value pair is "null", this will
* be interpreted as "this key should not be included in the
* extra data", and will cause a test failure if it is detected
* in the crash report.
*
* Note that this will ignore any keys that are not included
* in expectedExtra. It's possible that the crash report
* will contain other extra information that is not
* compared against.
* @returns Promise
*/
function promiseCrashReport(expectedExtra = {}) {
return (async
function () {
info(
"Starting wait on crash-report-status");
let [subject] = await TestUtils.topicObserved(
"crash-report-status",
(unused, data) => {
return data ==
"success";
}
);
info(
"Topic observed!");
if (!(subject
instanceof Ci.nsIPropertyBag2)) {
throw new Error(
"Subject was not a Ci.nsIPropertyBag2");
}
let remoteID = getPropertyBagValue(subject,
"serverCrashID");
if (!remoteID) {
throw new Error(
"Report should have a server ID");
}
let file = Cc[
"@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(Services.crashmanager._submittedDumpsDir);
file.append(remoteID +
".txt");
if (!file.exists()) {
throw new Error(
"Report should have been received by the server");
}
file.remove(
false);
let extra = getPropertyBagValue(subject,
"extra");
if (!(extra
instanceof Ci.nsIPropertyBag2)) {
throw new Error(
"extra was not a Ci.nsIPropertyBag2");
}
info(
"Iterating crash report extra keys");
for (let { name: key } of extra.enumerator) {
let value = extra.getPropertyAsAString(key);
if (key in expectedExtra) {
if (expectedExtra[key] ==
null) {
ok(
false, `Got unexpected key ${key} with value ${value}`);
}
else {
is(
value,
expectedExtra[key],
`Crash report had the right extra value
for ${key}`
);
}
}
}
})();
}
function promiseCrashReportFail() {
return (async
function () {
info(
"Starting wait on crash-report-status");
await TestUtils.topicObserved(
"crash-report-status", (unused, data) => {
return data ==
"failed";
});
info(
"Topic observed!");
})();
}
/**
* For an nsIPropertyBag, returns the value for a given
* key.
*
* @param bag
* The nsIPropertyBag to retrieve the value from
* @param key
* The key that we want to get the value for from the
* bag
* @returns The value corresponding to the key from the bag,
* or null if the value could not be retrieved (for
* example, if no value is set at that key).
*/
function getPropertyBagValue(bag, key) {
try {
let val = bag.getProperty(key);
return val;
}
catch (e) {
if (e.result != Cr.NS_ERROR_FAILURE) {
throw e;
}
}
return null;
}
/**
* Sets up the browser to send crash reports to the local crash report
* testing server.
*/
async
function setupLocalCrashReportServer() {
const SERVER_URL =
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
"http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
// The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables crash
// reports. This test needs them enabled. The test also needs a mock
// report server, and fortunately one is already set up by toolkit/
// crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL,
// which CrashSubmit.sys.mjs uses as a server override.
let noReport = Services.env.get(
"MOZ_CRASHREPORTER_NO_REPORT");
let serverUrl = Services.env.get(
"MOZ_CRASHREPORTER_URL");
Services.env.set(
"MOZ_CRASHREPORTER_NO_REPORT",
"");
Services.env.set(
"MOZ_CRASHREPORTER_URL", SERVER_URL);
registerCleanupFunction(
function () {
Services.env.set(
"MOZ_CRASHREPORTER_NO_REPORT", noReport);
Services.env.set(
"MOZ_CRASHREPORTER_URL", serverUrl);
});
}
/**
* Monkey patches TabCrashHandler.getDumpID to return null in order to test
* about:tabcrashed when a dump is not available.
*/
function prepareNoDump() {
let originalGetDumpID = TabCrashHandler.getDumpID;
TabCrashHandler.getDumpID =
function () {
return null;
};
registerCleanupFunction(() => {
TabCrashHandler.getDumpID = originalGetDumpID;
});
}
const kBuildidMatchEnv =
"MOZ_BUILDID_MATCH_DONTSEND";
function setBuildidMatchDontSendEnv() {
info(
"Setting " + kBuildidMatchEnv +
"=1");
Services.env.set(kBuildidMatchEnv,
"1");
}
function unsetBuildidMatchDontSendEnv() {
info(
"Unsetting " + kBuildidMatchEnv);
Services.env.set(kBuildidMatchEnv,
"0");
}
const kBuildidMismatchEnv =
"MOZ_FORCE_BUILDID_MISMATCH";
function setBuildidMismatchEnv() {
info(
"Setting " + kBuildidMismatchEnv +
"=1");
Services.env.set(kBuildidMismatchEnv,
"1");
}
function unsetBuildidMismatchEnv() {
info(
"Unsetting " + kBuildidMismatchEnv);
Services.env.set(kBuildidMismatchEnv,
"0");
}
function getEventPromise(eventName, eventKind) {
return new Promise(
function (resolve) {
info(
"Installing event listener (" + eventKind +
")");
window.addEventListener(
eventName,
() => {
ok(
true,
"Received " + eventName +
" (" + eventKind +
") event");
info(
"Call resolve() for " + eventKind +
" event");
resolve();
},
{ once:
true }
);
info(
"Installed event listener (" + eventKind +
")");
});
}
async
function openNewTab(forceCrash) {
const PAGE =
"data:text/html,A%20regular,%20everyday,%20normal%20page.";
let options = {
gBrowser,
PAGE,
waitForLoad:
false,
waitForStateStop:
false,
forceNewProcess:
true,
};
let tab = await BrowserTestUtils.openNewForegroundTab(options);
if (forceCrash ===
true) {
let browser = tab.linkedBrowser;
await BrowserTestUtils.crashFrame(
browser,
/* shouldShowTabCrashPage */ false,
/* shouldClearMinidumps */ true,
/* BrowsingContext */ null
);
}
return tab;
}
async
function closeTab(tab) {
await TestUtils.waitForTick();
BrowserTestUtils.removeTab(tab);
}
const kInterval = 100;
/* ms */
const kRetries = 5;
/**
* This function waits until utility scalars are reported into the
* scalar snapshot.
*/
async
function waitForProcessScalars(name) {
await ContentTaskUtils.waitForCondition(
() => {
const scalars = TelemetryTestUtils.getProcessScalars(
"parent");
return Object.keys(scalars).includes(name);
},
`Waiting
for ${name} scalars to have been set`,
kInterval,
kRetries
);
}
async
function getTelemetry(name) {
try {
await waitForProcessScalars(name);
const scalars = TelemetryTestUtils.getProcessScalars(
"parent");
return scalars[name];
}
catch (ex) {
const msg = `Waiting
for ${name} scalars to have been set`;
if (ex.indexOf(msg) === 0) {
return undefined;
}
throw ex;
}
}
async
function getFalsePositiveTelemetry() {
return await getTelemetry(
"dom.contentprocess.buildID_mismatch_false_positive"
);
}
async
function getTrueMismatchTelemetry() {
return await getTelemetry(
"dom.contentprocess.buildID_mismatch");
}
// The logic bound to dom.ipc.processPrelaunch.enabled will react to value
// changes: https://searchfox.org/mozilla-central/rev/ecd91b104714a8b2584a4c03175be50ccb3a7c67/dom/ipc/PreallocatedProcessManager.cpp#171-195
// So we force flip to ensure we have no dangling process.
async
function forceCleanProcesses() {
const origPrefValue = SpecialPowers.getBoolPref(
"dom.ipc.processPrelaunch.enabled"
);
await SpecialPowers.setBoolPref(
"dom.ipc.processPrelaunch.enabled",
!origPrefValue
);
await SpecialPowers.setBoolPref(
"dom.ipc.processPrelaunch.enabled",
origPrefValue
);
const currPrefValue = SpecialPowers.getBoolPref(
"dom.ipc.processPrelaunch.enabled"
);
Assert.strictEqual(
currPrefValue,
origPrefValue,
"processPrelaunch properly re-enabled"
);
}