Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
"use strict";
const { AddonTestUtils } = ChromeUtils.importESModule(
);
const { ExtensionProcessCrashObserver, Management } =
ChromeUtils.importESModule("resource://gre/modules/Extension.sys.mjs");
AddonTestUtils.initMochitest(this);
add_task(async function test_ExtensionProcessCrashObserver() {
await SpecialPowers.pushPrefEnv({
// This test triggers a crash and so it will be restarting all builtin
// extensions persistent background pages as a side effect (and that would
// be make this test to hit failures due to the builtin background pages
// still in the process of being restarted being detected as shutdown leaks).
set: [["extensions.background.disableRestartPersistentAfterCrash", true]],
});
let mv2Extension = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
manifest: {
manifest_version: 2,
},
background() {
browser.test.sendMessage("background_running");
},
});
await mv2Extension.startup();
await mv2Extension.awaitMessage("background_running");
let {
currentProcessChildID,
lastCrashedProcessChildID,
processSpawningDisabled,
lastCrashTimestamps,
} = ExtensionProcessCrashObserver;
Assert.notEqual(
currentProcessChildID,
undefined,
"Expect ExtensionProcessCrashObserver.currentProcessChildID to be set"
);
Assert.equal(
ChromeUtils.getAllDOMProcesses().find(
pp => pp.childID == currentProcessChildID
)?.remoteType,
"extension",
"Expect a child process with remoteType extension to be found for the process childID set"
);
Assert.notEqual(
lastCrashedProcessChildID,
currentProcessChildID,
"Expect lastCrashedProcessChildID to not be set to the same value that currentProcessChildID is set"
);
Assert.equal(
processSpawningDisabled,
false,
"Expect process spawning to be enabled"
);
Assert.deepEqual(lastCrashTimestamps, [], "Expect no crash timestamps");
let mv3Extension = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
manifest: {
manifest_version: 3,
},
background() {
browser.test.sendMessage("background_running");
},
});
const waitForExtensionBrowserInserted = () =>
new Promise(resolve => {
const listener = (_eventName, browser) => {
if (!browser.getAttribute("webextension-view-type") === "background") {
return;
}
Management.off("extension-browser-inserted", listener);
resolve(browser);
};
Management.on("extension-browser-inserted", listener);
});
const waitForExtensionProcessCrashNotified = () =>
new Promise(resolve => {
Management.once("extension-process-crash", (_evt, data) => resolve(data));
});
const promiseBackgroundBrowser = waitForExtensionBrowserInserted();
const promiseExtensionProcessCrashNotified =
waitForExtensionProcessCrashNotified();
await mv3Extension.startup();
await mv3Extension.awaitMessage("background_running");
const bgPageBrowser = await promiseBackgroundBrowser;
Assert.ok(
Glean.extensions.processEvent.created_fg.testGetValue() > 0,
"Expect glean processEvent.created_fg to be set."
);
Assert.equal(
undefined,
Glean.extensions.processEvent.created_bg.testGetValue(),
"Creating in the background is not expected on desktop."
);
info("Force extension process crash");
// Clear any existing telemetry data, so that we can be sure we can
// assert the glean process_event metric labels values to be strictly
// equal to 1 after the extension process crashed.
Services.fog.testResetFOG();
// NOTE: shouldShowTabCrashPage option needs to be set to false
// to make sure crashFrame method resolves without waiting for a
// tab crash page (which is not going to be shown for a background
// page browser element).
await BrowserTestUtils.crashFrame(
bgPageBrowser,
/* shouldShowTabCrashPage */ false
);
info("Verify ExtensionProcessCrashObserver after extension process crash");
Assert.equal(
ExtensionProcessCrashObserver.lastCrashedProcessChildID,
currentProcessChildID,
"Expect ExtensionProcessCrashObserver.lastCrashedProcessChildID to be set to the expected childID"
);
Assert.equal(
ExtensionProcessCrashObserver.processSpawningDisabled,
false,
"Expect process spawning to still be enabled"
);
Assert.equal(
ExtensionProcessCrashObserver.lastCrashTimestamps.length,
1,
"Expect a crash timestamp"
);
info("Expect the same childID to have been notified as a Management event");
Assert.deepEqual(
await promiseExtensionProcessCrashNotified,
{
childID: currentProcessChildID,
processSpawningDisabled: false,
// This boolean flag is expected to be always true on Desktop builds.
appInForeground: true,
},
"Got the expected childID notified as part of the extension-process-crash Management event"
);
Assert.ok(
Glean.extensions.processEvent.crashed_fg.testGetValue() > 0,
"Expect glean processEvent.crashed_fg to be set"
);
Assert.equal(
undefined,
Glean.extensions.processEvent.crashed_bg.testGetValue(),
"Crashing in the background is not expected on desktop."
);
info("Wait for mv3 extension shutdown");
await mv3Extension.unload();
info("Wait for mv2 extension shutdown");
await mv2Extension.unload();
// Reset this array to prevent TV failures.
ExtensionProcessCrashObserver.lastCrashTimestamps = [];
await SpecialPowers.popPrefEnv();
});