diff options
Diffstat (limited to 'dom/media/webrtc/tests/mochitests/test_ondevicechange.html')
-rw-r--r-- | dom/media/webrtc/tests/mochitests/test_ondevicechange.html | 180 |
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> |