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.toml94
-rw-r--r--ipc/glue/test/browser/browser_audio_fallback.toml17
-rw-r--r--ipc/glue/test/browser/browser_audio_fallback_content.toml17
-rw-r--r--ipc/glue/test/browser/browser_audio_locked.toml3
-rw-r--r--ipc/glue/test/browser/browser_audio_shutdown.toml5
-rw-r--r--ipc/glue/test/browser/browser_audio_telemetry_content.js39
-rw-r--r--ipc/glue/test/browser/browser_audio_telemetry_rdd.js36
-rw-r--r--ipc/glue/test/browser/browser_audio_telemetry_utility.js32
-rw-r--r--ipc/glue/test/browser/browser_audio_telemetry_utility_EME.js31
-rw-r--r--ipc/glue/test/browser/browser_child_hang.js37
-rw-r--r--ipc/glue/test/browser/browser_child_hang.toml7
-rw-r--r--ipc/glue/test/browser/browser_utility_audioDecodeCrash.js95
-rw-r--r--ipc/glue/test/browser/browser_utility_audio_locked.js28
-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_filepicker_crashed.js170
-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.js45
-rw-r--r--ipc/glue/test/browser/browser_utility_multipleAudio_fallback.js22
-rw-r--r--ipc/glue/test/browser/browser_utility_multipleAudio_fallback_content.js22
-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-multiple.js78
-rw-r--r--ipc/glue/test/browser/head-telemetry.js269
-rw-r--r--ipc/glue/test/browser/head.js562
-rw-r--r--ipc/glue/test/browser/mochitest_audio_off.toml12
-rw-r--r--ipc/glue/test/browser/mochitest_audio_on.toml12
-rw-r--r--ipc/glue/test/browser/moz.build15
-rw-r--r--ipc/glue/test/browser/test_utility_audio_off.html44
-rw-r--r--ipc/glue/test/browser/test_utility_audio_on.html45
33 files changed, 2148 insertions, 0 deletions
diff --git a/ipc/glue/test/browser/browser.toml b/ipc/glue/test/browser/browser.toml
new file mode 100644
index 0000000000..8f94bc130d
--- /dev/null
+++ b/ipc/glue/test/browser/browser.toml
@@ -0,0 +1,94 @@
+[DEFAULT]
+support-files = ["head.js"]
+# Set this since we want to continue monitoring the disabling of pref since we
+# still allow it a little bit.
+environment = "MOZ_DONT_LOCK_UTILITY_PLZ_FILE_A_BUG=1"
+
+["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_audio_telemetry_utility_EME.js"]
+support-files = [
+ "head-telemetry.js",
+ "../../../../dom/media/test/eme_standalone.js",
+ "../../../../dom/media/test/short-aac-encrypted-audio.mp4"
+]
+
+["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_filepicker_crashed.js"]
+run-if = ["os == 'win'"]
+skip-if = [
+ "!crashreporter",
+ "ccov",
+]
+
+["browser_utility_geolocation_crashed.js"]
+run-if = ["os == 'win'"]
+skip-if = [
+ "!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",
+ "head-multiple.js"
+]
+
+["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_fallback.toml b/ipc/glue/test/browser/browser_audio_fallback.toml
new file mode 100644
index 0000000000..0de2a1c9e7
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_fallback.toml
@@ -0,0 +1,17 @@
+[DEFAULT]
+support-files = [
+ "head.js",
+ "head-multiple.js",
+]
+prefs = ["media.allow-audio-non-utility=true"]
+# Set this since we want to continue monitoring the disabling of pref since we
+# still allow it a little bit.
+environment = "MOZ_DONT_LOCK_UTILITY_PLZ_FILE_A_BUG=1"
+
+["browser_utility_multipleAudio_fallback.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",
+]
diff --git a/ipc/glue/test/browser/browser_audio_fallback_content.toml b/ipc/glue/test/browser/browser_audio_fallback_content.toml
new file mode 100644
index 0000000000..3efc6409ac
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_fallback_content.toml
@@ -0,0 +1,17 @@
+[DEFAULT]
+support-files = [
+ "head.js",
+ "head-multiple.js",
+]
+prefs = [
+ "media.allow-audio-non-utility=true",
+ "media.rdd-process.enabled=false",
+]
+
+["browser_utility_multipleAudio_fallback_content.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",
+]
diff --git a/ipc/glue/test/browser/browser_audio_locked.toml b/ipc/glue/test/browser/browser_audio_locked.toml
new file mode 100644
index 0000000000..9f0607bf5f
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_locked.toml
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+["browser_utility_audio_locked.js"]
diff --git a/ipc/glue/test/browser/browser_audio_shutdown.toml b/ipc/glue/test/browser/browser_audio_shutdown.toml
new file mode 100644
index 0000000000..f99fff7830
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_shutdown.toml
@@ -0,0 +1,5 @@
+[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..89f5126794
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_telemetry_content.js
@@ -0,0 +1,39 @@
+/* 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();
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.allow-audio-non-utility", true]],
+ });
+});
+
+add_task(async function testAudioDecodingInContent() {
+ await runTest({ expectUtility: false, expectRDD: false });
+});
+
+add_task(async function testContentTelemetry() {
+ const codecs = ["vorbis", "mp3", "aac", "flac"];
+ const extraKey = getExtraKey({
+ rddPref: false,
+ utilityPref: false,
+ allowNonUtility: true,
+ });
+ 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..ec0944303b
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_telemetry_rdd.js
@@ -0,0 +1,36 @@
+/* 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();
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.allow-audio-non-utility", true]],
+ });
+});
+
+add_task(async function testAudioDecodingInRDD() {
+ await runTest({ expectUtility: false, expectRDD: true });
+});
+
+add_task(async function testRDDTelemetry() {
+ const extraKey = getExtraKey({
+ rddPref: true,
+ utilityPref: false,
+ allowNonUtility: true,
+ });
+ 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..e121c89049
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_telemetry_utility.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 testAudioDecodingInUtility() {
+ await runTest({ expectUtility: true, expectRDD: true });
+});
+
+add_task(async function testUtilityTelemetry() {
+ const platform = Services.appinfo.OS;
+ const extraKey = getExtraKey({ rddPref: true, utilityPref: true });
+ for (let exp of utilityPerCodecs[platform]) {
+ await verifyTelemetryForProcess(exp.process, exp.codecs, extraKey);
+ }
+ await verifyNoTelemetryForProcess(
+ "rdd",
+ ["vorbis", "mp3", "aac", "flac"],
+ extraKey
+ );
+});
diff --git a/ipc/glue/test/browser/browser_audio_telemetry_utility_EME.js b/ipc/glue/test/browser/browser_audio_telemetry_utility_EME.js
new file mode 100644
index 0000000000..7d2e9a4e78
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_telemetry_utility_EME.js
@@ -0,0 +1,31 @@
+/* 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
+);
+
+SimpleTest.requestCompleteLog();
+
+add_setup(async function testNoTelemetry() {
+ await Telemetry.clearScalars();
+});
+
+add_task(async function testAudioDecodingInUtility() {
+ await runTestWithEME();
+});
+
+add_task(async function testUtilityTelemetry() {
+ const platform = Services.appinfo.OS;
+ const extraKey = getExtraKey({ rddPref: true, utilityPref: true });
+ for (let exp of utilityPerCodecs[platform]) {
+ if (exp.codecs.includes("aac")) {
+ await verifyTelemetryForProcess(exp.process, ["aac"], extraKey);
+ }
+ }
+});
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_child_hang.toml b/ipc/glue/test/browser/browser_child_hang.toml
new file mode 100644
index 0000000000..ddc8b95670
--- /dev/null
+++ b/ipc/glue/test/browser/browser_child_hang.toml
@@ -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_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_locked.js b/ipc/glue/test/browser/browser_utility_audio_locked.js
new file mode 100644
index 0000000000..4be22de425
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_audio_locked.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head-multiple.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/ipc/glue/test/browser/head-multiple.js",
+ this
+);
+
+add_setup(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.utility-process.enabled", false]],
+ });
+});
+
+add_task(async function testAudioDecodingInUtility() {
+ // TODO: When getting rid of audio decoding on non utility at all, this
+ // should be removed
+ // We only lock the preference in Nightly builds so far, but on beta we expect
+ // audio decoding error
+ await runTest({
+ expectUtility: isNightlyOnly(),
+ expectError: !isNightlyOnly(),
+ });
+});
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..a0a4be63f6
--- /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", "ffvpx 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", "ffvpx 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_filepicker_crashed.js b/ipc/glue/test/browser/browser_utility_filepicker_crashed.js
new file mode 100644
index 0000000000..e8eb83cf30
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_filepicker_crashed.js
@@ -0,0 +1,170 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+SimpleTest.requestCompleteLog();
+
+// Wait until the child process with the given PID has indeed been terminated.
+//
+// Note that `checkUtilityExists`, and other functions deriving from the output
+// of `ChromeUtils.requestProcInfo()`, do not suffice for this purpose! It is an
+// attested failure mode that the file-dialog utility process has been removed
+// from the proc-info list, but is still live with the file-picker dialog still
+// displayed.
+function untilChildProcessDead(pid) {
+ return utilityProcessTest().untilChildProcessDead(pid);
+}
+
+async function fileDialogProcessExists() {
+ return !!(await tryGetUtilityPid("windowsFileDialog"));
+}
+
+// Poll for the creation of a file dialog process.
+function untilFileDialogProcessExists(options = { maxTime: 2000 }) {
+ // milliseconds
+ const maxTime = options.maxTime ?? 2000,
+ pollTime = options.pollTime ?? 100;
+ const count = maxTime / pollTime;
+
+ return TestUtils.waitForCondition(
+ () => tryGetUtilityPid("windowsFileDialog", { quiet: true }),
+ "waiting for file dialog process",
+ pollTime, // interval
+ count // maxTries
+ );
+}
+
+function openFileDialog() {
+ const process = (async () => {
+ await untilFileDialogProcessExists();
+ let pid = await tryGetUtilityPid("windowsFileDialog");
+ ok(pid, `pid should be acquired in openFileDialog::process (got ${pid})`);
+ // HACK: Wait briefly for the file dialog to open.
+ //
+ // If this is not done, we may attempt to crash the process while it's in
+ // the middle of creating and showing the file dialog window. There _should_
+ // be no problem with this, but `::MiniDumpWriteDump()` occasionally fails
+ // with mysterious errors (`ERROR_BAD_LENGTH`) if we crashed the process
+ // while that was happening, yielding no minidump and therefore a failing
+ // test.
+ //
+ // Use of an arbitrary timeout could presumably be avoided by setting a
+ // window hook for the file dialog being shown and `await`ing on that.
+ //
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(res => setTimeout(res, 1000));
+ return pid;
+ })();
+
+ const file = new Promise((resolve, reject) => {
+ info("Opening Windows file dialog");
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ fp.init(window, "Test: browser_utility_filepicker_crashed.js", fp.modeOpen);
+ fp.open(result => {
+ ok(
+ result == fp.returnCancel,
+ "filepicker should resolve to cancellation"
+ );
+ resolve();
+ });
+ });
+
+ return { process, file };
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // remote, no fallback
+ ["widget.windows.utility_process_file_picker", 2],
+ ],
+ });
+});
+
+function makeTask(description, Describe, action) {
+ let task = async function () {
+ if (await fileDialogProcessExists()) {
+ // If this test proceeds, it will probably cause whatever other test has a
+ // file dialog open to fail.
+ //
+ // (We shouldn't be running two such tests in parallel on the same Fx
+ // instance, but that's not obvious at this level.)
+ ok(false, "another test has a file dialog open; aborting");
+ return;
+ }
+
+ const { process, file } = openFileDialog();
+ const pid = await process;
+ const untilDead = untilChildProcessDead(pid);
+
+ info(Describe + " the file-dialog utility process");
+ await action();
+
+ // the file-picker's callback should have been promptly cancelled
+ const _before = Date.now();
+ await file;
+ const _after = Date.now();
+ const delta = _after - _before;
+ info(`file callback resolved after ${description} after ${delta}ms`);
+
+ // depending on the test configuration, this may take some time while
+ // cleanup occurs
+ await untilDead;
+ };
+
+ // give this task a legible name
+ Object.defineProperty(task, "name", {
+ value: "testFileDialogProcess-" + Describe.replace(" ", ""),
+ });
+
+ return task;
+}
+
+for (let [description, Describe, action] of [
+ ["crash", "Crash", () => crashSomeUtilityActor("windowsFileDialog")],
+ [
+ "being killed",
+ "Kill",
+ () => cleanUtilityProcessShutdown("windowsFileDialog", true),
+ ],
+ // Unfortunately, a controlled shutdown doesn't actually terminate the utility
+ // process; the file dialog remains open. (This is expected to be resolved with
+ // bug 1837008.)
+ /* [
+ "shutdown",
+ "Shut down",
+ () => cleanUtilityProcessShutdown("windowsFileDialog"),
+ ] */
+]) {
+ add_task(makeTask(description, Describe, action));
+ add_task(testCleanup);
+}
+
+async function testCleanup() {
+ const killFileDialogProcess = async () => {
+ if (await tryGetUtilityPid("windowsFileDialog", { quiet: true })) {
+ await cleanUtilityProcessShutdown("windowsFileDialog", true);
+ return true;
+ }
+ return false;
+ };
+
+ // If a test failure occurred, the file dialog process may or may not already
+ // exist...
+ if (await killFileDialogProcess()) {
+ console.warn("File dialog process found and killed");
+ return;
+ }
+
+ // ... and if not, may or may not be pending creation.
+ info("Active file dialog process not found; waiting...");
+ try {
+ await untilFileDialogProcessExists({ maxTime: 1000 });
+ } catch (e) {
+ info("File dialog process not found during cleanup (as expected)");
+ return;
+ }
+ await killFileDialogProcess();
+ console.warn("Delayed file dialog process found and killed");
+}
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..107cd2e234
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_multipleAudio.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head-multiple.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/ipc/glue/test/browser/head-multiple.js",
+ this
+);
+
+add_setup(async function checkAudioDecodingNonUtility() {
+ const isAudioDecodingNonUtilityAllowed = await SpecialPowers.getBoolPref(
+ "media.allow-audio-non-utility"
+ );
+ ok(
+ !isAudioDecodingNonUtilityAllowed,
+ "Audio decoding should not be allowed on non utility processes by default"
+ );
+});
+
+add_task(async function testAudioDecodingInUtility() {
+ await runTest({ expectUtility: true });
+});
+
+add_task(async function testFailureAudioDecodingInRDD() {
+ await runTest({ expectUtility: false, expectError: true });
+});
+
+add_task(async function testFailureAudioDecodingInContent() {
+ const platform = Services.appinfo.OS;
+ if (platform === "WINNT") {
+ ok(
+ true,
+ "Manually skipping on Windows because of gfx killing us, cf browser.ini"
+ );
+ return;
+ }
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.rdd-process.enabled", false]],
+ });
+ await runTest({ expectUtility: false, expectRDD: false, expectError: true });
+});
diff --git a/ipc/glue/test/browser/browser_utility_multipleAudio_fallback.js b/ipc/glue/test/browser/browser_utility_multipleAudio_fallback.js
new file mode 100644
index 0000000000..cbebd08287
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_multipleAudio_fallback.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head-multiple.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/ipc/glue/test/browser/head-multiple.js",
+ this
+);
+
+add_setup(async function checkAudioDecodingNonUtility() {
+ const isAudioDecodingNonUtilityAllowed = await SpecialPowers.getBoolPref(
+ "media.allow-audio-non-utility"
+ );
+ ok(isAudioDecodingNonUtilityAllowed, "Audio decoding has been allowed");
+});
+
+add_task(async function testFallbackAudioDecodingInRDD() {
+ await runTest({ expectUtility: false, expectError: false });
+});
diff --git a/ipc/glue/test/browser/browser_utility_multipleAudio_fallback_content.js b/ipc/glue/test/browser/browser_utility_multipleAudio_fallback_content.js
new file mode 100644
index 0000000000..f07a29985f
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_multipleAudio_fallback_content.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head-multiple.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/ipc/glue/test/browser/head-multiple.js",
+ this
+);
+
+add_setup(async function checkAudioDecodingNonUtility() {
+ const isAudioDecodingNonUtilityAllowed = await SpecialPowers.getBoolPref(
+ "media.allow-audio-non-utility"
+ );
+ ok(isAudioDecodingNonUtilityAllowed, "Audio decoding has been allowed");
+});
+
+add_task(async function testFallbackAudioDecodingInContent() {
+ await runTest({ expectContent: true });
+});
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-multiple.js b/ipc/glue/test/browser/head-multiple.js
new file mode 100644
index 0000000000..0ab098448e
--- /dev/null
+++ b/ipc/glue/test/browser/head-multiple.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head.js */
+
+async function runTest({
+ expectUtility = false,
+ expectRDD = false,
+ expectContent = false,
+ expectError = false,
+}) {
+ info(`Running tests with decoding from somewhere`);
+ info(` expectUtility: ${expectUtility}`);
+ info(` expectRDD: ${expectRDD}`);
+ info(` expectContent: ${expectContent}`);
+
+ // 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 && !expectContent && !expectError
+ ? expectation.process
+ : "RDD",
+ expectation.decoder,
+ expectContent,
+ false, // expectJava
+ expectError
+ )
+ );
+ }
+
+ 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);
+ }
+}
diff --git a/ipc/glue/test/browser/head-telemetry.js b/ipc/glue/test/browser/head-telemetry.js
new file mode 100644
index 0000000000..46841347fa
--- /dev/null
+++ b/ipc/glue/test/browser/head-telemetry.js
@@ -0,0 +1,269 @@
+/* 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} [${process}, ${codecNames}, ${extra}]`,
+ kInterval,
+ kRetries
+ );
+}
+
+async function runTest({
+ expectUtility = false,
+ expectRDD = false,
+ expectError = false,
+}) {
+ info(
+ `Running tests with decoding from Utility or RDD: expectUtility=${expectUtility} expectRDD=${expectRDD} expectError=${expectError}`
+ );
+
+ 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,
+ false,
+ expectError
+ );
+
+ info("Stop tab");
+ await stop(tab);
+
+ info("Remove tab");
+ await BrowserTestUtils.removeTab(tab);
+ }
+}
+
+async function runTestWithEME() {
+ info(`Running tests with decoding from Utility for EME`);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["media.utility-process.enabled", true],
+ ["toolkit.telemetry.ipcBatchTimeout", 0],
+ ],
+ });
+
+ const platform = Services.appinfo.OS;
+
+ for (let { src, expectations } of audioTestDataEME()) {
+ if (!(platform in expectations)) {
+ info(`Skipping ${src} for ${platform}`);
+ continue;
+ }
+
+ const expectation = expectations[platform];
+
+ info(`Add EME media tab`);
+ let tab = await addMediaTabWithEME(src.sourceBuffer, src.audioFile);
+
+ info("Play tab");
+ await play(
+ tab,
+ expectation.process,
+ expectation.decoder,
+ false, // expectContent
+ false, // expectJava
+ false, // expectError
+ true // withEME
+ );
+
+ 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}]`
+ );
+ }
+ });
+}
+
+function getExtraKey({ utilityPref, rddPref, allowNonUtility }) {
+ let extraKey = "";
+ if (!rddPref) {
+ extraKey += ",rdd-disabled";
+ }
+ if (!utilityPref) {
+ extraKey += ",utility-disabled";
+ }
+ // TODO: This needs to be removed when getting rid of ability to decode on
+ // non utility at all
+ if (allowNonUtility) {
+ extraKey += ",allow-non-utility";
+ }
+ return extraKey;
+}
diff --git a/ipc/glue/test/browser/head.js b/ipc/glue/test/browser/head.js
new file mode 100644
index 0000000000..8acff88273
--- /dev/null
+++ b/ipc/glue/test/browser/head.js
@@ -0,0 +1,562 @@
+/* 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, options = {}) {
+ let procInfos = (await ChromeUtils.requestProcInfo()).children.filter(p => {
+ return (
+ p.type === "utility" &&
+ (actor == undefined ||
+ p.utilityActors.find(a => a.actorName.startsWith(actor)))
+ );
+ });
+
+ if (!options?.quiet) {
+ info(`Utility process infos = ${JSON.stringify(procInfos)}`);
+ }
+ return procInfos;
+}
+
+async function tryGetUtilityPid(actor, options = {}) {
+ let process = await getUtilityProcesses(actor, options);
+ if (!options?.quiet) {
+ ok(process.length <= 1, `at most one ${actor} process exists`);
+ }
+ return process[0]?.pid;
+}
+
+async function checkUtilityExists(actor) {
+ info(`Looking for a running ${actor} utility process`);
+ const utilityPid = await tryGetUtilityPid(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 tryGetUtilityPid(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: "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",
+ },
+ },
+ },
+ {
+ src: "small-shot.mp3",
+ 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",
+ },
+ },
+ },
+ {
+ 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",
+ },
+ },
+ },
+ ];
+}
+
+function audioTestDataEME() {
+ return [
+ {
+ src: {
+ audioFile:
+ "https://example.com/browser/ipc/glue/test/browser/short-aac-encrypted-audio.mp4",
+ sourceBuffer: "audio/mp4",
+ },
+ expectations: {
+ Linux: {
+ process: "Utility Generic",
+ decoder: "ffmpeg audio decoder",
+ },
+ WINNT: {
+ process: "Utility WMF",
+ decoder: "wmf audio decoder",
+ },
+ Darwin: {
+ process: "Utility AppleMedia",
+ decoder: "apple coremedia 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 addMediaTabWithEME(sourceBuffer, audioFile) {
+ const tab = BrowserTestUtils.addTab(
+ gBrowser,
+ "https://example.com/browser/",
+ {
+ forceNewProcess: true,
+ }
+ );
+ const browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+ await SpecialPowers.spawn(
+ browser,
+ [sourceBuffer, audioFile],
+ createAudioElementEME
+ );
+ return tab;
+}
+
+async function play(
+ tab,
+ expectUtility,
+ expectDecoder,
+ expectContent = false,
+ expectJava = false,
+ expectError = false,
+ withEME = false
+) {
+ let browser = tab.linkedBrowser;
+ return SpecialPowers.spawn(
+ browser,
+ [
+ expectUtility,
+ expectDecoder,
+ expectContent,
+ expectJava,
+ expectError,
+ withEME,
+ ],
+ 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 createAudioElementEME(sourceBuffer, audioFile) {
+ // Helper to clone data into content so the EME helper can use the data.
+ function cloneIntoContent(data) {
+ return Cu.cloneInto(data, content.wrappedJSObject);
+ }
+
+ // Load the EME helper into content.
+ Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/ipc/glue/test/browser/eme_standalone.js",
+ content
+ );
+
+ let audio = content.document.createElement("audio");
+ audio.setAttribute("controls", "true");
+ audio.setAttribute("loop", true);
+ audio.setAttribute("_sourceBufferType", sourceBuffer);
+ audio.setAttribute("_audioUrl", audioFile);
+ content.document.body.appendChild(audio);
+
+ let emeHelper = new content.wrappedJSObject.EmeHelper();
+ emeHelper.SetKeySystem(
+ content.wrappedJSObject.EmeHelper.GetClearkeyKeySystemString()
+ );
+ emeHelper.SetInitDataTypes(cloneIntoContent(["keyids", "cenc"]));
+ emeHelper.SetAudioCapabilities(
+ cloneIntoContent([{ contentType: 'audio/mp4; codecs="mp4a.40.2"' }])
+ );
+ emeHelper.AddKeyIdAndKey(
+ "2cdb0ed6119853e7850671c3e9906c3c",
+ "808B9ADAC384DE1E4F56140F4AD76194"
+ );
+ emeHelper.onerror = error => {
+ is(false, `Got unexpected error from EME helper: ${error}`);
+ };
+ await emeHelper.ConfigureEme(audio);
+ // Done setting up EME.
+}
+
+async function checkAudioDecoder(
+ expectedProcess,
+ expectedDecoder,
+ expectContent = false,
+ expectJava = false,
+ expectError = false,
+ withEME = 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
+ (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("error", async err => {
+ info(
+ `Received HTML media error: ${audio.error.code}: ${audio.error.message}`
+ );
+ if (expectError) {
+ const w = typeof content !== "undefined" ? content.window : window;
+ ok(
+ audio.error.code === w.MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED ||
+ w.MediaError.MEDIA_ERR_DECODE,
+ "Media supported but decoding failed"
+ );
+ resolve();
+ } else {
+ info(`Unexpected error`);
+ reject();
+ }
+ });
+
+ audio.addEventListener("canplaythrough", startPlaybackHandler, {
+ once: true,
+ });
+ });
+
+ if (!withEME) {
+ // We need to make sure the decoder is ready before play()ing otherwise we
+ // could get into bad situations
+ audio.load();
+ } else {
+ // For EME we need to create and load content ourselves. We do this here
+ // because if we do it in createAudioElementEME() above then we end up
+ // with events fired before we get a chance to listen to them here
+ async function once(target, name) {
+ return new Promise(r => target.addEventListener(name, r, { once: true }));
+ }
+
+ // Setup MSE.
+ const ms = new content.wrappedJSObject.MediaSource();
+ audio.src = content.wrappedJSObject.URL.createObjectURL(ms);
+ await once(ms, "sourceopen");
+ const sb = ms.addSourceBuffer(audio.getAttribute("_sourceBufferType"));
+ let fetchResponse = await content.fetch(audio.getAttribute("_audioUrl"));
+ let dataBuffer = await fetchResponse.arrayBuffer();
+ sb.appendBuffer(dataBuffer);
+ await once(sb, "updateend");
+ ms.endOfStream();
+ await once(ms, "sourceended");
+ }
+
+ return checkPromise;
+}
+
+async function runMochitestUtilityAudio(
+ src,
+ {
+ expectUtility,
+ expectDecoder,
+ expectContent = false,
+ expectJava = false,
+ expectError = 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,
+ expectError
+ );
+
+ 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);
+}
+
+function isNightlyOnly() {
+ const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+ );
+ return AppConstants.NIGHTLY_BUILD;
+}
diff --git a/ipc/glue/test/browser/mochitest_audio_off.toml b/ipc/glue/test/browser/mochitest_audio_off.toml
new file mode 100644
index 0000000000..d174ea3939
--- /dev/null
+++ b/ipc/glue/test/browser/mochitest_audio_off.toml
@@ -0,0 +1,12 @@
+[DEFAULT]
+run-if = ["os == '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.toml b/ipc/glue/test/browser/mochitest_audio_on.toml
new file mode 100644
index 0000000000..908f4005f1
--- /dev/null
+++ b/ipc/glue/test/browser/mochitest_audio_on.toml
@@ -0,0 +1,12 @@
+[DEFAULT]
+run-if = ["os == '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..671bdea5de
--- /dev/null
+++ b/ipc/glue/test/browser/moz.build
@@ -0,0 +1,15 @@
+# -*- 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.toml",
+ "browser_audio_fallback.toml",
+ "browser_audio_fallback_content.toml",
+ "browser_audio_locked.toml",
+ "browser_audio_shutdown.toml",
+ "browser_child_hang.toml",
+]
+MOCHITEST_MANIFESTS += ["mochitest_audio_off.toml", "mochitest_audio_on.toml"]
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..619cfaf11d
--- /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, expectError: true });
+ } catch (ex) {
+ ok(false, "Failure");
+ }
+ }
+
+ for (let src of [
+ "small-shot.m4a",
+ ]) {
+ try {
+ await runMochitestUtilityAudio(src, { expectUtility: "", expectDecoder: "android decoder", expectContent: false, expectJava: true, expectError: false });
+ } 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>