Skip to content

Commit

Permalink
Bug 1528042 - Add successful gUM calls to existing WPT and mochitests…
Browse files Browse the repository at this point in the history
… to let them continue to test full device information exposure in enumerateDevices(). r=karlt

This is required by spec. See
w3c/mediacapture-main#641 and
w3c/mediacapture-main#773 for details.

Also fixes test_enumerateDevices_getUserMediaFake.html to run on macOS outside automation.

Differential Revision: https://phabricator.services.mozilla.com/D154302

UltraBlame original commit: 173f133fb868ce50a9feec4e26496be7f7aeae11
  • Loading branch information
marco-c committed May 31, 2023
1 parent c7887b8 commit ecaa941
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 68 deletions.
36 changes: 21 additions & 15 deletions dom/media/webrtc/tests/mochitests/test_enumerateDevices.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@
}
}

var gUM = c => navigator.mediaDevices.getUserMedia(c);
const gUM = c => navigator.mediaDevices.getUserMedia(c);

var validateDevice = ({kind, label, deviceId, groupId}) => {
ok(kind == "videoinput" || kind == "audioinput", "Known device kind");
const kinds = ["videoinput", "audioinput", "audiooutput"];

function validateDevice({kind, label, deviceId, groupId}) {
ok(kinds.includes(kind), "Known device kind");
is(deviceId.length, 44, "deviceId length id as expected for Firefox");
ok(label.length !== undefined, "Device label: " + label);
isnot(groupId, "", "groupId must be present.");
Expand All @@ -44,20 +46,24 @@
runTest(async () => {
await pushPrefs(["media.navigator.streams.fake", true]);

// Validate enumerated devices.
// Validate enumerated devices after gUM.
for (const track of (await gUM({video: true, audio: true})).getTracks()) {
track.stop();
}

let devices = await navigator.mediaDevices.enumerateDevices();
ok(devices.length, "At least one device found");
let jsoned = JSON.parse(JSON.stringify(devices));
const jsoned = JSON.parse(JSON.stringify(devices));
is(jsoned[0].kind, devices[0].kind, "kind survived serializer");
is(jsoned[0].deviceId, devices[0].deviceId, "deviceId survived serializer");
for (let device of devices) {
for (const device of devices) {
validateDevice(device);
if (device.kind == "audiooutput") continue;
// Test deviceId constraint
let deviceId = device.deviceId;
let constraints = (device.kind == "videoinput") ? { video: { deviceId } }
: { audio: { deviceId } };
for (let track of (await gUM(constraints)).getTracks()) {
for (const track of (await gUM(constraints)).getTracks()) {
is(typeof(track.label), "string", "Track label is a string");
is(track.label, device.label, "Track label is the device label");
track.stop();
Expand Down Expand Up @@ -85,8 +91,8 @@
const origins = ["https://example.com", "https://test1.example.com"];
info(window.location);

let haveDevicesMap = new Promise(resolve => {
let map = new Map();
const haveDevicesMap = new Promise(resolve => {
const map = new Map();
window.addEventListener("message", ({origin, data}) => {
ok(origins.includes(origin), "Got message from expected origin");
map.set(origin, JSON.parse(data));
Expand All @@ -96,25 +102,25 @@
});

await Promise.all(origins.map(origin => {
let iframe = document.createElement("iframe");
const iframe = document.createElement("iframe");
iframe.src = origin + path;
iframe.allow = "camera;microphone";
iframe.allow = "camera;microphone;speaker-selection";
info(iframe.src);
document.documentElement.appendChild(iframe);
return new Promise(resolve => iframe.onload = resolve);
}));
let devicesMap = await haveDevicesMap;
let [sameOriginDevices, differentOriginDevices] = origins.map(o => devicesMap.get(o));

is(sameOriginDevices.length, devices.length);
is(differentOriginDevices.length, devices.length);
is(sameOriginDevices.length, devices.length, "same origin same devices");
is(differentOriginDevices.length, devices.length, "cross origin same devices");
[...sameOriginDevices, ...differentOriginDevices].forEach(d => validateDevice(d));

for (let device of sameOriginDevices) {
for (const device of sameOriginDevices) {
ok(devices.find(d => d.deviceId == device.deviceId),
"Same origin deviceId for " + device.label + " must match");
}
for (let device of differentOriginDevices) {
for (const device of differentOriginDevices) {
ok(!devices.find(d => d.deviceId == device.deviceId),
"Different origin deviceId for " + device.label + " must be different");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"use strict";

createHTML({
title: "Test enumerateDevices() after fake getUserMedia()",
title: "Test labeled devices or speakers aren't exposed in enumerateDevices() after fake getUserMedia()",
bug: "1743524"
});

Expand All @@ -24,44 +24,38 @@
// `fake:true` means that getUserMedia() resolves without any permission
// check, and so this should not be sufficient to expose real device info.
const stream = await devices.getUserMedia({ audio: true, fake: true });
const list = await devices.enumerateDevices();
stream.getTracks()[0].stop();
const list = await devices.enumerateDevices();
const labeledDevices = list.filter(({label}) => label != "");
is(labeledDevices.length, 0, "number of labeled devices after fake gUM");
is(labeledDevices.length, 0, "must be zero labeled devices after fake gUM");
const outputDevices = list.filter(({kind}) => kind == "audiooutput");
is(outputDevices.length, 0, "number of output devices after fake gUM");
is(outputDevices.length, 0, "must be zero output devices after fake gUM");
}
{
// Check without `fake:true` to verify assumptions about existing devices.
const streamPromise = devices.getUserMedia({ audio: true });
if (navigator.userAgent.includes("Mac OS X")) {
let rejection = "resolved";
try {
await streamPromise;
} catch (e) {
rejection = e.name;
let stream;
try {
stream = await devices.getUserMedia({ audio: true });
stream.getTracks()[0].stop();
} catch (e) {
if (e.name == "NotFoundError" &&
navigator.userAgent.includes("Mac OS X")) {
todo(false, "Expecting no real audioinput device on Mac test machines");
return;
}
todo_isnot(rejection, "NotFoundError",
"Expecting no real audioinput device on Mac.");
return;
throw e;
}
const stream = await streamPromise;
{
const list = await devices.enumerateDevices();
// input labels disappear when the track is stopped - bug 1528042
const unlabeledAudioDevices =
list.filter(({ kind, label }) => {
return kind != "videoinput" && label == ""
});
const audioDevices = list.filter(({kind}) => kind.includes("audio"));
ok(audioDevices.length, "have audio devices after real gUM");
const unlabeledAudioDevices = audioDevices.filter(({label}) => !label);
is(unlabeledAudioDevices.length, 0,
"number of unlabeled audio devices after real gUM");
"must be zero unlabeled audio devices after real gUM");

const outputDevices = list.filter(({kind}) => kind == "audiooutput");
isnot(outputDevices.length, 0, "have output devices after real gUM");
}
stream.getTracks()[0].stop();
const list = await devices.enumerateDevices();
const outputDevices = list.filter(({ kind, label }) => {
return kind == "audiooutput" && label != "";
});
isnot(outputDevices.length, 0, "number of output devices after real gUM");
}
});
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@
Runs inside iframe in test_enumerateDevices.html.
*/

var pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
var gUM = c => navigator.mediaDevices.getUserMedia(c);
const pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
const gUM = c => navigator.mediaDevices.getUserMedia(c);

(async () => {
await pushPrefs(["media.navigator.streams.fake", true]);

let devices = await navigator.mediaDevices.enumerateDevices();
// Validate enumerated devices after gUM.
for (const track of (await gUM({video: true, audio: true})).getTracks()) {
track.stop();
}

const devices = await navigator.mediaDevices.enumerateDevices();
parent.postMessage(JSON.stringify(devices), "https://example.com:443");

})().catch(e => setTimeout(() => { throw e; }));
Expand Down
13 changes: 9 additions & 4 deletions dom/media/webrtc/tests/mochitests/test_groupId.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
<script type="application/javascript">
createHTML({ title: "Test group id of MediaDeviceInfo", bug: "1213453" });

let getDefaultDevices = async () => {
let devices = await navigator.mediaDevices.enumerateDevices();
async function getDefaultDevices() {
const devices = await navigator.mediaDevices.enumerateDevices();
is(devices.length, 2, "Two fake devices found.");

devices.forEach(d => isnot(d.groupId, "", "GroupId is included in every device"));

let videos = devices.filter(d => d.kind == "videoinput");
const videos = devices.filter(d => d.kind == "videoinput");
is(videos.length, 1, "One video device found.");
let audios = devices.filter(d => d.kind == "audioinput");
const audios = devices.filter(d => d.kind == "audioinput");
is(audios.length, 1, "One microphone device found.");

return {audio: audios[0], video: videos[0]};
Expand All @@ -28,6 +28,11 @@
["media.audio_loopback_dev", ""],
["media.video_loopback_dev", ""]);

const afterGum = await navigator.mediaDevices.getUserMedia({
video: true, audio: true
});
afterGum.getTracks().forEach(track => track.stop());

let {audio, video} = await getDefaultDevices();

/* The low level method to correlate groupIds is by device names.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
assert_equals(device1.deviceId, "", "deviceId is empty before capture");
assert_equals(device1.groupId, "", "groupId is empty before capture");
assert_equals(device1.label, "", "label is empty before capture");
assert_in_array(device1.kind, ["audioinput", "audiooutput", "videoinput", "kind is set to a valid value before capture"]);
assert_in_array(device1.kind, ["audioinput", "audiooutput", "videoinput"],
"kind is set to a valid value before capture");
}
}
/* Additionally, at most one device of each kind
Expand All @@ -52,8 +53,8 @@
}, testName);
}

doTest(false, "enumerateDevices returns expected mostly empty objects in case device-info permission is not granted");
doTest(true, "enumerateDevices returns expected objects in case device-info permission is granted");
doTest(false, "enumerateDevices exposes mostly empty objects ahead of successful getUserMedia call");
doTest(true, "enumerateDevices exposes expected objects after successful getUserMedia call");
</script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ <h1 class="instructions">Description</h1>
promise_test(async t => {
// Both permissions are needed at some point, asking both at once
await setMediaPermission();
// A successful camera gUM call is needed to expose camera information
const afterGum = await navigator.mediaDevices.getUserMedia({video: true});
afterGum.getTracks()[0].stop();

assert_true(navigator.mediaDevices.getSupportedConstraints()["groupId"],
"groupId should be supported");
const devices = await navigator.mediaDevices.enumerateDevices();
Expand All @@ -66,6 +70,10 @@ <h1 class="instructions">Description</h1>
}, 'groupId is correctly supported by getUserMedia() for video devices');

promise_test(async t => {
// A successful microphone gUM call is needed to expose microphone information
const afterGum = await navigator.mediaDevices.getUserMedia({audio: true});
afterGum.getTracks()[0].stop();

assert_true(navigator.mediaDevices.getSupportedConstraints()["groupId"],
"groupId should be supported");
const devices = await navigator.mediaDevices.enumerateDevices();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,25 @@
}, 'A device can be opened twice with different resolutions requested');

promise_test(async t => {
// getUserMedia needs to be called before deviceIds and groupIds are exposed
const afterGum = await navigator.mediaDevices.getUserMedia({
video: true, audio: true
});
afterGum.getTracks().forEach(track => track.stop());

const devices = await navigator.mediaDevices.enumerateDevices();
const inputDevices = devices.filter(d => d.kind != "audiooutput");
assert_greater_than(inputDevices.length, 0);
for (const device of inputDevices) {
const device_id_constraint = {deviceId: {exact: device.deviceId}};
const constraints = device.kind == "audioinput"
? {audio: device_id_constraint}
: {video: device_id_constraint};

const stream = await navigator.mediaDevices.getUserMedia(constraints);
assert_true(stream.getTracks()[0].getSettings().groupId === device.groupId, "device groupId");
assert_greater_than(device.groupId.length, 0);
const inputDevices = devices.filter(({kind}) => kind != "audiooutput");
assert_greater_than(inputDevices.length, 1, "have at least 2 test devices");
for (const {kind, deviceId, groupId} of inputDevices) {
const type = {videoinput: "video", audioinput: "audio"}[kind];
const stream = await navigator.mediaDevices.getUserMedia({
[type]: {deviceId: {exact: deviceId}}
});
const [track] = stream.getTracks();
const settings = track.getSettings();
track.stop();
assert_true(settings.groupId == groupId, "device groupId");
assert_greater_than(settings.groupId.length, 0, "groupId is not empty");
}
}, 'groupId is correctly reported by getSettings() for all input devices');

Expand Down

0 comments on commit ecaa941

Please sign in to comment.