diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /ipc/glue/test | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ipc/glue/test')
32 files changed, 2145 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> diff --git a/ipc/glue/test/gtest/TestAsyncBlockers.cpp b/ipc/glue/test/gtest/TestAsyncBlockers.cpp new file mode 100644 index 0000000000..6f8d298621 --- /dev/null +++ b/ipc/glue/test/gtest/TestAsyncBlockers.cpp @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "gtest/gtest.h" + +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/ipc/AsyncBlockers.h" +#include "mozilla/gtest/MozHelpers.h" + +#include "nsCOMPtr.h" +#include "nsITimer.h" +#include "nsINamed.h" + +using namespace mozilla; +using namespace mozilla::ipc; + +#define PROCESS_EVENTS_UNTIL(_done) \ + SpinEventLoopUntil("TestAsyncBlockers"_ns, [&]() { return _done; }); + +class TestAsyncBlockers : public ::testing::Test { + protected: + void SetUp() override { + SAVE_GDB_SLEEP(mOldSleepDuration); + return; + } + + void TearDown() final { RESTORE_GDB_SLEEP(mOldSleepDuration); } + + private: +#if defined(HAS_GDB_SLEEP_DURATION) + unsigned int mOldSleepDuration = 0; +#endif // defined(HAS_GDB_SLEEP_DURATION) +}; + +class Blocker {}; + +TEST_F(TestAsyncBlockers, Register) { + AsyncBlockers blockers; + Blocker* blocker = new Blocker(); + blockers.Register(blocker); + EXPECT_TRUE(true); +} + +TEST_F(TestAsyncBlockers, Register_Deregister) { + AsyncBlockers blockers; + Blocker* blocker = new Blocker(); + blockers.Register(blocker); + blockers.Deregister(blocker); + EXPECT_TRUE(true); +} + +TEST_F(TestAsyncBlockers, Register_WaitUntilClear) { + AsyncBlockers blockers; + bool done = false; + + Blocker* blocker = new Blocker(); + blockers.Register(blocker); + + blockers.WaitUntilClear(5 * 1000)->Then(GetCurrentSerialEventTarget(), + __func__, [&]() { + EXPECT_TRUE(true); + done = true; + }); + + NS_ProcessPendingEvents(nullptr); + + blockers.Deregister(blocker); + + PROCESS_EVENTS_UNTIL(done); +} + +class AsyncBlockerTimerCallback : public nsITimerCallback, public nsINamed { + protected: + virtual ~AsyncBlockerTimerCallback(); + + public: + explicit AsyncBlockerTimerCallback() {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED +}; + +NS_IMPL_ISUPPORTS(AsyncBlockerTimerCallback, nsITimerCallback, nsINamed) + +AsyncBlockerTimerCallback::~AsyncBlockerTimerCallback() = default; + +NS_IMETHODIMP +AsyncBlockerTimerCallback::Notify(nsITimer* timer) { + // If we resolve through this, it means + // blockers.WaitUntilClear() started to wait for + // the completion of the timeout which is not + // good. + EXPECT_TRUE(false); + return NS_OK; +} + +NS_IMETHODIMP +AsyncBlockerTimerCallback::GetName(nsACString& aName) { + aName.AssignLiteral("AsyncBlockerTimerCallback"); + return NS_OK; +} + +TEST_F(TestAsyncBlockers, NoRegister_WaitUntilClear) { + AsyncBlockers blockers; + bool done = false; + + nsCOMPtr<nsITimer> timer = NS_NewTimer(); + ASSERT_TRUE(timer); + + RefPtr<AsyncBlockerTimerCallback> timerCb = new AsyncBlockerTimerCallback(); + timer->InitWithCallback(timerCb, 1 * 1000, nsITimer::TYPE_ONE_SHOT); + + blockers.WaitUntilClear(10 * 1000)->Then(GetCurrentSerialEventTarget(), + __func__, [&]() { + // If we resolve through this + // before the nsITimer it means we + // have been resolved before the 5s + // timeout + EXPECT_TRUE(true); + timer->Cancel(); + done = true; + }); + + PROCESS_EVENTS_UNTIL(done); +} + +TEST_F(TestAsyncBlockers, Register_WaitUntilClear_0s) { + AsyncBlockers blockers; + bool done = false; + + Blocker* blocker = new Blocker(); + blockers.Register(blocker); + + blockers.WaitUntilClear(0)->Then(GetCurrentSerialEventTarget(), __func__, + [&]() { + EXPECT_TRUE(true); + done = true; + }); + + NS_ProcessPendingEvents(nullptr); + + blockers.Deregister(blocker); + + PROCESS_EVENTS_UNTIL(done); +} + +#if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && !defined(ANDROID) && \ + !(defined(XP_DARWIN) && !defined(MOZ_DEBUG)) +static void DeregisterEmpty_Test() { + mozilla::gtest::DisableCrashReporter(); + + AsyncBlockers blockers; + Blocker* blocker = new Blocker(); + blockers.Deregister(blocker); +} + +TEST_F(TestAsyncBlockers, DeregisterEmpty) { + ASSERT_DEATH_IF_SUPPORTED(DeregisterEmpty_Test(), ""); +} +#endif // defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && !defined(ANDROID) && + // !(defined(XP_DARWIN) && !defined(MOZ_DEBUG)) + +#undef PROCESS_EVENTS_UNTIL diff --git a/ipc/glue/test/gtest/TestUtilityProcess.cpp b/ipc/glue/test/gtest/TestUtilityProcess.cpp new file mode 100644 index 0000000000..c5d19c992f --- /dev/null +++ b/ipc/glue/test/gtest/TestUtilityProcess.cpp @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "gtest/gtest.h" +#include "mozilla/SpinEventLoopUntil.h" + +#include "mozilla/ipc/UtilityProcessManager.h" + +#if defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) +# include "nsIAppShellService.h" +# include "nsServiceManagerUtils.h" +#endif // defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) + +#if defined(XP_WIN) +# include "mozilla/gtest/MozHelpers.h" +# include "mozilla/ipc/UtilityProcessImpl.h" +#endif // defined(XP_WIN) + +#ifdef MOZ_WIDGET_ANDROID +# define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/widget/appshell/android;1" +#endif // MOZ_WIDGET_ANDROID + +#ifdef XP_MACOSX +# define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/widget/appshell/mac;1" +#endif // XP_MACOSX + +using namespace mozilla; +using namespace mozilla::ipc; + +#define WAIT_FOR_EVENTS \ + SpinEventLoopUntil("UtilityProcess::emptyUtil"_ns, [&]() { return done; }); + +bool setupDone = false; + +class UtilityProcess : public ::testing::Test { + protected: + void SetUp() override { + if (setupDone) { + return; + } + +#if defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) + appShell = do_GetService(NS_APPSHELLSERVICE_CONTRACTID); +#endif // defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + mozilla::SandboxBroker::GeckoDependentInitialize(); +#endif // defined(XP_WIN) && defined(MOZ_SANDBOX) + + setupDone = true; + } + +#if defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) + nsCOMPtr<nsIAppShellService> appShell; +#endif // defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) +}; + +TEST_F(UtilityProcess, ProcessManager) { + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + ASSERT_NE(utilityProc, nullptr); +} + +TEST_F(UtilityProcess, NoProcess) { + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + EXPECT_NE(utilityProc, nullptr); + + Maybe<int32_t> noPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + ASSERT_TRUE(noPid.isNothing()); +} + +TEST_F(UtilityProcess, LaunchProcess) { + bool done = false; + + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + EXPECT_NE(utilityProc, nullptr); + + int32_t thisPid = base::GetCurrentProcId(); + EXPECT_GE(thisPid, 1); + + utilityProc->LaunchProcess(SandboxingKind::GENERIC_UTILITY) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&]() mutable { + EXPECT_TRUE(true); + + Maybe<int32_t> utilityPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + EXPECT_TRUE(utilityPid.isSome()); + EXPECT_GE(*utilityPid, 1); + EXPECT_NE(*utilityPid, thisPid); + + printf_stderr("UtilityProcess running as %d\n", *utilityPid); + + done = true; + }, + [&](nsresult aError) mutable { + EXPECT_TRUE(false); + done = true; + }); + + WAIT_FOR_EVENTS; +} + +TEST_F(UtilityProcess, DestroyProcess) { + bool done = false; + + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + + utilityProc->LaunchProcess(SandboxingKind::GENERIC_UTILITY) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&]() { + Maybe<int32_t> utilityPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + EXPECT_TRUE(utilityPid.isSome()); + EXPECT_GE(*utilityPid, 1); + + utilityProc->CleanShutdown(SandboxingKind::GENERIC_UTILITY); + + utilityPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + EXPECT_TRUE(utilityPid.isNothing()); + + EXPECT_TRUE(true); + done = true; + }, + [&](nsresult aError) { + EXPECT_TRUE(false); + done = true; + }); + + WAIT_FOR_EVENTS; +} + +#if defined(XP_WIN) +static void LoadLibraryCrash_Test() { + mozilla::gtest::DisableCrashReporter(); + // Just a uuidgen name to have something random + UtilityProcessImpl::LoadLibraryOrCrash( + L"2b49036e-6ba3-400c-a297-38fa1f6c5255.dll"); +} + +TEST_F(UtilityProcess, LoadLibraryCrash) { + ASSERT_DEATH_IF_SUPPORTED(LoadLibraryCrash_Test(), ""); +} +#endif // defined(XP_WIN) + +#undef WAIT_FOR_EVENTS diff --git a/ipc/glue/test/gtest/moz.build b/ipc/glue/test/gtest/moz.build new file mode 100644 index 0000000000..7b72cc7fe1 --- /dev/null +++ b/ipc/glue/test/gtest/moz.build @@ -0,0 +1,21 @@ +# -*- Mode: python; c-basic-offset: 4; 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/. + +Library("ipcgluetest") + +UNIFIED_SOURCES = [ + "TestAsyncBlockers.cpp", + "TestUtilityProcess.cpp", +] + +LOCAL_INCLUDES += [ + "/widget", + "/widget/android", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" diff --git a/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.cpp b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.cpp new file mode 100644 index 0000000000..3828c6971b --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.cpp @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#if defined(ENABLE_TESTS) +# include "mozilla/ipc/UtilityProcessTest.h" +# include "mozilla/ipc/UtilityProcessManager.h" +# include "mozilla/dom/Promise.h" +# include "mozilla/ProcInfo.h" +# include "mozilla/IntentionalCrash.h" + +namespace mozilla::ipc { + +static UtilityActorName UtilityActorNameFromString( + const nsACString& aStringName) { + using namespace mozilla::dom; + + // We use WebIDLUtilityActorNames because UtilityActorNames is not designed + // for iteration. + for (size_t i = 0; i < WebIDLUtilityActorNameValues::Count; ++i) { + auto idlName = static_cast<UtilityActorName>(i); + const nsDependentCSubstring idlNameString( + WebIDLUtilityActorNameValues::GetString(idlName)); + if (idlNameString.Equals(aStringName)) { + return idlName; + } + } + MOZ_CRASH("Unknown utility actor name"); +} + +// Find the utility process with the given actor or any utility process if +// the actor is UtilityActorName::EndGuard_. +static SandboxingKind FindUtilityProcessWithActor(UtilityActorName aActorName) { + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + MOZ_ASSERT(utilityProc, "No UtilityprocessManager?"); + + for (size_t i = 0; i < SandboxingKind::COUNT; ++i) { + auto sbKind = static_cast<SandboxingKind>(i); + if (!utilityProc->Process(sbKind)) { + continue; + } + if (aActorName == UtilityActorName::EndGuard_) { + return sbKind; + } + for (auto actor : utilityProc->GetActors(sbKind)) { + if (actor == aActorName) { + return sbKind; + } + } + } + + return SandboxingKind::COUNT; +} + +NS_IMETHODIMP +UtilityProcessTest::StartProcess(const nsTArray<nsCString>& aActorsToRegister, + JSContext* aCx, + mozilla::dom::Promise** aOutPromise) { + NS_ENSURE_ARG(aOutPromise); + *aOutPromise = nullptr; + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + + ErrorResult erv; + RefPtr<dom::Promise> promise = dom::Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return erv.StealNSResult(); + } + + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + MOZ_ASSERT(utilityProc, "No UtilityprocessManager?"); + + auto actors = aActorsToRegister.Clone(); + + utilityProc->LaunchProcess(SandboxingKind::GENERIC_UTILITY) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [promise, utilityProc, actors = std::move(actors)] { + RefPtr<UtilityProcessParent> utilityParent = + utilityProc->GetProcessParent(SandboxingKind::GENERIC_UTILITY); + Maybe<int32_t> utilityPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + for (size_t i = 0; i < actors.Length(); ++i) { + auto uan = UtilityActorNameFromString(actors[i]); + utilityProc->RegisterActor(utilityParent, uan); + } + if (utilityPid.isSome()) { + promise->MaybeResolve(*utilityPid); + } else { + promise->MaybeReject(NS_ERROR_NOT_AVAILABLE); + } + }, + [promise](nsresult aError) { + MOZ_ASSERT_UNREACHABLE( + "UtilityProcessTest; failure to get Utility process"); + promise->MaybeReject(aError); + }); + + promise.forget(aOutPromise); + return NS_OK; +} + +NS_IMETHODIMP +UtilityProcessTest::NoteIntentionalCrash(uint32_t aPid) { + mozilla::NoteIntentionalCrash("utility", aPid); + return NS_OK; +} + +NS_IMETHODIMP +UtilityProcessTest::StopProcess(const char* aActorName) { + using namespace mozilla::dom; + + SandboxingKind sbKind; + if (aActorName) { + const nsDependentCString actorStringName(aActorName); + UtilityActorName actorName = UtilityActorNameFromString(actorStringName); + sbKind = FindUtilityProcessWithActor(actorName); + } else { + sbKind = FindUtilityProcessWithActor(UtilityActorName::EndGuard_); + } + + if (sbKind == SandboxingKind::COUNT) { + MOZ_ASSERT_UNREACHABLE( + "Attempted to stop process for actor when no " + "such process exists"); + return NS_ERROR_FAILURE; + } + + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + MOZ_ASSERT(utilityProc, "No UtilityprocessManager?"); + + utilityProc->CleanShutdown(sbKind); + Maybe<int32_t> utilityPid = utilityProc->ProcessPid(sbKind); + MOZ_RELEASE_ASSERT(utilityPid.isNothing(), + "Should not have a utility process PID anymore"); + + return NS_OK; +} + +NS_IMETHODIMP +UtilityProcessTest::TestTelemetryProbes() { + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + MOZ_ASSERT(utilityProc, "No UtilityprocessManager?"); + + for (RefPtr<UtilityProcessParent>& parent : + utilityProc->GetAllProcessesProcessParent()) { + Unused << parent->SendTestTelemetryProbes(); + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(UtilityProcessTest, nsIUtilityProcessTest) + +} // namespace mozilla::ipc +#endif // defined(ENABLE_TESTS) diff --git a/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.h b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.h new file mode 100644 index 0000000000..6c80fce71b --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ +#ifndef _include_ipc_glue_UtilityProcessTest_h_ +#define _include_ipc_glue_UtilityProcessTest_h_ + +#if defined(ENABLE_TESTS) +# include "nsServiceManagerUtils.h" +# include "nsIUtilityProcessTest.h" + +namespace mozilla::ipc { + +class UtilityProcessTest final : public nsIUtilityProcessTest { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUTILITYPROCESSTEST + + UtilityProcessTest() = default; + + private: + ~UtilityProcessTest() = default; +}; + +} // namespace mozilla::ipc +#endif // defined(ENABLE_TESTS) + +#endif // _include_ipc_glue_UtilityProcessTest_h_ diff --git a/ipc/glue/test/utility_process_xpcom/components.conf b/ipc/glue/test/utility_process_xpcom/components.conf new file mode 100644 index 0000000000..25208ba7fc --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/components.conf @@ -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/. + +Classes = [ + { + 'cid': '{0a4478f4-c5ae-4fb1-8686-d5b09fb99afb}', + 'contract_ids': ['@mozilla.org/utility-process-test;1'], + 'type': 'mozilla::ipc::UtilityProcessTest', + 'headers': ['mozilla/ipc/UtilityProcessTest.h'], + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, +] diff --git a/ipc/glue/test/utility_process_xpcom/moz.build b/ipc/glue/test/utility_process_xpcom/moz.build new file mode 100644 index 0000000000..f04b436cbe --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/moz.build @@ -0,0 +1,21 @@ +# -*- 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/. + +EXPORTS.mozilla.ipc += ["UtilityProcessTest.h"] + +UNIFIED_SOURCES += ["UtilityProcessTest.cpp"] + +XPCOM_MANIFESTS += ["components.conf"] + +XPIDL_MODULE = "utility_process_xpcom_test" + +XPIDL_SOURCES += [ + "nsIUtilityProcessTest.idl", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/ipc/glue/test/utility_process_xpcom/nsIUtilityProcessTest.idl b/ipc/glue/test/utility_process_xpcom/nsIUtilityProcessTest.idl new file mode 100644 index 0000000000..839c4f3673 --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/nsIUtilityProcessTest.idl @@ -0,0 +1,44 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(0a4478f4-c5ae-4fb1-8686-d5b09fb99afb)] +interface nsIUtilityProcessTest : nsISupports +{ + /** + * ** Test-only Method ** + * + * Allowing to start Utility Process from JS code. + * + * actorsToAdd: An array of actor names, taken from WebIDLUtilityActorName. + * Unlike normal utility processes, test processes launched this way do not + * have any associated actor names unless specified here. Empty by default. + */ + [implicit_jscontext] + Promise startProcess([optional] in Array<ACString> actorsToAdd); + + /** + * ** Test-only Method ** + * + * Note that we are going to manually crash a process + */ + void noteIntentionalCrash(in unsigned long pid); + + /** + * ** Test-only Method ** + * + * Allowing to stop Utility Process from JS code. + * Default behavior is to stop any utility process. + */ + void stopProcess([optional] in string utilityActorName); + + /** + * ** Test-only Method ** + * + * Sending Telemetry probes + */ + void testTelemetryProbes(); +}; |