summaryrefslogtreecommitdiffstats
path: root/ipc/glue/test/browser/head.js
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/glue/test/browser/head.js')
-rw-r--r--ipc/glue/test/browser/head.js424
1 files changed, 424 insertions, 0 deletions
diff --git a/ipc/glue/test/browser/head.js b/ipc/glue/test/browser/head.js
new file mode 100644
index 0000000000..14c0859eb3
--- /dev/null
+++ b/ipc/glue/test/browser/head.js
@@ -0,0 +1,424 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const utilityProcessTest = () => {
+ return Cc["@mozilla.org/utility-process-test;1"].createInstance(
+ Ci.nsIUtilityProcessTest
+ );
+};
+
+const kGenericUtilitySandbox = 0;
+const kGenericUtilityActor = "unknown";
+
+// Start a generic utility process with the given array of utility actor names
+// registered.
+async function startUtilityProcess(actors = []) {
+ info("Start a UtilityProcess");
+ return utilityProcessTest().startProcess(actors);
+}
+
+// Returns an array of process infos for utility processes of the given type
+// or all utility processes if actor is not defined.
+async function getUtilityProcesses(actor = undefined) {
+ let procInfos = (await ChromeUtils.requestProcInfo()).children.filter(p => {
+ return (
+ p.type === "utility" &&
+ (actor == undefined ||
+ p.utilityActors.find(a => a.actorName.startsWith(actor)))
+ );
+ });
+
+ info(`Utility process infos = ${JSON.stringify(procInfos)}`);
+ return procInfos;
+}
+
+async function getUtilityPid(actor) {
+ let process = await getUtilityProcesses(actor);
+ is(process.length, 1, `exactly one ${actor} process exists`);
+ return process[0].pid;
+}
+
+async function checkUtilityExists(actor) {
+ info(`Looking for a running ${actor} utility process`);
+ const utilityPid = await getUtilityPid(actor);
+ ok(utilityPid > 0, `Found ${actor} utility process ${utilityPid}`);
+ return utilityPid;
+}
+
+// "Cleanly stop" a utility process. This will never leave a crash dump file.
+// preferKill will "kill" the process (e.g. SIGABRT) instead of using the
+// UtilityProcessManager.
+// To "crash" -- i.e. shutdown and generate a crash dump -- use
+// crashSomeUtility().
+async function cleanUtilityProcessShutdown(actor, preferKill = false) {
+ info(`${preferKill ? "Kill" : "Clean shutdown"} Utility Process ${actor}`);
+
+ const utilityPid = await getUtilityPid(actor);
+ ok(utilityPid !== undefined, `Must have PID for ${actor} utility process`);
+
+ const utilityProcessGone = TestUtils.topicObserved(
+ "ipc:utility-shutdown",
+ (subject, data) => parseInt(data, 10) === utilityPid
+ );
+
+ if (preferKill) {
+ SimpleTest.expectChildProcessCrash();
+ info(`Kill Utility Process ${utilityPid}`);
+ const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService(
+ Ci.nsIProcessToolsService
+ );
+ ProcessTools.kill(utilityPid);
+ } else {
+ info(`Stopping Utility Process ${utilityPid}`);
+ await utilityProcessTest().stopProcess(actor);
+ }
+
+ let [subject, data] = await utilityProcessGone;
+ ok(
+ subject instanceof Ci.nsIPropertyBag2,
+ "Subject needs to be a nsIPropertyBag2 to clean up properly"
+ );
+ is(
+ parseInt(data, 10),
+ utilityPid,
+ `Should match the crashed PID ${utilityPid} with ${data}`
+ );
+
+ // Make sure the process is dead, otherwise there is a risk of race for
+ // writing leak logs
+ utilityProcessTest().noteIntentionalCrash(utilityPid);
+
+ ok(!subject.hasKey("dumpID"), "There should be no dumpID");
+}
+
+async function killUtilityProcesses() {
+ let utilityProcesses = await getUtilityProcesses();
+ for (const utilityProcess of utilityProcesses) {
+ for (const actor of utilityProcess.utilityActors) {
+ info(`Stopping ${actor.actorName} utility process`);
+ await cleanUtilityProcessShutdown(actor.actorName, /* preferKill */ true);
+ }
+ }
+}
+
+function audioTestData() {
+ return [
+ {
+ src: "small-shot.ogg",
+ expectations: {
+ Android: {
+ process: "Utility Generic",
+ decoder: "vorbis audio decoder",
+ },
+ Linux: {
+ process: "Utility Generic",
+ decoder: "vorbis audio decoder",
+ },
+ WINNT: {
+ process: "Utility Generic",
+ decoder: "vorbis audio decoder",
+ },
+ Darwin: {
+ process: "Utility Generic",
+ decoder: "vorbis audio decoder",
+ },
+ },
+ },
+ {
+ src: "small-shot.mp3",
+ expectations: {
+ Android: { process: "Utility Generic", decoder: "ffvpx audio decoder" },
+ Linux: {
+ process: "Utility Generic",
+ decoder: "ffvpx audio decoder",
+ },
+ WINNT: {
+ process: SpecialPowers.getBoolPref("media.ffvpx.mp3.enabled")
+ ? "Utility Generic"
+ : "Utility WMF",
+ decoder: SpecialPowers.getBoolPref("media.ffvpx.mp3.enabled")
+ ? "ffvpx audio decoder"
+ : "wmf audio decoder",
+ },
+ Darwin: {
+ process: SpecialPowers.getBoolPref("media.ffvpx.mp3.enabled")
+ ? "Utility Generic"
+ : "Utility AppleMedia",
+ decoder: SpecialPowers.getBoolPref("media.ffvpx.mp3.enabled")
+ ? "ffvpx audio decoder"
+ : "apple coremedia decoder",
+ },
+ },
+ },
+ {
+ src: "small-shot.m4a",
+ expectations: {
+ // Add Android after Bug 1771196
+ Linux: {
+ process: "Utility Generic",
+ decoder: "ffmpeg audio decoder",
+ },
+ WINNT: {
+ process: "Utility WMF",
+ decoder: "wmf audio decoder",
+ },
+ Darwin: {
+ process: "Utility AppleMedia",
+ decoder: "apple coremedia decoder",
+ },
+ },
+ },
+ {
+ src: "small-shot.flac",
+ expectations: {
+ Android: { process: "Utility Generic", decoder: "ffvpx audio decoder" },
+ Linux: {
+ process: "Utility Generic",
+ decoder: "ffvpx audio decoder",
+ },
+ WINNT: {
+ process: "Utility Generic",
+ decoder: "ffvpx audio decoder",
+ },
+ Darwin: {
+ process: "Utility Generic",
+ decoder: "ffvpx audio decoder",
+ },
+ },
+ },
+ ];
+}
+
+async function addMediaTab(src) {
+ const tab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
+ forceNewProcess: true,
+ });
+ const browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+ await SpecialPowers.spawn(browser, [src], createAudioElement);
+ return tab;
+}
+
+async function play(
+ tab,
+ expectUtility,
+ expectDecoder,
+ expectContent = false,
+ expectJava = false
+) {
+ let browser = tab.linkedBrowser;
+ return SpecialPowers.spawn(
+ browser,
+ [expectUtility, expectDecoder, expectContent, expectJava],
+ checkAudioDecoder
+ );
+}
+
+async function stop(tab) {
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [], async function () {
+ let audio = content.document.querySelector("audio");
+ audio.pause();
+ });
+}
+
+async function createAudioElement(src) {
+ const doc = typeof content !== "undefined" ? content.document : document;
+ const ROOT = "https://example.com/browser/ipc/glue/test/browser";
+ let audio = doc.createElement("audio");
+ audio.setAttribute("controls", "true");
+ audio.setAttribute("loop", true);
+ audio.src = `${ROOT}/${src}`;
+ doc.body.appendChild(audio);
+}
+
+async function checkAudioDecoder(
+ expectedProcess,
+ expectedDecoder,
+ expectContent = false,
+ expectJava = false
+) {
+ const doc = typeof content !== "undefined" ? content.document : document;
+ let audio = doc.querySelector("audio");
+ const checkPromise = new Promise((resolve, reject) => {
+ const timeUpdateHandler = async ev => {
+ const debugInfo = await SpecialPowers.wrap(audio).mozRequestDebugInfo();
+ const audioDecoderName = debugInfo.decoder.reader.audioDecoderName;
+
+ const isExpectedDecoder =
+ audioDecoderName.indexOf(`${expectedDecoder}`) == 0;
+ ok(
+ isExpectedDecoder,
+ `playback ${audio.src} was from decoder '${audioDecoderName}', expected '${expectedDecoder}'`
+ );
+
+ const isExpectedProcess =
+ audioDecoderName.indexOf(`(${expectedProcess} remote)`) > 0;
+ const isJavaRemote = audioDecoderName.indexOf("(remote)") > 0;
+ const isOk =
+ (isExpectedProcess && !isJavaRemote && !expectContent && !expectJava) || // Running in Utility/RDD
+ (expectJava && !isExpectedProcess && isJavaRemote) || // Running in Java remote
+ (expectContent && !isExpectedProcess && !isJavaRemote); // Running in Content
+
+ ok(
+ isOk,
+ `playback ${audio.src} was from process '${audioDecoderName}', expected '${expectedProcess}'`
+ );
+
+ if (isOk) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ const startPlaybackHandler = async ev => {
+ ok(
+ await audio.play().then(
+ _ => true,
+ _ => false
+ ),
+ "audio started playing"
+ );
+
+ audio.addEventListener("timeupdate", timeUpdateHandler, { once: true });
+ };
+
+ audio.addEventListener("canplaythrough", startPlaybackHandler, {
+ once: true,
+ });
+ });
+
+ // We need to make sure the decoder is ready before play()ing otherwise we
+ // could get into bad situations
+ audio.load();
+ return checkPromise;
+}
+
+async function runMochitestUtilityAudio(
+ src,
+ {
+ expectUtility,
+ expectDecoder,
+ expectContent = false,
+ expectJava = false,
+ } = {}
+) {
+ info(`Add media: ${src}`);
+ await createAudioElement(src);
+ let audio = document.querySelector("audio");
+ ok(audio, "Found an audio element created");
+
+ info(`Play media: ${src}`);
+ await checkAudioDecoder(
+ expectUtility,
+ expectDecoder,
+ expectContent,
+ expectJava
+ );
+
+ info(`Pause media: ${src}`);
+ await audio.pause();
+
+ info(`Remove media: ${src}`);
+ document.body.removeChild(audio);
+}
+
+async function crashSomeUtility(utilityPid, actorsCheck) {
+ SimpleTest.expectChildProcessCrash();
+
+ const crashMan = Services.crashmanager;
+ const utilityProcessGone = TestUtils.topicObserved(
+ "ipc:utility-shutdown",
+ (subject, data) => {
+ info(`ipc:utility-shutdown: data=${data} subject=${subject}`);
+ return parseInt(data, 10) === utilityPid;
+ }
+ );
+
+ info("prune any previous crashes");
+ const future = new Date(Date.now() + 1000 * 60 * 60 * 24);
+ await crashMan.pruneOldCrashes(future);
+
+ info("crash Utility Process");
+ const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService(
+ Ci.nsIProcessToolsService
+ );
+
+ info(`Crash Utility Process ${utilityPid}`);
+ ProcessTools.crash(utilityPid);
+
+ info(`Waiting for utility process ${utilityPid} to go away.`);
+ let [subject, data] = await utilityProcessGone;
+ ok(
+ parseInt(data, 10) === utilityPid,
+ `Should match the crashed PID ${utilityPid} with ${data}`
+ );
+ ok(
+ subject instanceof Ci.nsIPropertyBag2,
+ "Subject needs to be a nsIPropertyBag2 to clean up properly"
+ );
+
+ // Make sure the process is dead, otherwise there is a risk of race for
+ // writing leak logs
+ utilityProcessTest().noteIntentionalCrash(utilityPid);
+
+ const dumpID = subject.getPropertyAsAString("dumpID");
+ ok(dumpID, "There should be a dumpID");
+
+ await crashMan.ensureCrashIsPresent(dumpID);
+ await crashMan.getCrashes().then(crashes => {
+ is(crashes.length, 1, "There should be only one record");
+ const crash = crashes[0];
+ ok(
+ crash.isOfType(
+ crashMan.processTypes[Ci.nsIXULRuntime.PROCESS_TYPE_UTILITY],
+ crashMan.CRASH_TYPE_CRASH
+ ),
+ "Record should be a utility process crash"
+ );
+ ok(crash.id === dumpID, "Record should have an ID");
+ ok(
+ actorsCheck(crash.metadata.UtilityActorsName),
+ `Record should have the correct actors name for: ${crash.metadata.UtilityActorsName}`
+ );
+ });
+
+ let minidumpDirectory = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ minidumpDirectory.append("minidumps");
+
+ let dumpfile = minidumpDirectory.clone();
+ dumpfile.append(dumpID + ".dmp");
+ if (dumpfile.exists()) {
+ info(`Removal of ${dumpfile.path}`);
+ dumpfile.remove(false);
+ }
+
+ let extrafile = minidumpDirectory.clone();
+ extrafile.append(dumpID + ".extra");
+ info(`Removal of ${extrafile.path}`);
+ if (extrafile.exists()) {
+ extrafile.remove(false);
+ }
+}
+
+// Crash a utility process and generate a crash dump. To close a utility
+// process (forcefully or not) without a generating a crash, use
+// cleanUtilityProcessShutdown.
+async function crashSomeUtilityActor(
+ actor,
+ actorsCheck = () => {
+ return true;
+ }
+) {
+ // Get PID for utility type
+ const procInfos = await getUtilityProcesses(actor);
+ ok(
+ procInfos.length == 1,
+ `exactly one ${actor} utility process should be found`
+ );
+ const utilityPid = procInfos[0].pid;
+ return crashSomeUtility(utilityPid, actorsCheck);
+}