summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/tests/mochitests/test_ondevicechange.html
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webrtc/tests/mochitests/test_ondevicechange.html')
-rw-r--r--dom/media/webrtc/tests/mochitests/test_ondevicechange.html180
1 files changed, 180 insertions, 0 deletions
diff --git a/dom/media/webrtc/tests/mochitests/test_ondevicechange.html b/dom/media/webrtc/tests/mochitests/test_ondevicechange.html
new file mode 100644
index 0000000000..4358d9d748
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_ondevicechange.html
@@ -0,0 +1,180 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<script type="application/javascript">
+"use strict";
+
+createHTML({
+ title: "ondevicechange tests",
+ bug: "1152383"
+});
+
+async function resolveOnEvent(target, name) {
+ return new Promise(r => target.addEventListener(name, r, {once: true}));
+}
+let eventCount = 0;
+async function triggerVideoDevicechange() {
+ ++eventCount;
+ // "media.getusermedia.fake-camera-name" specifies the name of the single
+ // fake video camera.
+ // Changing the pref imitates replacing one device with another.
+ return pushPrefs(["media.getusermedia.fake-camera-name",
+ `devicechange ${eventCount}`])
+}
+function addIframe() {
+ const iframe = document.createElement("iframe");
+ // Workaround for bug 1743933
+ iframe.loadPromise = resolveOnEvent(iframe, "load");
+ document.documentElement.appendChild(iframe);
+ return iframe;
+}
+
+runTest(async () => {
+ // A toplevel Window and an iframe Windows are compared for devicechange
+ // events.
+ const iframe1 = addIframe();
+ const iframe2 = addIframe();
+ await Promise.all([
+ iframe1.loadPromise,
+ iframe2.loadPromise,
+ pushPrefs(
+ // Use the fake video backend to trigger devicechange events.
+ ["media.navigator.streams.fake", true],
+ // Loopback would override fake.
+ ["media.video_loopback_dev", ""],
+ // Make fake devices count as real, permission-wise, or devicechange
+ // events won't be exposed
+ ["media.navigator.permission.fake", true],
+ // For gUM.
+ ["media.navigator.permission.disabled", true]
+ ),
+ ]);
+ const topDevices = navigator.mediaDevices;
+ const frame1Devices = iframe1.contentWindow.navigator.mediaDevices;
+ const frame2Devices = iframe2.contentWindow.navigator.mediaDevices;
+ // Initialization of MediaDevices::mLastPhysicalDevices is triggered when
+ // ondevicechange is set but tests "media.getusermedia.fake-camera-name"
+ // asynchronously. Wait for getUserMedia() completion to ensure that the
+ // pref has been read before doDevicechanges() changes it.
+ frame1Devices.ondevicechange = () => {};
+ const topEventPromise = resolveOnEvent(topDevices, "devicechange");
+ const frame2EventPromise = resolveOnEvent(frame2Devices, "devicechange");
+ (await frame1Devices.getUserMedia({video: true})).getTracks()[0].stop();
+
+ await Promise.all([
+ resolveOnEvent(frame1Devices, "devicechange"),
+ triggerVideoDevicechange(),
+ ]);
+ ok(true,
+ "devicechange event is fired when gUM has been in use");
+ // The number of devices has not changed. Race a settled Promise to check
+ // that no devicechange event has been received in frame2.
+ const racer = {};
+ is(await Promise.race([frame2EventPromise, racer]), racer,
+ "devicechange event is NOT fired in iframe2 for replaced device when " +
+ "gUM has NOT been in use");
+ // getUserMedia() is invoked on frame2Devices after a first device list
+ // change but before returning to the previous state, in order to test that
+ // the device set is compared with the set after previous device list
+ // changes regardless of whether a "devicechange" event was previously
+ // dispatched.
+ (await frame2Devices.getUserMedia({video: true})).getTracks()[0].stop();
+ // Revert device list change.
+ await Promise.all([
+ resolveOnEvent(frame1Devices, "devicechange"),
+ resolveOnEvent(frame2Devices, "devicechange"),
+ SpecialPowers.popPrefEnv(),
+ ]);
+ ok(true,
+ "devicechange event is fired on return to previous list " +
+ "after gUM has been is use");
+
+ const frame1EventPromise1 = resolveOnEvent(frame1Devices, "devicechange");
+ while (true) {
+ const racePromise = Promise.race([
+ frame1EventPromise1,
+ // 100ms is half the coalescing time in MediaManager::DeviceListChanged().
+ wait(100, {type: "wait done"}),
+ ]);
+ await triggerVideoDevicechange();
+ if ((await racePromise).type == "devicechange") {
+ ok(true,
+ "devicechange event is fired even when hardware changes continue");
+ break;
+ }
+ }
+
+ is(await Promise.race([topEventPromise, racer]), racer,
+ "devicechange event is NOT fired for device replacements when " +
+ "gUM has NOT been in use");
+
+ if (navigator.userAgent.includes("Android")) {
+ todo(false, "test assumes Firefox-for-Desktop specific API and behavior");
+ return;
+ }
+ // Open a new tab, which is expected to receive focus and hide the first tab.
+ const tab = window.open();
+ SimpleTest.registerCleanupFunction(() => tab.close());
+ await Promise.all([
+ resolveOnEvent(document, 'visibilitychange'),
+ resolveOnEvent(tab, 'focus'),
+ ]);
+ ok(tab.document.hasFocus(), "tab.document.hasFocus()");
+ await Promise.all([
+ resolveOnEvent(tab, 'blur'),
+ SpecialPowers.spawnChrome([], function focusUrlBar() {
+ this.browsingContext.topChromeWindow.gURLBar.focus();
+ }),
+ ]);
+ ok(!tab.document.hasFocus(), "!tab.document.hasFocus()");
+ is(document.visibilityState, 'hidden', 'visibilityState')
+ const frame1EventPromise2 = resolveOnEvent(frame1Devices, "devicechange");
+ const tabDevices = tab.navigator.mediaDevices;
+ tabDevices.ondevicechange = () => {};
+ const tabStream = await tabDevices.getUserMedia({video: true});
+ // Trigger and await two devicechanges on tabDevices to wait long enough to
+ // provide that a devicechange on another MediaDevices would be received.
+ for (let i = 0; i < 2; ++i) {
+ await Promise.all([
+ resolveOnEvent(tabDevices, "devicechange"),
+ triggerVideoDevicechange(),
+ ]);
+ };
+ is(await Promise.race([frame1EventPromise2, racer]), racer,
+ "devicechange event is NOT fired while tab is in background");
+ tab.close();
+ await resolveOnEvent(document, 'visibilitychange');
+ is(document.visibilityState, 'visible', 'visibilityState')
+ await frame1EventPromise2;
+ ok(true, "devicechange event IS fired when tab returns to foreground");
+
+ const audioLoopbackDev =
+ SpecialPowers.getCharPref("media.audio_loopback_dev", "");
+ if (!navigator.userAgent.includes("Linux")) {
+ todo_isnot(audioLoopbackDev, "", "audio_loopback_dev");
+ return;
+ }
+ isnot(audioLoopbackDev, "", "audio_loopback_dev");
+ await Promise.all([
+ resolveOnEvent(topDevices, "devicechange"),
+ pushPrefs(["media.audio_loopback_dev", "none"]),
+ ]);
+ ok(true,
+ "devicechange event IS fired when last audio device is removed and " +
+ "gUM has NOT been in use");
+ await Promise.all([
+ resolveOnEvent(topDevices, "devicechange"),
+ pushPrefs(["media.audio_loopback_dev", audioLoopbackDev]),
+ ]);
+ ok(true,
+ "devicechange event IS fired when first audio device is added and " +
+ "gUM has NOT been in use");
+});
+
+</script>
+</body>
+</html>