180 lines
6.6 KiB
HTML
180 lines
6.6 KiB
HTML
<!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 IceCat-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>
|