summaryrefslogtreecommitdiffstats
path: root/ipc/glue/test/browser
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/glue/test/browser')
-rw-r--r--ipc/glue/test/browser/browser.ini59
-rw-r--r--ipc/glue/test/browser/browser_audio_shutdown.ini6
-rw-r--r--ipc/glue/test/browser/browser_audio_telemetry_content.js32
-rw-r--r--ipc/glue/test/browser/browser_audio_telemetry_rdd.js29
-rw-r--r--ipc/glue/test/browser/browser_audio_telemetry_utility.js27
-rw-r--r--ipc/glue/test/browser/browser_child_hang.ini7
-rw-r--r--ipc/glue/test/browser/browser_child_hang.js37
-rw-r--r--ipc/glue/test/browser/browser_utility_audioDecodeCrash.js95
-rw-r--r--ipc/glue/test/browser/browser_utility_audio_shutdown.js76
-rw-r--r--ipc/glue/test/browser/browser_utility_crashReporter.js34
-rw-r--r--ipc/glue/test/browser/browser_utility_geolocation_crashed.js70
-rw-r--r--ipc/glue/test/browser/browser_utility_hard_kill.js13
-rw-r--r--ipc/glue/test/browser/browser_utility_hard_kill_delayed.js57
-rw-r--r--ipc/glue/test/browser/browser_utility_memoryReport.js76
-rw-r--r--ipc/glue/test/browser/browser_utility_multipleAudio.js73
-rw-r--r--ipc/glue/test/browser/browser_utility_profiler.js76
-rw-r--r--ipc/glue/test/browser/browser_utility_start_clean_shutdown.js9
-rw-r--r--ipc/glue/test/browser/head-telemetry.js205
-rw-r--r--ipc/glue/test/browser/head.js424
-rw-r--r--ipc/glue/test/browser/mochitest_audio_off.ini12
-rw-r--r--ipc/glue/test/browser/mochitest_audio_on.ini12
-rw-r--r--ipc/glue/test/browser/moz.build12
-rw-r--r--ipc/glue/test/browser/test_utility_audio_off.html44
-rw-r--r--ipc/glue/test/browser/test_utility_audio_on.html45
24 files changed, 1530 insertions, 0 deletions
diff --git a/ipc/glue/test/browser/browser.ini b/ipc/glue/test/browser/browser.ini
new file mode 100644
index 0000000000..9dc84c3914
--- /dev/null
+++ b/ipc/glue/test/browser/browser.ini
@@ -0,0 +1,59 @@
+[DEFAULT]
+support-files = head.js
+
+[browser_audio_telemetry_content.js]
+skip-if =
+ (os == 'win') # gfx blocks us because media.rdd-process.enabled=false disables PDMFactory::AllDecodersAreRemote()
+support-files =
+ head-telemetry.js
+ ../../../../dom/media/test/small-shot.ogg
+ ../../../../dom/media/test/small-shot.mp3
+ ../../../../dom/media/test/small-shot.m4a
+ ../../../../dom/media/test/small-shot.flac
+[browser_audio_telemetry_rdd.js]
+support-files =
+ head-telemetry.js
+ ../../../../dom/media/test/small-shot.ogg
+ ../../../../dom/media/test/small-shot.mp3
+ ../../../../dom/media/test/small-shot.m4a
+ ../../../../dom/media/test/small-shot.flac
+[browser_audio_telemetry_utility.js]
+support-files =
+ head-telemetry.js
+ ../../../../dom/media/test/small-shot.ogg
+ ../../../../dom/media/test/small-shot.mp3
+ ../../../../dom/media/test/small-shot.m4a
+ ../../../../dom/media/test/small-shot.flac
+[browser_utility_audioDecodeCrash.js]
+support-files =
+ ../../../../dom/media/test/small-shot.ogg
+ ../../../../dom/media/test/small-shot.mp3
+ ../../../../dom/media/test/small-shot.m4a
+ ../../../../dom/media/test/small-shot.flac
+skip-if =
+ !crashreporter
+ ccov
+[browser_utility_crashReporter.js]
+skip-if =
+ !crashreporter
+ ccov
+[browser_utility_geolocation_crashed.js]
+skip-if =
+ !(os == 'win' && os_version == '10.0') # Geolocation is remoted only on Windows 8+
+ !crashreporter
+ ccov
+[browser_utility_hard_kill.js]
+[browser_utility_hard_kill_delayed.js] # bug 1754572: we really want hard_kill to be rust before hard_kill_delayed
+[browser_utility_memoryReport.js]
+skip-if = tsan # bug 1754554
+[browser_utility_multipleAudio.js]
+support-files =
+ ../../../../dom/media/test/small-shot.ogg
+ ../../../../dom/media/test/small-shot.mp3
+ ../../../../dom/media/test/small-shot.m4a
+ ../../../../dom/media/test/small-shot.flac
+[browser_utility_profiler.js]
+support-files =
+ ../../../../tools/profiler/tests/shared-head.js
+skip-if = tsan # from tools/profiler/tests/browser/browser.ini, timing out on profiler tests?
+[browser_utility_start_clean_shutdown.js]
diff --git a/ipc/glue/test/browser/browser_audio_shutdown.ini b/ipc/glue/test/browser/browser_audio_shutdown.ini
new file mode 100644
index 0000000000..a0ce9796f0
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_shutdown.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files = head.js
+
+[browser_utility_audio_shutdown.js]
+support-files =
+ ../../../../dom/media/test/small-shot.ogg
diff --git a/ipc/glue/test/browser/browser_audio_telemetry_content.js b/ipc/glue/test/browser/browser_audio_telemetry_content.js
new file mode 100644
index 0000000000..5d69175d5c
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_telemetry_content.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head-telemetry.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/ipc/glue/test/browser/head-telemetry.js",
+ this
+);
+
+add_setup(async function testNoTelemetry() {
+ await Telemetry.clearScalars();
+});
+
+add_task(async function testAudioDecodingInContent() {
+ await runTest({ expectUtility: false, expectRDD: false });
+});
+
+add_task(async function testUtilityTelemetry() {
+ const codecs = ["vorbis", "mp3", "aac", "flac"];
+ const extraKey = ",rdd-disabled,utility-disabled";
+ await verifyTelemetryForProcess("tab", codecs, extraKey);
+
+ const platform = Services.appinfo.OS;
+ for (let exp of utilityPerCodecs[platform]) {
+ await verifyNoTelemetryForProcess(exp.process, exp.codecs, extraKey);
+ }
+
+ await verifyNoTelemetryForProcess("rdd", codecs, extraKey);
+});
diff --git a/ipc/glue/test/browser/browser_audio_telemetry_rdd.js b/ipc/glue/test/browser/browser_audio_telemetry_rdd.js
new file mode 100644
index 0000000000..b4e41b562e
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_telemetry_rdd.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head-telemetry.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/ipc/glue/test/browser/head-telemetry.js",
+ this
+);
+
+add_setup(async function testNoTelemetry() {
+ await Telemetry.clearScalars();
+});
+
+add_task(async function testAudioDecodingInRDD() {
+ await runTest({ expectUtility: false, expectRDD: true });
+});
+
+add_task(async function testRDDTelemetry() {
+ const extraKey = ",utility-disabled";
+ const platform = Services.appinfo.OS;
+ for (let exp of utilityPerCodecs[platform]) {
+ await verifyNoTelemetryForProcess(exp.process, exp.codecs, extraKey);
+ }
+ const codecs = ["vorbis", "mp3", "aac", "flac"];
+ await verifyTelemetryForProcess("rdd", codecs, extraKey);
+});
diff --git a/ipc/glue/test/browser/browser_audio_telemetry_utility.js b/ipc/glue/test/browser/browser_audio_telemetry_utility.js
new file mode 100644
index 0000000000..07b492cc12
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_telemetry_utility.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head-telemetry.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/ipc/glue/test/browser/head-telemetry.js",
+ this
+);
+
+add_setup(async function testNoTelemetry() {
+ await Telemetry.clearScalars();
+});
+
+add_task(async function testAudioDecodingInUtility() {
+ await runTest({ expectUtility: true, expectRDD: true });
+});
+
+add_task(async function testUtilityTelemetry() {
+ const platform = Services.appinfo.OS;
+ for (let exp of utilityPerCodecs[platform]) {
+ await verifyTelemetryForProcess(exp.process, exp.codecs);
+ }
+ await verifyNoTelemetryForProcess("rdd", ["vorbis", "mp3", "aac", "flac"]);
+});
diff --git a/ipc/glue/test/browser/browser_child_hang.ini b/ipc/glue/test/browser/browser_child_hang.ini
new file mode 100644
index 0000000000..94931561b0
--- /dev/null
+++ b/ipc/glue/test/browser/browser_child_hang.ini
@@ -0,0 +1,7 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+[DEFAULT]
+tags = ipc
+environment = MOZ_TEST_CHILD_EXIT_HANG=3
+
+[browser_child_hang.js]
diff --git a/ipc/glue/test/browser/browser_child_hang.js b/ipc/glue/test/browser/browser_child_hang.js
new file mode 100644
index 0000000000..cf890a6c61
--- /dev/null
+++ b/ipc/glue/test/browser/browser_child_hang.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+//
+// Try to open a tab. This provides code coverage for a few things,
+// although currently there's no automated functional test of correctness:
+//
+// * On opt builds, when the tab is closed and the process exits, it
+// will hang for 3s and the parent will kill it after 2s.
+//
+// * On debug[*] builds, the parent process will wait until the
+// process exits normally; but also, on browser shutdown, the
+// preallocated content processes will block parent shutdown in
+// WillDestroyCurrentMessageLoop.
+//
+// [*] Also sanitizer and code coverage builds.
+//
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "https://example.com/",
+ forceNewProcess: true,
+ },
+ async function (browser) {
+ // browser.frameLoader.remoteTab.osPid is the child pid; once we
+ // have a way to get notifications about child process termination
+ // events, that could be useful.
+ ok(true, "Browser isn't broken");
+ }
+ );
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 4000));
+ ok(true, "Still running after child process (hopefully) exited");
+});
diff --git a/ipc/glue/test/browser/browser_utility_audioDecodeCrash.js b/ipc/glue/test/browser/browser_utility_audioDecodeCrash.js
new file mode 100644
index 0000000000..1c7551c623
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_audioDecodeCrash.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function getAudioDecoderPid(expectation) {
+ info("Finding a running AudioDecoder");
+
+ const actor = expectation.replace("Utility ", "");
+
+ let audioDecoderProcess = (await ChromeUtils.requestProcInfo()).children.find(
+ p =>
+ p.type === "utility" &&
+ p.utilityActors.find(a => a.actorName === `audioDecoder_${actor}`)
+ );
+ ok(
+ audioDecoderProcess,
+ `Found the AudioDecoder ${actor} process at ${audioDecoderProcess.pid}`
+ );
+ return audioDecoderProcess.pid;
+}
+
+async function crashDecoder(expectation) {
+ const audioPid = await getAudioDecoderPid(expectation);
+ ok(audioPid > 0, `Found an audio decoder ${audioPid}`);
+ const actorIsAudioDecoder = actorNames => {
+ return actorNames
+ .split(",")
+ .some(actorName => actorName.trim().startsWith("audio-decoder-"));
+ };
+ info(`Crashing audio decoder ${audioPid}`);
+ await crashSomeUtility(audioPid, actorIsAudioDecoder);
+}
+
+async function runTest(src, withClose, expectation) {
+ info(`Add media tabs: ${src}`);
+ let tab = await addMediaTab(src);
+
+ info("Play tab");
+ await play(tab, expectation.process, expectation.decoder);
+
+ info("Crash decoder");
+ await crashDecoder(expectation.process);
+
+ if (withClose) {
+ info("Stop tab");
+ await stop(tab);
+
+ info("Remove tab");
+ await BrowserTestUtils.removeTab(tab);
+
+ info("Create tab again");
+ tab = await addMediaTab(src);
+ }
+
+ info("Play tab again");
+ await play(tab, expectation.process, expectation.decoder);
+
+ info("Stop tab");
+ await stop(tab);
+
+ info("Remove tab");
+ await BrowserTestUtils.removeTab(tab);
+}
+
+add_setup(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.utility-process.enabled", true]],
+ });
+});
+
+async function testAudioCrash(withClose) {
+ info(`Running tests for audio decoder process crashing: ${withClose}`);
+
+ SimpleTest.expectChildProcessCrash();
+
+ const platform = Services.appinfo.OS;
+
+ for (let { src, expectations } of audioTestData()) {
+ if (!(platform in expectations)) {
+ info(`Skipping ${src} for ${platform}`);
+ continue;
+ }
+
+ await runTest(src, withClose, expectations[platform]);
+ }
+}
+
+add_task(async function testAudioCrashSimple() {
+ await testAudioCrash(false);
+});
+
+add_task(async function testAudioCrashClose() {
+ await testAudioCrash(true);
+});
diff --git a/ipc/glue/test/browser/browser_utility_audio_shutdown.js b/ipc/glue/test/browser/browser_utility_audio_shutdown.js
new file mode 100644
index 0000000000..31811cfd55
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_audio_shutdown.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// The purpose of that test is to reproduce edge case behaviors that one can
+// have while running whole ipc/glue/test/browser/ suite but that could this
+// way be intermittent and hard to diagnose. By having such a test we make sure
+// it is cleanly reproduced and wont regress somewhat silently.
+
+"use strict";
+
+async function runTest(src, process, decoder) {
+ info(`Add media tabs: ${src}`);
+ let tab = await addMediaTab(src);
+
+ info("Play tab");
+ await play(tab, process, decoder);
+
+ info("Stop tab");
+ await stop(tab);
+
+ info("Remove tab");
+ await BrowserTestUtils.removeTab(tab);
+}
+
+async function findGenericAudioDecoder() {
+ const audioDecoders = (await ChromeUtils.requestProcInfo()).children.filter(
+ p => {
+ return (
+ p.type === "utility" &&
+ p.utilityActors.find(a => a.actorName === "audioDecoder_Generic")
+ );
+ }
+ );
+ ok(audioDecoders.length === 1, "Only one audio decoder present");
+ return audioDecoders[0].pid;
+}
+
+add_setup(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.utility-process.enabled", true]],
+ });
+});
+
+add_task(async function testKill() {
+ await runTest("small-shot.ogg", "Utility Generic", "vorbis audio decoder");
+
+ await cleanUtilityProcessShutdown(
+ "audioDecoder_Generic",
+ true /* preferKill */
+ );
+
+ info("Waiting 15s to trigger mShutdownBlockers assertions");
+ await new Promise((resolve, reject) => {
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ setTimeout(resolve, 15 * 1000);
+ });
+
+ ok(true, "Waited 15s to trigger mShutdownBlockers assertions: over");
+});
+
+add_task(async function testShutdown() {
+ await runTest("small-shot.ogg", "Utility Generic", "vorbis audio decoder");
+
+ const audioDecoderPid = await findGenericAudioDecoder();
+ ok(audioDecoderPid > 0, `Valid PID found: ${audioDecoderPid}`);
+
+ await cleanUtilityProcessShutdown("audioDecoder_Generic");
+
+ info("Waiting 15s to trigger mShutdownBlockers assertions");
+ await new Promise((resolve, reject) => {
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ setTimeout(resolve, 15 * 1000);
+ });
+
+ ok(true, "Waited 15s to trigger mShutdownBlockers assertions: over");
+});
diff --git a/ipc/glue/test/browser/browser_utility_crashReporter.js b/ipc/glue/test/browser/browser_utility_crashReporter.js
new file mode 100644
index 0000000000..73e6c6355a
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_crashReporter.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function startAndCrashUtility(numUnknownActors, actorsCheck) {
+ const actors = Array(numUnknownActors).fill("unknown");
+ const utilityPid = await startUtilityProcess(actors);
+ await crashSomeUtility(utilityPid, actorsCheck);
+}
+
+// When running full suite, previous tests may have left some utility
+// processes running and this might interfere with our testing.
+add_setup(async function ensureNoExistingProcess() {
+ await killUtilityProcesses();
+});
+
+add_task(async function utilityNoActor() {
+ await startAndCrashUtility(0, actorNames => {
+ return actorNames === undefined;
+ });
+});
+
+add_task(async function utilityOneActor() {
+ await startAndCrashUtility(1, actorNames => {
+ return actorNames === kGenericUtilityActor;
+ });
+});
+
+add_task(async function utilityManyActors() {
+ await startAndCrashUtility(42, actorNames => {
+ return actorNames === Array(42).fill("unknown").join(", ");
+ });
+});
diff --git a/ipc/glue/test/browser/browser_utility_geolocation_crashed.js b/ipc/glue/test/browser/browser_utility_geolocation_crashed.js
new file mode 100644
index 0000000000..b0c341b69f
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_geolocation_crashed.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function getGeolocation() {
+ info("Requesting geolocation");
+
+ let resolve;
+ let promise = new Promise((_resolve, _reject) => {
+ resolve = _resolve;
+ });
+
+ navigator.geolocation.getCurrentPosition(
+ () => {
+ ok(true, "geolocation succeeded");
+ resolve(undefined);
+ },
+ () => {
+ ok(false, "geolocation failed");
+ resolve(undefined);
+ }
+ );
+
+ return promise;
+}
+
+add_setup(async function () {
+ // Avoid the permission doorhanger and cache that would trigger instead
+ // of re-requesting location. Setting geo.timeout to 0 causes it to
+ // retry the system geolocation (incl. recreating the utility process)
+ // instead of reusing the MLS geolocation fallback it found the first time.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["geo.prompt.testing", true],
+ ["geo.prompt.testing.allow", true],
+ ["geo.provider.network.debug.requestCache.enabled", false],
+ ["geo.provider.testing", false],
+ ["geo.timeout", 0],
+ ],
+ });
+});
+
+add_task(async function testGeolocationProcessCrash() {
+ info("Start the Windows utility process");
+ await getGeolocation();
+
+ info("Crash the utility process");
+ await crashSomeUtilityActor("windowsUtils");
+
+ info("Restart the Windows utility process");
+ await getGeolocation();
+
+ info("Confirm the restarted process");
+ await checkUtilityExists("windowsUtils");
+
+ info("Kill the utility process");
+ await cleanUtilityProcessShutdown("windowsUtils", true);
+
+ info("Restart the Windows utility process again");
+ await getGeolocation();
+
+ info("Confirm the restarted process");
+ await checkUtilityExists("windowsUtils");
+});
+
+add_task(async function testCleanup() {
+ info("Clean up to avoid confusing future tests");
+ await cleanUtilityProcessShutdown("windowsUtils", true);
+});
diff --git a/ipc/glue/test/browser/browser_utility_hard_kill.js b/ipc/glue/test/browser/browser_utility_hard_kill.js
new file mode 100644
index 0000000000..516ef64045
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_hard_kill.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ await startUtilityProcess(["unknown"]);
+
+ SimpleTest.expectChildProcessCrash();
+
+ info("Hard kill Utility Process");
+ await cleanUtilityProcessShutdown("unknown", true /* preferKill */);
+});
diff --git a/ipc/glue/test/browser/browser_utility_hard_kill_delayed.js b/ipc/glue/test/browser/browser_utility_hard_kill_delayed.js
new file mode 100644
index 0000000000..ffda4d3988
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_hard_kill_delayed.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ const utilityPid = await startUtilityProcess();
+
+ SimpleTest.expectChildProcessCrash();
+
+ const utilityProcessGone = TestUtils.topicObserved("ipc:utility-shutdown");
+
+ info("Hard kill Utility Process");
+ const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService(
+ Ci.nsIProcessToolsService
+ );
+
+ // Here we really want to exercise the fact that kill() might not be done
+ // right now but a bit later, and we should wait for the process to be dead
+ // before considering the test is finished.
+ //
+ // Without this, we get into bug 1754572 (where there was no setTimeout nor
+ // the wait) where the kill() operation ends up really killing the child a
+ // bit after the current test has been finished ; unfortunately, this happened
+ // right after the next test, browser_utility_memoryReport.js did start and
+ // even worse, after it thought it had started a new utility process. We were
+ // in fact re-using the one we started here, and when we wanted to query its
+ // pid in the browser_utility_memoryReport.js then the kill() happened, so
+ // no more process and the test intermittently failed.
+ //
+ // The timeout value of 50ms should be long enough to allow the test to finish
+ // and the next one to start and get a reference on the process we launched,
+ // and yet allow us to kill the process in the middle of the next test. Higher
+ // values would allow browser_utility_memoryReport.js to complete without
+ // reproducing the issue (both locally and on try).
+ //
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(() => {
+ ProcessTools.kill(utilityPid);
+ }, 50);
+
+ info(`Waiting for utility process ${utilityPid} to go away.`);
+ 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);
+});
diff --git a/ipc/glue/test/browser/browser_utility_memoryReport.js b/ipc/glue/test/browser/browser_utility_memoryReport.js
new file mode 100644
index 0000000000..8cec61b8be
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_memoryReport.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// When running full suite, previous audio decoding tests might have left some
+// running and this might interfere with our testing
+add_setup(async function ensureNoExistingProcess() {
+ await killUtilityProcesses();
+});
+
+add_task(async () => {
+ const utilityPid = await startUtilityProcess();
+
+ const gMgr = Cc["@mozilla.org/memory-reporter-manager;1"].getService(
+ Ci.nsIMemoryReporterManager
+ );
+ ok(utilityPid !== undefined, `Utility process is running as ${utilityPid}`);
+
+ var utilityReports = [];
+
+ const performCollection = new Promise((resolve, reject) => {
+ // Record the reports from the live memory reporters then process them.
+ let handleReport = function (
+ aProcess,
+ aUnsafePath,
+ aKind,
+ aUnits,
+ aAmount,
+ aDescription
+ ) {
+ const expectedProcess = `Utility (pid ${utilityPid}, sandboxingKind ${kGenericUtilitySandbox})`;
+ if (aProcess !== expectedProcess) {
+ return;
+ }
+
+ let report = {
+ process: aProcess,
+ path: aUnsafePath,
+ kind: aKind,
+ units: aUnits,
+ amount: aAmount,
+ description: aDescription,
+ };
+
+ utilityReports.push(report);
+ };
+
+ info("Memory report: Perform the call");
+ gMgr.getReports(handleReport, null, resolve, null, false);
+ });
+
+ await performCollection;
+
+ info(
+ `Collected ${utilityReports.length} reports from utility process ${utilityPid}`
+ );
+ ok(!!utilityReports.length, "Collected some reports");
+ ok(
+ utilityReports.filter(r => r.path === "vsize" && r.amount > 0).length === 1,
+ "Collected vsize report"
+ );
+ ok(
+ utilityReports.filter(r => r.path === "resident" && r.amount > 0).length ===
+ 1,
+ "Collected resident report"
+ );
+ ok(
+ !!utilityReports.filter(
+ r => r.path.search(/^explicit\/.*/) >= 0 && r.amount > 0
+ ).length,
+ "Collected some explicit/ report"
+ );
+
+ await cleanUtilityProcessShutdown();
+});
diff --git a/ipc/glue/test/browser/browser_utility_multipleAudio.js b/ipc/glue/test/browser/browser_utility_multipleAudio.js
new file mode 100644
index 0000000000..d88ee014e2
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_multipleAudio.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function runTest(expectUtility) {
+ info(
+ `Running tests with decoding from Utility or RDD: expectUtility=${expectUtility}`
+ );
+
+ // Utility should now be the default, so dont toggle the pref unless we test
+ // RDD
+ if (!expectUtility) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.utility-process.enabled", expectUtility]],
+ });
+ }
+
+ const platform = Services.appinfo.OS;
+
+ for (let { src, expectations } of audioTestData()) {
+ if (!(platform in expectations)) {
+ info(`Skipping ${src} for ${platform}`);
+ continue;
+ }
+
+ const expectation = expectations[platform];
+
+ info(`Add media tabs: ${src}`);
+ let tabs = [await addMediaTab(src), await addMediaTab(src)];
+ let playback = [];
+
+ info("Play tabs");
+ for (let tab of tabs) {
+ playback.push(
+ play(
+ tab,
+ expectUtility ? expectation.process : "RDD",
+ expectation.decoder
+ )
+ );
+ }
+
+ info("Wait all playback");
+ await Promise.all(playback);
+
+ let allstop = [];
+ info("Stop tabs");
+ for (let tab of tabs) {
+ allstop.push(stop(tab));
+ }
+
+ info("Wait all stop");
+ await Promise.all(allstop);
+
+ let remove = [];
+ info("Remove tabs");
+ for (let tab of tabs) {
+ remove.push(BrowserTestUtils.removeTab(tab));
+ }
+
+ info("Wait all tabs to be removed");
+ await Promise.all(remove);
+ }
+}
+
+add_task(async function testAudioDecodingInUtility() {
+ await runTest(true);
+});
+
+add_task(async function testAudioDecodingInRDD() {
+ await runTest(false);
+});
diff --git a/ipc/glue/test/browser/browser_utility_profiler.js b/ipc/glue/test/browser/browser_utility_profiler.js
new file mode 100644
index 0000000000..084cd67747
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_profiler.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from /tools/profiler/tests/shared-head.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/tools/profiler/tests/browser/shared-head.js",
+ this
+);
+
+// When running full suite, previous tests may have left some utility
+// processes running and this might interfere with our testing.
+add_setup(async function ensureNoExistingProcess() {
+ await killUtilityProcesses();
+});
+
+add_task(async () => {
+ const utilityPid = await startUtilityProcess();
+
+ info("Start the profiler");
+ await startProfiler();
+
+ let profile;
+ await TestUtils.waitForCondition(async () => {
+ profile = await Services.profiler.getProfileDataAsync();
+ return (
+ // Search for process name to not be disturbed by other types of utility
+ // e.g. Utility AudioDecoder
+ profile.processes.filter(
+ ps => ps.threads[0].processName === "Utility Process"
+ ).length === 1
+ );
+ }, "Give time for the profiler to start and collect some samples");
+
+ info(`Check that the utility process ${utilityPid} is present.`);
+ let utilityProcessIndex = profile.processes.findIndex(
+ p => p.threads[0].pid == utilityPid
+ );
+ Assert.notEqual(utilityProcessIndex, -1, "Could find index of utility");
+
+ Assert.equal(
+ profile.processes[utilityProcessIndex].threads[0].processType,
+ "utility",
+ "Profile has processType utility"
+ );
+
+ Assert.equal(
+ profile.processes[utilityProcessIndex].threads[0].name,
+ "GeckoMain",
+ "Profile has correct main thread name"
+ );
+
+ Assert.equal(
+ profile.processes[utilityProcessIndex].threads[0].processName,
+ "Utility Process",
+ "Profile has correct process name"
+ );
+
+ Assert.greater(
+ profile.processes[utilityProcessIndex].threads.length,
+ 0,
+ "The utility process should have threads"
+ );
+
+ Assert.equal(
+ profile.threads.length,
+ 1,
+ "The parent process should have only one thread"
+ );
+
+ Services.profiler.StopProfiler();
+
+ await cleanUtilityProcessShutdown();
+});
diff --git a/ipc/glue/test/browser/browser_utility_start_clean_shutdown.js b/ipc/glue/test/browser/browser_utility_start_clean_shutdown.js
new file mode 100644
index 0000000000..62a9e4065b
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_start_clean_shutdown.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ await startUtilityProcess();
+ await cleanUtilityProcessShutdown();
+});
diff --git a/ipc/glue/test/browser/head-telemetry.js b/ipc/glue/test/browser/head-telemetry.js
new file mode 100644
index 0000000000..64a6aee1c4
--- /dev/null
+++ b/ipc/glue/test/browser/head-telemetry.js
@@ -0,0 +1,205 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head.js */
+
+const Telemetry = Services.telemetry;
+
+const { TelemetryController } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryController.sys.mjs"
+);
+
+/* eslint-disable mozilla/no-redeclare-with-import-autofix */
+const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+);
+
+const MEDIA_AUDIO_PROCESS = "media.audio_process_per_codec_name";
+
+const utilityPerCodecs = {
+ Linux: [
+ {
+ process: "utility+audioDecoder_Generic",
+ codecs: ["vorbis", "mp3", "aac", "flac"],
+ },
+ ],
+ WINNT: [
+ {
+ process: "utility+audioDecoder_Generic",
+ codecs: ["vorbis", "mp3", "flac"],
+ },
+ {
+ process: "utility+audioDecoder_WMF",
+ codecs: ["aac"],
+ },
+ ],
+ Darwin: [
+ {
+ process: "utility+audioDecoder_Generic",
+ codecs: ["vorbis", "mp3", "flac"],
+ },
+ {
+ process: "utility+audioDecoder_AppleMedia",
+ codecs: ["aac"],
+ },
+ ],
+};
+
+const kInterval = 300; /* ms */
+const kRetries = 5;
+
+/**
+ * This function waits until utility scalars are reported into the
+ * scalar snapshot.
+ */
+async function waitForKeyedScalars(process) {
+ await ContentTaskUtils.waitForCondition(
+ () => {
+ const scalars = Telemetry.getSnapshotForKeyedScalars("main", false);
+ return Object.keys(scalars).includes("content");
+ },
+ `Waiting for ${process} scalars to have been set`,
+ kInterval,
+ kRetries
+ );
+}
+
+async function waitForValue(process, codecNames, extra = "") {
+ await ContentTaskUtils.waitForCondition(
+ () => {
+ const telemetry = Telemetry.getSnapshotForKeyedScalars(
+ "main",
+ false
+ ).content;
+ if (telemetry && MEDIA_AUDIO_PROCESS in telemetry) {
+ const keyProcMimeTypes = Object.keys(telemetry[MEDIA_AUDIO_PROCESS]);
+ const found = codecNames.every(item =>
+ keyProcMimeTypes.includes(`${process},${item}${extra}`)
+ );
+ return found;
+ }
+ return false;
+ },
+ `Waiting for ${MEDIA_AUDIO_PROCESS}`,
+ kInterval,
+ kRetries
+ );
+}
+
+async function runTest({ expectUtility = false, expectRDD = false }) {
+ info(
+ `Running tests with decoding from Utility or RDD: expectUtility=${expectUtility} expectRDD=${expectRDD}`
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["media.utility-process.enabled", expectUtility],
+ ["media.rdd-process.enabled", expectRDD],
+ ["toolkit.telemetry.ipcBatchTimeout", 0],
+ ],
+ });
+
+ const platform = Services.appinfo.OS;
+
+ for (let { src, expectations } of audioTestData()) {
+ if (!(platform in expectations)) {
+ info(`Skipping ${src} for ${platform}`);
+ continue;
+ }
+
+ const expectation = expectations[platform];
+
+ info(`Add media tab: ${src}`);
+ let tab = await addMediaTab(src);
+
+ info("Play tab");
+ await play(
+ tab,
+ expectUtility ? expectation.process : "RDD",
+ expectation.decoder,
+ !expectUtility && !expectRDD
+ );
+
+ info("Stop tab");
+ await stop(tab);
+
+ info("Remove tab");
+ await BrowserTestUtils.removeTab(tab);
+ }
+}
+
+function getTelemetry() {
+ const telemetry = Telemetry.getSnapshotForKeyedScalars("main", false).content;
+ return telemetry;
+}
+
+async function verifyTelemetryForProcess(process, codecNames, extraKey = "") {
+ // Once scalars are set by the utility process, they don't immediately get
+ // sent to the parent process. Wait for the Telemetry IPC Timer to trigger
+ // and batch send the data back to the parent process.
+ await waitForKeyedScalars(process);
+ await waitForValue(process, codecNames, extraKey);
+
+ const telemetry = getTelemetry();
+
+ // The amount here depends on how many times RemoteAudioDecoderParent::RemoteAudioDecoderParent
+ // gets called, which might be more than actual audio files being playback, e.g., we would get one for metadata loading, then one for playback etc.
+ // But we dont care really we just want to ensure 0 on RDD, Content and others
+ // in the wild.[${codecName}]
+ codecNames.forEach(codecName => {
+ Assert.equal(
+ telemetry[MEDIA_AUDIO_PROCESS][`${process},${codecName}${extraKey}`],
+ 1,
+ `${MEDIA_AUDIO_PROCESS} must have the correct value (${process}, ${codecName}).`
+ );
+ });
+}
+
+async function verifyNoTelemetryForProcess(process, codecNames, extraKey = "") {
+ try {
+ await waitForKeyedScalars(process);
+ await waitForValue(process, codecNames, extraKey);
+ } catch (ex) {
+ if (ex.indexOf("timed out after") > 0) {
+ Assert.ok(
+ true,
+ `Expected timeout ${process}[${MEDIA_AUDIO_PROCESS}] for ${codecNames}`
+ );
+ } else {
+ Assert.ok(
+ false,
+ `Unexpected exception on ${process}[${MEDIA_AUDIO_PROCESS}] for ${codecNames}: ${ex}`
+ );
+ }
+ }
+
+ const telemetry = getTelemetry();
+
+ // There could be races with telemetry for power usage coming up
+ codecNames.forEach(codecName => {
+ if (telemetry) {
+ if (telemetry && MEDIA_AUDIO_PROCESS in telemetry) {
+ Assert.ok(
+ !(
+ `${process},${codecName}${extraKey}` in
+ telemetry[MEDIA_AUDIO_PROCESS]
+ ),
+ `Some telemetry but no ${process}[${MEDIA_AUDIO_PROCESS}][${codecName}]`
+ );
+ } else {
+ Assert.ok(
+ !(MEDIA_AUDIO_PROCESS in telemetry),
+ `No telemetry for ${process}[${MEDIA_AUDIO_PROCESS}][${codecName}]`
+ );
+ }
+ } else {
+ Assert.equal(
+ undefined,
+ telemetry,
+ `No telemetry for ${process}[${MEDIA_AUDIO_PROCESS}][${codecName}]`
+ );
+ }
+ });
+}
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);
+}
diff --git a/ipc/glue/test/browser/mochitest_audio_off.ini b/ipc/glue/test/browser/mochitest_audio_off.ini
new file mode 100644
index 0000000000..1e105c5f87
--- /dev/null
+++ b/ipc/glue/test/browser/mochitest_audio_off.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+run-if = toolkit == 'android' && !isolated_process # Bug 1771452
+support-files =
+ head.js
+ ../../../../dom/media/test/small-shot.ogg
+ ../../../../dom/media/test/small-shot.mp3
+ ../../../../dom/media/test/small-shot.m4a
+ ../../../../dom/media/test/small-shot.flac
+prefs =
+ media.utility-process.enabled=false
+
+[test_utility_audio_off.html]
diff --git a/ipc/glue/test/browser/mochitest_audio_on.ini b/ipc/glue/test/browser/mochitest_audio_on.ini
new file mode 100644
index 0000000000..8d74572341
--- /dev/null
+++ b/ipc/glue/test/browser/mochitest_audio_on.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+run-if = toolkit == 'android' && !isolated_process # Bug 1771452
+support-files =
+ head.js
+ ../../../../dom/media/test/small-shot.ogg
+ ../../../../dom/media/test/small-shot.mp3
+ ../../../../dom/media/test/small-shot.m4a
+ ../../../../dom/media/test/small-shot.flac
+prefs =
+ media.utility-process.enabled=true
+
+[test_utility_audio_on.html]
diff --git a/ipc/glue/test/browser/moz.build b/ipc/glue/test/browser/moz.build
new file mode 100644
index 0000000000..d0a85bebcd
--- /dev/null
+++ b/ipc/glue/test/browser/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+BROWSER_CHROME_MANIFESTS += [
+ "browser.ini",
+ "browser_audio_shutdown.ini",
+ "browser_child_hang.ini",
+]
+MOCHITEST_MANIFESTS += ["mochitest_audio_off.ini", "mochitest_audio_on.ini"]
diff --git a/ipc/glue/test/browser/test_utility_audio_off.html b/ipc/glue/test/browser/test_utility_audio_off.html
new file mode 100644
index 0000000000..7df18ed383
--- /dev/null
+++ b/ipc/glue/test/browser/test_utility_audio_off.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Audio decoder not in Utility process</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+(async function() {
+ const platform = SpecialPowers.Services.appinfo.OS;
+ for (let {src, expectations} of audioTestData()) {
+ if (!(platform in expectations)) {
+ info(`Skipping ${src} for ${platform}`);
+ continue;
+ }
+
+ try {
+ await runMochitestUtilityAudio(src, { expectUtility: "", expectDecoder: expectations[platform].decoder, expectContent: true, expectJava: false });
+ } catch (ex) {
+ ok(false, "Failure");
+ }
+ }
+
+ for (let src of [
+ "small-shot.m4a",
+ ]) {
+ try {
+ await runMochitestUtilityAudio(src, { expectUtility: "", expectDecoder: "android decoder", expectContent: false, expectJava: true });
+ } catch (ex) {
+ ok(false, `Failure ${ex}`);
+ }
+ }
+
+ SimpleTest.finish();
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/ipc/glue/test/browser/test_utility_audio_on.html b/ipc/glue/test/browser/test_utility_audio_on.html
new file mode 100644
index 0000000000..f473527520
--- /dev/null
+++ b/ipc/glue/test/browser/test_utility_audio_on.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Audio decoder in Utility process</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+(async function() {
+ const platform = SpecialPowers.Services.appinfo.OS;
+ for (let {src, expectations} of audioTestData()) {
+ if (!(platform in expectations)) {
+ info(`Skipping ${src} for ${platform}`);
+ continue;
+ }
+
+ try {
+ await runMochitestUtilityAudio(src, { expectUtility: expectations[platform].process, expectDecoder: expectations[platform].decoder, expectContent: false, expectJava: false });
+ } catch (ex) {
+ ok(false, "Failure");
+ }
+ }
+
+ // Remove all after Bug 1771196
+ for (let src of [
+ "small-shot.m4a",
+ ]) {
+ try {
+ await runMochitestUtilityAudio(src, { expectUtility: "", expectDecoder: "android decoder", expectContent: false, expectJava: true });
+ } catch (ex) {
+ ok(false, `Failure ${ex}`);
+ }
+ }
+
+ SimpleTest.finish();
+})();
+</script>
+</pre>
+</body>
+</html>