Backed out 10 changesets (bug 1528042) for causing build bustages on MediaDevices.cpp.

Backed out changeset 04d9fa0993ab (bug 1528042)
Backed out changeset a20768227ca6 (bug 1528042)
Backed out changeset 94066f37c5a0 (bug 1528042)
Backed out changeset cb2b433b47bd (bug 1528042)
Backed out changeset d0bfd185f0e7 (bug 1528042)
Backed out changeset 59b9147c85f0 (bug 1528042)
Backed out changeset e864d7f810b3 (bug 1528042)
Backed out changeset c0373264c89b (bug 1528042)
Backed out changeset cf689822deaf (bug 1528042)
Backed out changeset 0ac40a8f40a0 (bug 1528042)
This commit is contained in:
Iulian Moraru 2023-05-16 09:35:29 +03:00
parent f92a6be70e
commit fd6836d5a1
31 changed files with 207 additions and 660 deletions

View File

@ -1321,15 +1321,11 @@ void Navigator::MozGetUserMedia(const MediaStreamConstraints& aConstraints,
NavigatorUserMediaErrorCallback& aOnError,
CallerType aCallerType, ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread());
if (!mWindow || !mWindow->IsFullyActive()) {
aRv.ThrowInvalidStateError("The document is not fully active.");
return;
}
GetMediaDevices(aRv);
if (aRv.Failed()) {
return;
}
MOZ_ASSERT(mMediaDevices);
if (Document* doc = mWindow->GetExtantDoc()) {
if (!mWindow->IsSecureContext()) {
doc->SetUseCounter(eUseCounter_custom_MozGetUserMediaInsec);
@ -1343,7 +1339,7 @@ void Navigator::MozGetUserMedia(const MediaStreamConstraints& aConstraints,
"audio and/or video is required"),
__func__);
} else {
sp = mMediaDevices->GetUserMedia(mWindow, aConstraints, aCallerType);
sp = MediaManager::Get()->GetUserMedia(mWindow, aConstraints, aCallerType);
}
RefPtr<NavigatorUserMediaSuccessCallback> onsuccess(&aOnSuccess);
RefPtr<NavigatorUserMediaErrorCallback> onerror(&aOnError);

View File

@ -98,32 +98,7 @@ already_AddRefed<Promise> MediaDevices::GetUserMedia(
}
}
}
RefPtr<MediaDevices> self(this);
GetUserMedia(owner, aConstraints, aCallerType)
->Then(
GetCurrentSerialEventTarget(), __func__,
[this, self, p](RefPtr<DOMMediaStream>&& aStream) {
if (!GetWindowIfCurrent()) {
return; // Leave Promise pending after navigation by design.
}
p->MaybeResolve(std::move(aStream));
},
[this, self, p](const RefPtr<MediaMgrError>& error) {
nsPIDOMWindowInner* window = GetWindowIfCurrent();
if (!window) {
return; // Leave Promise pending after navigation by design.
}
error->Reject(p);
});
return p.forget();
}
RefPtr<MediaDevices::StreamPromise> MediaDevices::GetUserMedia(
nsPIDOMWindowInner* aWindow, const MediaStreamConstraints& aConstraints,
CallerType aCallerType) {
MOZ_ASSERT(NS_IsMainThread());
bool haveFake = aConstraints.mFake.WasPassed() && aConstraints.mFake.Value();
const OwningBooleanOrMediaTrackConstraints& video = aConstraints.mVideo;
const OwningBooleanOrMediaTrackConstraints& audio = aConstraints.mAudio;
bool isMicrophone =
!haveFake &&
@ -135,26 +110,32 @@ RefPtr<MediaDevices::StreamPromise> MediaDevices::GetUserMedia(
(video.IsBoolean()
? video.GetAsBoolean()
: !video.GetAsMediaTrackConstraints().mMediaSource.WasPassed());
RefPtr<MediaDevices> self(this);
return MediaManager::Get()
->GetUserMedia(aWindow, aConstraints, aCallerType)
MediaManager::Get()
->GetUserMedia(owner, aConstraints, aCallerType)
->Then(
GetCurrentSerialEventTarget(), __func__,
[this, self, isMicrophone,
[this, self, p, isMicrophone,
isCamera](RefPtr<DOMMediaStream>&& aStream) {
if (!GetWindowIfCurrent()) {
return; // Leave Promise pending after navigation by design.
}
if (isMicrophone) {
mCanExposeMicrophoneInfo = true;
}
if (isCamera) {
mCanExposeCameraInfo = true;
}
return StreamPromise::CreateAndResolve(std::move(aStream),
__func__);
p->MaybeResolve(std::move(aStream));
},
[](RefPtr<MediaMgrError>&& aError) {
return StreamPromise::CreateAndReject(std::move(aError), __func__);
[this, self, p](const RefPtr<MediaMgrError>& error) {
nsPIDOMWindowInner* window = GetWindowIfCurrent();
if (!window) {
return; // Leave Promise pending after navigation by design.
}
error->Reject(p);
});
return p.forget();
}
already_AddRefed<Promise> MediaDevices::EnumerateDevices(ErrorResult& aRv) {
@ -257,7 +238,6 @@ RefPtr<MediaDeviceSetRefCnt> MediaDevices::FilterExposedDevices(
// they are exposed only when explicitly and individually allowed by the
// user.
}
bool legacy = StaticPrefs::media_devices_enumerate_legacy_enabled();
bool outputIsDefault = true; // First output is the default.
bool haveDefaultOutput = false;
nsTHashSet<nsString> exposedMicrophoneGroupIds;
@ -270,17 +250,13 @@ RefPtr<MediaDeviceSetRefCnt> MediaDevices::FilterExposedDevices(
if (mCanExposeMicrophoneInfo) {
exposedMicrophoneGroupIds.Insert(device->mRawGroupID);
}
if (!mCanExposeMicrophoneInfo && !legacy) {
dropMics = true;
}
// Reducing to one mic or cam device when not mCanExposeMicrophoneInfo
// or not mCanExposeCameraInfo is bug 1528042.
break;
case MediaDeviceKind::Videoinput:
if (dropCams) {
continue;
}
if (!mCanExposeCameraInfo && !legacy) {
dropCams = true;
}
break;
case MediaDeviceKind::Audiooutput:
if (dropSpeakers ||
@ -325,23 +301,6 @@ RefPtr<MediaDeviceSetRefCnt> MediaDevices::FilterExposedDevices(
return exposed;
}
bool MediaDevices::CanExposeInfo(MediaDeviceKind aKind) const {
switch (aKind) {
case MediaDeviceKind::Audioinput:
return mCanExposeMicrophoneInfo;
case MediaDeviceKind::Videoinput:
return mCanExposeCameraInfo;
case MediaDeviceKind::Audiooutput:
// Assumes caller has used FilterExposedDevices()
return true;
case MediaDeviceKind::EndGuard_:
MOZ_ASSERT_UNREACHABLE("unexpected MediaDeviceKind");
return false;
// Avoid `default:` so that `-Wswitch` catches missing enumerators at
// compile time.
}
};
bool MediaDevices::ShouldQueueDeviceChange(
const MediaDeviceSet& aExposedDevices) const {
if (!mLastPhysicalDevices) { // SetupDeviceChangeListener not complete
@ -358,6 +317,22 @@ bool MediaDevices::ShouldQueueDeviceChange(
// exposed by enumerateDevices() (but multiple devices are currently exposed
// - bug 1528042). "devicechange" events are not queued when the number
// of such devices changes but remains non-zero.
auto CanExposeNonZeroChanges = [this](MediaDeviceKind aKind) {
switch (aKind) {
case MediaDeviceKind::Audioinput:
return mCanExposeMicrophoneInfo;
case MediaDeviceKind::Videoinput:
return mCanExposeCameraInfo;
case MediaDeviceKind::Audiooutput:
return true;
case MediaDeviceKind::EndGuard_:
break;
// Avoid `default:` so that `-Wswitch` catches missing enumerators at
// compile time.
}
MOZ_ASSERT_UNREACHABLE("unexpected MediaDeviceKind");
return false;
};
while (exposed < exposedEnd && last < lastEnd) {
// First determine whether there is at least one device of the same kind
// in both `aExposedDevices` and `lastExposedDevices`.
@ -369,7 +344,7 @@ bool MediaDevices::ShouldQueueDeviceChange(
return true;
}
// `exposed` and `last` have matching kind.
if (CanExposeInfo(kind)) {
if (CanExposeNonZeroChanges(kind)) {
// Queue "devicechange" if there has been any change in devices of this
// exposed kind. ID and kind uniquely identify a device.
if ((*exposed)->mRawID != (*last)->mRawID) {
@ -424,16 +399,37 @@ void MediaDevices::ResumeEnumerateDevices(
void MediaDevices::ResolveEnumerateDevicesPromise(
Promise* aPromise, const LocalMediaDeviceSet& aDevices) const {
nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
auto windowId = window->WindowID();
nsTArray<RefPtr<MediaDeviceInfo>> infos;
bool legacy = StaticPrefs::media_devices_enumerate_legacy_enabled();
bool allowLabel =
aDevices.Length() == 0 ||
MediaManager::Get()->IsActivelyCapturingOrHasAPermission(windowId);
for (const RefPtr<LocalMediaDevice>& device : aDevices) {
bool exposeInfo = CanExposeInfo(device->Kind()) || legacy;
bool exposeLabel = legacy ? DeviceInformationCanBeExposed() : exposeInfo;
infos.AppendElement(MakeRefPtr<MediaDeviceInfo>(
exposeInfo ? device->mID : u""_ns, device->Kind(),
exposeLabel ? device->mName : u""_ns,
exposeInfo ? device->mGroupID : u""_ns));
nsString label;
MOZ_ASSERT(device->Kind() < MediaDeviceKind::EndGuard_);
switch (device->Kind()) {
case MediaDeviceKind::Audioinput:
case MediaDeviceKind::Videoinput:
// Include name only if page currently has a gUM stream
// active or persistent permissions (audio or video) have
// been granted. See bug 1528042 for using
// mCanExposeMicrophoneInfo.
if (allowLabel || Preferences::GetBool(
"media.navigator.permission.disabled", false)) {
label = device->mName;
}
break;
case MediaDeviceKind::Audiooutput:
label = device->mName;
break;
case MediaDeviceKind::EndGuard_:
break;
// Avoid `default:` so that `-Wswitch` catches missing
// enumerators at compile time.
}
infos.AppendElement(MakeRefPtr<MediaDeviceInfo>(device->mID, device->Kind(),
label, device->mGroupID));
}
aPromise->MaybeResolve(std::move(infos));
}
@ -788,10 +784,6 @@ void MediaDevices::EventListenerAdded(nsAtom* aType) {
SetupDeviceChangeListener();
}
bool MediaDevices::DeviceInformationCanBeExposed() const {
return mCanExposeCameraInfo || mCanExposeMicrophoneInfo;
}
JSObject* MediaDevices::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return MediaDevices_Binding::Wrap(aCx, this, aGivenProto);

View File

@ -11,7 +11,6 @@
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/UseCounter.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/MediaDeviceInfoBinding.h"
#include "nsCOMPtr.h"
#include "nsID.h"
#include "nsISupports.h"
@ -24,7 +23,6 @@ namespace mozilla {
class LocalMediaDevice;
class MediaDevice;
class MediaMgrError;
class DOMMediaStream;
template <typename ResolveValueT, typename RejectValueT, bool IsExclusive>
class MozPromise;
@ -43,8 +41,6 @@ struct AudioOutputOptions;
class MediaDevices final : public DOMEventTargetHelper {
public:
using StreamPromise =
MozPromise<RefPtr<DOMMediaStream>, RefPtr<MediaMgrError>, true>;
using SinkInfoPromise = MozPromise<RefPtr<AudioDeviceInfo>, nsresult, true>;
explicit MediaDevices(nsPIDOMWindowInner* aWindow);
@ -62,10 +58,6 @@ class MediaDevices final : public DOMEventTargetHelper {
const MediaStreamConstraints& aConstraints, CallerType aCallerType,
ErrorResult& aRv);
RefPtr<StreamPromise> GetUserMedia(nsPIDOMWindowInner* aWindow,
const MediaStreamConstraints& aConstraints,
CallerType aCallerType);
already_AddRefed<Promise> EnumerateDevices(ErrorResult& aRv);
already_AddRefed<Promise> GetDisplayMedia(
@ -111,14 +103,10 @@ class MediaDevices final : public DOMEventTargetHelper {
RefPtr<const MediaDeviceSetRefCnt> aExposedDevices) const;
RefPtr<MediaDeviceSetRefCnt> FilterExposedDevices(
const MediaDeviceSet& aDevices) const;
bool CanExposeInfo(MediaDeviceKind aKind) const;
bool ShouldQueueDeviceChange(const MediaDeviceSet& aExposedDevices) const;
void ResolveEnumerateDevicesPromise(
Promise* aPromise, const LocalMediaDeviceSet& aDevices) const;
// See https://www.w3.org/TR/mediacapture-streams/#device-information-exposure
bool DeviceInformationCanBeExposed() const;
nsTHashSet<nsString> mExplicitlyGrantedAudioOutputRawIds;
nsTArray<RefPtr<Promise>> mPendingEnumerateDevicesPromises;
// Set only once, if and when required.

View File

@ -1869,8 +1869,15 @@ RefPtr<MediaManager::DeviceSetPromise> MediaManager::EnumerateRawDevices(
Maybe<MediaDeviceSet> speakers;
RefPtr devices = new MediaDeviceSetRefCnt();
// Enumerate microphones first, then cameras, then speakers, since the
// enumerateDevices() algorithm expects them listed in that order.
if (hasVideo) {
videoBackend = hasFakeCams ? fakeBackend : realBackend;
MediaDeviceSet videos;
LOG("EnumerateRawDevices Task: Getting video sources with %s backend",
videoBackend == fakeBackend ? "fake" : "real");
GetMediaDevices(videoBackend, aVideoInputType, videos,
videoLoopDev.get());
devices->AppendElements(videos);
}
if (hasAudio) {
audioBackend = hasFakeMics ? fakeBackend : realBackend;
MediaDeviceSet audios;
@ -1885,15 +1892,6 @@ RefPtr<MediaManager::DeviceSetPromise> MediaManager::EnumerateRawDevices(
}
devices->AppendElements(audios);
}
if (hasVideo) {
videoBackend = hasFakeCams ? fakeBackend : realBackend;
MediaDeviceSet videos;
LOG("EnumerateRawDevices Task: Getting video sources with %s backend",
videoBackend == fakeBackend ? "fake" : "real");
GetMediaDevices(videoBackend, aVideoInputType, videos,
videoLoopDev.get());
devices->AppendElements(videos);
}
if (hasAudioOutput) {
MediaDeviceSet outputs;
MOZ_ASSERT(realBackend);

View File

@ -175,9 +175,9 @@ function addLoadEvent() {}
/* import-globals-from /testing/mochitest/tests/SimpleTest/SimpleTest.js */
/* import-globals-from head.js */
const scriptsReady = Promise.all(
var scriptsReady = Promise.all(
["/tests/SimpleTest/SimpleTest.js", "head.js"].map(script => {
const el = document.createElement("script");
var el = document.createElement("script");
el.src = script;
document.head.appendChild(el);
return new Promise(r => (el.onload = r));
@ -189,12 +189,7 @@ function createHTML(options) {
}
async function runTest(testFunction) {
await Promise.all([
scriptsReady,
SpecialPowers.pushPrefEnv({
set: [["media.navigator.permission.fake", true]],
}),
]);
await scriptsReady;
await runTestWhenReady(async (...args) => {
await testFunction(...args);
await noGum();
@ -203,39 +198,22 @@ async function runTest(testFunction) {
// noGum - Helper to detect whether active guM tracks still exist.
//
// Note it relies on the permissions system to detect active tracks, so it won't
// catch getUserMedia use while media.navigator.permission.disabled is true
// (which is common in automation), UNLESS we set
// media.navigator.permission.fake to true also, like runTest() does above.
// It relies on the fact that, by spec, device labels from enumerateDevices are
// only visible during active gum calls. They're also visible when persistent
// permissions are granted, so turn off media.navigator.permission.disabled
// (which is normally on otherwise in our tests). Lastly, we must turn on
// media.navigator.permission.fake otherwise fake devices don't count as active.
async function noGum() {
await pushPrefs(
["media.navigator.permission.disabled", false],
["media.navigator.permission.fake", true]
);
if (!navigator.mediaDevices) {
// No mediaDevices, then gUM cannot have been called either.
return;
}
const mediaManagerService = Cc[
"@mozilla.org/mediaManagerService;1"
].getService(Ci.nsIMediaManagerService);
const hasCamera = {};
const hasMicrophone = {};
mediaManagerService.mediaCaptureWindowState(
window,
hasCamera,
hasMicrophone,
{},
{},
{},
{},
false
);
is(
hasCamera.value,
mediaManagerService.STATE_NOCAPTURE,
"Test must leave no active camera gUM tracks behind."
);
is(
hasMicrophone.value,
mediaManagerService.STATE_NOCAPTURE,
"Test must leave no active microphone gUM tracks behind."
);
const [device] = await navigator.mediaDevices.enumerateDevices();
if (device) {
is(device.label, "", "Test must leave no active gUM streams behind.");
}
}

View File

@ -12,7 +12,6 @@ support-files =
stats.js
templates.js
test_enumerateDevices_iframe.html
test_enumerateDevices_iframe_pre_gum.html
test_getUserMedia_permission_iframe.html
NetworkPreparationChromeScript.js
blacksilence.js
@ -43,7 +42,6 @@ skip-if =
scheme=http
[test_enumerateDevices.html]
[test_enumerateDevices_getUserMediaFake.html]
[test_enumerateDevices_legacy.html]
[test_enumerateDevices_navigation.html]
skip-if = true # Disabled because it is a racy test and causes timeouts, see bug 1650932
[test_fingerprinting_resistance.html]

View File

@ -11,12 +11,9 @@ createHTML({ title: "Run enumerateDevices code", bug: "1046245" });
Tests covering enumerateDevices API and deviceId constraint. Exercise code.
*/
async function mustSucceedWithStream(msg, f) {
async function mustSucceed(msg, f) {
try {
const stream = await f();
for (const track of stream.getTracks()) {
track.stop();
}
await f();
ok(true, msg + " must succeed");
} catch (e) {
is(e.name, null, msg + " must succeed: " + e.message);
@ -35,12 +32,10 @@ async function mustFailWith(msg, reason, constraint, f) {
}
}
const gUM = c => navigator.mediaDevices.getUserMedia(c);
var gUM = c => navigator.mediaDevices.getUserMedia(c);
const kinds = ["videoinput", "audioinput", "audiooutput"];
function validateDevice({kind, label, deviceId, groupId}) {
ok(kinds.includes(kind), "Known device kind");
var validateDevice = ({kind, label, deviceId, groupId}) => {
ok(kind == "videoinput" || kind == "audioinput", "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.");
@ -49,24 +44,20 @@ function validateDevice({kind, label, deviceId, groupId}) {
runTest(async () => {
await pushPrefs(["media.navigator.streams.fake", true]);
// Validate enumerated devices after gUM.
for (const track of (await gUM({video: true, audio: true})).getTracks()) {
track.stop();
}
// Validate enumerated devices.
let devices = await navigator.mediaDevices.enumerateDevices();
ok(devices.length, "At least one device found");
const jsoned = JSON.parse(JSON.stringify(devices));
let 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 (const device of devices) {
for (let 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 (const track of (await gUM(constraints)).getTracks()) {
for (let 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();
@ -77,10 +68,10 @@ runTest(async () => {
// Check deviceId failure paths for video.
await mustSucceedWithStream("unknown plain deviceId on video",
() => gUM({ video: { deviceId: unknownId } }));
await mustSucceedWithStream("unknown plain deviceId on audio",
() => gUM({ audio: { deviceId: unknownId } }));
await mustSucceed("unknown plain deviceId on video",
() => gUM({ video: { deviceId: unknownId } }));
await mustSucceed("unknown plain deviceId on audio",
() => gUM({ audio: { deviceId: unknownId } }));
await mustFailWith("unknown exact deviceId on video",
"OverconstrainedError", "deviceId",
() => gUM({ video: { deviceId: { exact: unknownId } } }));
@ -94,8 +85,8 @@ runTest(async () => {
const origins = ["https://example.com", "https://test1.example.com"];
info(window.location);
const haveDevicesMap = new Promise(resolve => {
const map = new Map();
let haveDevicesMap = new Promise(resolve => {
let map = new Map();
window.addEventListener("message", ({origin, data}) => {
ok(origins.includes(origin), "Got message from expected origin");
map.set(origin, JSON.parse(data));
@ -105,9 +96,9 @@ runTest(async () => {
});
await Promise.all(origins.map(origin => {
const iframe = document.createElement("iframe");
let iframe = document.createElement("iframe");
iframe.src = origin + path;
iframe.allow = "camera;microphone;speaker-selection";
iframe.allow = "camera;microphone";
info(iframe.src);
document.documentElement.appendChild(iframe);
return new Promise(resolve => iframe.onload = resolve);
@ -115,15 +106,15 @@ runTest(async () => {
let devicesMap = await haveDevicesMap;
let [sameOriginDevices, differentOriginDevices] = origins.map(o => devicesMap.get(o));
is(sameOriginDevices.length, devices.length, "same origin same devices");
is(differentOriginDevices.length, devices.length, "cross origin same devices");
is(sameOriginDevices.length, devices.length);
is(differentOriginDevices.length, devices.length);
[...sameOriginDevices, ...differentOriginDevices].forEach(d => validateDevice(d));
for (const device of sameOriginDevices) {
for (let device of sameOriginDevices) {
ok(devices.find(d => d.deviceId == device.deviceId),
"Same origin deviceId for " + device.label + " must match");
}
for (const device of differentOriginDevices) {
for (let device of differentOriginDevices) {
ok(!devices.find(d => d.deviceId == device.deviceId),
"Different origin deviceId for " + device.label + " must be different");
}

View File

@ -9,7 +9,7 @@
"use strict";
createHTML({
title: "Test labeled devices or speakers aren't exposed in enumerateDevices() after fake getUserMedia()",
title: "Test enumerateDevices() after fake getUserMedia()",
bug: "1743524"
});
@ -24,38 +24,47 @@ runTest(async () => {
// `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 });
// permission.disabled exposes labels - bug 1528042
const list = await withPrefs(
[["media.navigator.permission.disabled", false]],
async () => devices.enumerateDevices());
stream.getTracks()[0].stop();
const list = await devices.enumerateDevices();
const labeledDevices = list.filter(({label}) => label != "");
is(labeledDevices.length, 0, "must be zero labeled devices after fake gUM");
is(labeledDevices.length, 0, "number of labeled devices after fake gUM");
const outputDevices = list.filter(({kind}) => kind == "audiooutput");
is(outputDevices.length, 0, "must be zero output devices after fake gUM");
is(outputDevices.length, 0, "number of output devices after fake gUM");
}
{
// Check without `fake:true` to verify assumptions about existing devices.
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;
const streamPromise = devices.getUserMedia({ audio: true });
if (navigator.userAgent.includes("Mac OS X")) {
let rejection = "resolved";
try {
await streamPromise;
} catch (e) {
rejection = e.name;
}
throw e;
todo_isnot(rejection, "NotFoundError",
"Expecting no real audioinput device on Mac.");
return;
}
const stream = await streamPromise;
{
const list = await devices.enumerateDevices();
const audioDevices = list.filter(({kind}) => kind.includes("audio"));
ok(audioDevices.length, "have audio devices after real gUM");
const unlabeledAudioDevices = audioDevices.filter(({label}) => !label);
// input labels disappear when the track is stopped - bug 1528042
const unlabeledAudioDevices =
list.filter(({ kind, label }) => {
return kind != "videoinput" && label == ""
});
is(unlabeledAudioDevices.length, 0,
"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");
"number of unlabeled audio 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>

View File

@ -7,18 +7,13 @@
Runs inside iframe in test_enumerateDevices.html.
*/
const pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
const gUM = c => navigator.mediaDevices.getUserMedia(c);
var pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
var gUM = c => navigator.mediaDevices.getUserMedia(c);
(async () => {
await pushPrefs(["media.navigator.streams.fake", true]);
// Validate enumerated devices after gUM.
for (const track of (await gUM({video: true, audio: true})).getTracks()) {
track.stop();
}
const devices = await navigator.mediaDevices.enumerateDevices();
let devices = await navigator.mediaDevices.enumerateDevices();
parent.postMessage(JSON.stringify(devices), "https://example.com:443");
})().catch(e => setTimeout(() => { throw e; }));

View File

@ -1,22 +0,0 @@
<!DOCTYPE HTML>
<html>
<body>
<pre id="test">
<script type="application/javascript">
/**
Runs inside iframe in test_enumerateDevices_legacy.html.
*/
const pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
(async () => {
await pushPrefs(["media.navigator.streams.fake", true]);
const devices = await navigator.mediaDevices.enumerateDevices();
parent.postMessage(JSON.stringify(devices), "https://example.com:443");
})().catch(e => setTimeout(() => { throw e; }));
</script>
</pre>
</body>
</html>

View File

@ -1,147 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<script src="mediaStreamPlayback.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({ title: "Run enumerateDevices code", bug: "1046245" });
/**
This is a modified copy of test_enumerateDevices.html testing the
enumerateDevices() legacy version and deviceId constraint.
*/
async function mustSucceedWithStream(msg, f) {
try {
const stream = await f();
for (const track of stream.getTracks()) {
track.stop();
}
ok(true, msg + " must succeed");
} catch (e) {
is(e.name, null, msg + " must succeed: " + e.message);
}
}
async function mustFailWith(msg, reason, constraint, f) {
try {
await f();
ok(false, msg + " must fail");
} catch(e) {
is(e.name, reason, msg + " must fail: " + e.message);
if (constraint) {
is(e.constraint, constraint, msg + " must fail w/correct constraint.");
}
}
}
const gUM = c => navigator.mediaDevices.getUserMedia(c);
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.");
}
runTest(async () => {
await pushPrefs(["media.navigator.streams.fake", true],
["media.devices.enumerate.legacy.enabled", true]);
// Validate enumerated devices before gUM (legacy).
let devices = await navigator.mediaDevices.enumerateDevices();
ok(devices.length, "At least one device found");
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 (const device of devices) {
validateDevice(device);
if (device.kind == "audiooutput") continue;
is(device.label, "", "Device label is empty");
// Test deviceId constraint
let deviceId = device.deviceId;
let constraints = (device.kind == "videoinput") ? { video: { deviceId } }
: { audio: { deviceId } };
let namedDevices;
for (const track of (await gUM(constraints)).getTracks()) {
is(typeof(track.label), "string", "Track label is a string");
isnot(track.label.length, 0, "Track label is not empty");
if (!namedDevices) {
namedDevices = await navigator.mediaDevices.enumerateDevices();
}
const namedDevice = namedDevices.find(d => d.deviceId == device.deviceId);
is(track.label, namedDevice.label, "Track label is the device label");
track.stop();
}
}
const unknownId = "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=";
// Check deviceId failure paths for video.
await mustSucceedWithStream("unknown plain deviceId on video",
() => gUM({ video: { deviceId: unknownId } }));
await mustSucceedWithStream("unknown plain deviceId on audio",
() => gUM({ audio: { deviceId: unknownId } }));
await mustFailWith("unknown exact deviceId on video",
"OverconstrainedError", "deviceId",
() => gUM({ video: { deviceId: { exact: unknownId } } }));
await mustFailWith("unknown exact deviceId on audio",
"OverconstrainedError", "deviceId",
() => gUM({ audio: { deviceId: { exact: unknownId } } }));
// Check that deviceIds are stable for same origin and differ across origins.
const path = "/tests/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe_pre_gum.html";
const origins = ["https://example.com", "https://test1.example.com"];
info(window.location);
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));
if (map.size < origins.length) return;
resolve(map);
});
});
await Promise.all(origins.map(origin => {
const iframe = document.createElement("iframe");
iframe.src = origin + path;
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, "same origin same devices");
is(differentOriginDevices.length, devices.length, "cross origin same devices");
[...sameOriginDevices, ...differentOriginDevices].forEach(d => validateDevice(d));
for (const device of sameOriginDevices) {
ok(devices.find(d => d.deviceId == device.deviceId),
"Same origin deviceId for " + device.label + " must match");
}
for (const device of differentOriginDevices) {
ok(!devices.find(d => d.deviceId == device.deviceId),
"Different origin deviceId for " + device.label + " must be different");
}
// Check the special case of no devices found.
await pushPrefs(["media.navigator.streams.fake", false],
["media.audio_loopback_dev", "none"],
["media.video_loopback_dev", "none"]);
devices = await navigator.mediaDevices.enumerateDevices();
is(devices.length, 0, "No devices");
});
</script>
</pre>
</body>
</html>

View File

@ -169,9 +169,6 @@
info("Testing screenshare with size and framerate constraints");
SpecialPowers.wrap(document).notifyUserGestureActivation();
for (const track of stream.getTracks()) {
track.stop();
}
stream = await getUserMedia({
video: {
mediaSource: 'screen',
@ -196,8 +193,7 @@
`Height setting ${settings.height} should be correct after gUM (or 0 per bug 1453247)`);
colors = ["green", "red", "white", "blue"];
draw(colors);
const streamClone = stream.clone();
await doVerify(streamClone, colors);
await doVerify(stream.clone(), colors);
settings = stream.getTracks()[0].getSettings();
ok(settings.width >= 10 && settings.width <= 100,
`Width setting ${settings.width} should be within constraints`);
@ -250,9 +246,6 @@
colors = ["white", "green", "blue", "red"];
draw(colors);
await doVerify(stream, colors);
for (const track of [...stream.getTracks(), ...streamClone.getTracks()]) {
track.stop();
}
});
</script>
</pre>

View File

@ -13,10 +13,8 @@
bug: "1294605"
});
runTest(async () => {
await pushPrefs(["media.navigator.permission.fake", true]);
const stream = await getUserMedia({audio: true, video: true, fake: true});
const clone = stream.clone();
runTest(() => getUserMedia({audio: true, video: true}).then(stream => {
let clone = stream.clone();
stream.getTracks().forEach(t => t.stop());
stream.clone().getTracks().forEach(t => stream.addTrack(t));
is(stream.getTracks().filter(t => t.readyState == "live").length, 0,
@ -25,7 +23,7 @@
// Bug 1295352: better to be explicit about noGum here wrt future refactoring.
return noGum();
});
}));
</script>
</pre>
</body>

View File

@ -8,15 +8,15 @@
<script type="application/javascript">
createHTML({ title: "Test group id of MediaDeviceInfo", bug: "1213453" });
async function getDefaultDevices() {
const devices = await navigator.mediaDevices.enumerateDevices();
let getDefaultDevices = async () => {
let 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"));
const videos = devices.filter(d => d.kind == "videoinput");
let videos = devices.filter(d => d.kind == "videoinput");
is(videos.length, 1, "One video device found.");
const audios = devices.filter(d => d.kind == "audioinput");
let audios = devices.filter(d => d.kind == "audioinput");
is(audios.length, 1, "One microphone device found.");
return {audio: audios[0], video: videos[0]};
@ -28,11 +28,6 @@ runTest(async () => {
["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.

View File

@ -10749,23 +10749,6 @@
value: true
mirror: once
# This pref turns on legacy (non-spec) exposure of camera and microphone
# information from enumerateDevices and devicechange ahead of successful
# getUserMedia calls. Should only be turned on to resolve web compat issues,
# since doing so reveals more user fingerprinting information to trackers.
#
# This mode is marginally more permissive than the legacy behavior it attempts
# to emulate in that device labels do not disappear when tracks are stopped and
# temporary permission expires. That behavior isn't emulated due to low relative
# privacy value compared to spec and it not being being conducive to spec
# convergence.
#
# Planned next steps: true → @IS_NOT_NIGHTLY_BUILD@ → false
- name: media.devices.enumerate.legacy.enabled
type: bool
value: true
mirror: always
# WebRTC prefs follow
# Enables RTCPeerConnection support. Note that, when true, this pref enables

View File

@ -1 +1 @@
prefs: [media.navigator.permission.disabled:true, media.setsinkid.enabled:true, media.devices.enumerate.legacy.enabled:false]
prefs: [media.navigator.permission.disabled:true, media.setsinkid.enabled:true]

View File

@ -0,0 +1,5 @@
[MediaDevices-enumerateDevices-persistent-permission.https.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[enumerateDevices depends only on capture state, not permission state]
expected: FAIL

View File

@ -0,0 +1,5 @@
[MediaDevices-enumerateDevices-returned-objects.https.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[enumerateDevices returns expected mostly empty objects in case device-info permission is not granted]
expected: FAIL

View File

@ -3,3 +3,6 @@
if (os == "android") and fission: [OK, TIMEOUT]
[InputDeviceInfo is supported]
expected: FAIL
[mediaDevices.enumerateDevices() is present and working - before capture]
expected: FAIL

View File

@ -1,2 +1,2 @@
prefs: [media.navigator.permission.disabled:true, media.navigator.streams.fake:true, dom.security.featurePolicy.header.enabled:true, dom.security.featurePolicy.webidl.enabled:true,media.devices.enumerate.legacy.enabled:false]
prefs: [media.navigator.permission.disabled:true, media.navigator.streams.fake:true, dom.security.featurePolicy.header.enabled:true, dom.security.featurePolicy.webidl.enabled:true]
lsan-allowed: [NewSegment, mozilla::layers::BufferTextureData::CreateInternal]

View File

@ -1,6 +0,0 @@
[MediaDevices-enumerateDevices.https.html]
prefs: [media.devices.enumerate.legacy.enabled:true]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[InputDeviceInfo is supported]
expected: FAIL

View File

@ -1,110 +0,0 @@
<!doctype html>
<html>
<head>
<title>enumerateDevices: test that enumerateDevices is present (legacy Firefox)</title>
<meta name='assert' content='Check that the enumerateDevices() method is present (legacy Firefox).'/>
</head>
<body>
<h1 class="instructions">Description</h1>
<p class="instructions">This is a modified copy of
testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices.https.html
testing legacy Firefox version of the <code>navigator.mediaDevices.enumerateDevices()</code> method.</p>
<div id='log'></div>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=/resources/testdriver.js></script>
<script src=/resources/testdriver-vendor.js></script>
<script src=permission-helper.js></script>
<script>
"use strict";
promise_test(async () => {
assert_not_equals(navigator.mediaDevices.enumerateDevices, undefined, "navigator.mediaDevices.enumerateDevices exists");
const devices = await navigator.mediaDevices.enumerateDevices();
for (const {kind, deviceId, label, groupId} of devices) {
assert_in_array(kind, ["videoinput", "audioinput", "audiooutput"]);
assert_greater_than(deviceId.length, 0, "deviceId should be present even if getUserMedia was never called successfully (legacy).");
assert_equals(label, "", "label should be empty string if getUserMedia was never called successfully.");
assert_greater_than(groupId.length, 0, "groupId should be present even if getUserMedia was never called successfully (legacy).");
}
assert_less_than_equal(devices.filter(({kind}) => kind == "audioinput").length,
1, "there should be zero or one audio input device.");
assert_less_than_equal(devices.filter(({kind}) => kind == "videoinput").length,
1, "there should be zero or one video input device.");
assert_equals(devices.filter(({kind}) => kind == "audiooutput").length,
0, "there should be no audio output devices.");
assert_less_than_equal(devices.length, 2,
"there should be no more than two devices.");
if (devices.length > 1) {
assert_equals(devices[0].kind, "audioinput", "audioinput is first");
assert_equals(devices[1].kind, "videoinput", "videoinput is second");
}
}, "mediaDevices.enumerateDevices() is present and working - before capture");
promise_test(async t => {
await setMediaPermission("granted");
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
stream.getTracks()[0].stop();
const devices = await navigator.mediaDevices.enumerateDevices();
const kinds = ["audioinput", "videoinput"];
for (const {kind, deviceId} of devices) {
assert_in_array(kind, kinds, "camera doesn't expose audiooutput");
assert_equals(typeof deviceId, "string", "deviceId is a string.");
switch (kind) {
case "videoinput":
assert_greater_than(deviceId.length, 0, "video deviceId should not be empty.");
break;
case "audioinput":
assert_greater_than(deviceId.length, 0, "audio deviceId should not be empty (legacy).");
break;
}
}
}, "mediaDevices.enumerateDevices() is working - after video capture");
// This test is designed to come after its video counterpart directly above
promise_test(async t => {
const devices1 = await navigator.mediaDevices.enumerateDevices();
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stream.getTracks()[0].stop();
const devices = await navigator.mediaDevices.enumerateDevices();
assert_equals(devices.filter(({kind}) => kind == "videoinput").length,
devices1.filter(({kind}) => kind == "videoinput").length,
"same number of (previously exposed) videoinput devices");
assert_greater_than_equal(devices.filter(d => d.kind == "audioinput").length,
devices1.filter(d => d.kind == "audioinput").length,
"same number or more audioinput devices");
const order = ["audioinput", "videoinput", "audiooutput"];
for (const {kind, deviceId} of devices) {
assert_in_array(kind, order, "expected kind");
assert_equals(typeof deviceId, "string", "deviceId is a string.");
switch (kind) {
case "videoinput":
assert_greater_than(deviceId.length, 0, "video deviceId should not be empty.");
break;
case "audioinput":
assert_greater_than(deviceId.length, 0, "audio deviceId should not be empty.");
break;
}
}
const kinds = devices.map(({kind}) => kind);
const correct = [...kinds].sort((a, b) => order.indexOf(a) - order.indexOf(b));
assert_equals(JSON.stringify(kinds), JSON.stringify(correct), "correct order");
}, "mediaDevices.enumerateDevices() is working - after video then audio capture");
promise_test(async () => {
const devices = await navigator.mediaDevices.enumerateDevices();
for (const mediaInfo of devices) {
if (mediaInfo.kind == "audioinput" || mediaInfo.kind == "videoinput") {
assert_true("InputDeviceInfo" in window, "InputDeviceInfo exists");
assert_true(mediaInfo instanceof InputDeviceInfo);
} else if (mediaInfo.kind == "audiooutput") {
assert_true(mediaInfo instanceof MediaDeviceInfo);
} else {
assert_unreached("mediaInfo.kind should be one of 'audioinput', 'videoinput', or 'audiooutput'.")
}
}
}, "InputDeviceInfo is supported");
</script>
</body>
</html>

View File

@ -1,24 +0,0 @@
// Set permissions for camera and microphone using Web Driver
// Status can be one of "granted" or "denied"
// Scope take values from permission names
async function setMediaPermission(status="granted", scope=["camera", "microphone"]) {
try {
for (let s of scope) {
await test_driver.set_permission({ name: s }, status);
}
} catch (e) {
const noSetPermissionSupport = typeof e === "string" && e.match(/set_permission not implemented/);
if (!(noSetPermissionSupport ||
(e instanceof Error && e.message.match("unimplemented")) )) {
throw e;
}
// Web Driver not implemented action
// FF: https://bugzilla.mozilla.org/show_bug.cgi?id=1524074
// with current WPT runners, will default to granted state for FF and Safari
// throw if status!="granted" to invalidate test results
if (status === "denied") {
assert_implements_optional(!noSetPermissionSupport, "Unable to set permission to denied for this test");
}
}
}

View File

@ -40,9 +40,9 @@ promise_test(async () => {
const outputDevices = devices.filter(info => info.kind == "audiooutput");
assert_equals(outputDevices.length, 1, "number of audiooutput devices.");
assert_not_equals(selected, undefined);
const [info] = outputDevices;
assert_equals(info.deviceId, selected.deviceId, "deviceId exposed");
assert_equals(info.groupId, selected.groupId, "groupId exposed");
assert_equals(info.label, selected.label, "label exposed");
const info = outputDevices[0];
assert_equals(info.deviceId, selected.deviceId);
assert_equals(info.groupId, selected.groupId);
assert_equals(info.label, selected.label);
}, "enumerateDevices() after selectAudioOutput()");
</script>

View File

@ -19,7 +19,7 @@
// so enumerateDevices should not list detailed info yet
const iframe = document.createElement("iframe");
iframe.setAttribute("allow", "camera 'src';microphone 'src'");
iframe.src = "/mediacapture-streams/iframe-enumerate-pre-gum.html";
iframe.src = "/mediacapture-streams/iframe-enumerate.html";
document.body.appendChild(iframe);
const loadWatcher = new EventWatcher(t, iframe, ['load']);
await loadWatcher.wait_for('load');

View File

@ -36,8 +36,7 @@ function doTest(callGetUserMedia, testName)
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
@ -53,8 +52,8 @@ function doTest(callGetUserMedia, testName)
}, testName);
}
doTest(false, "enumerateDevices exposes mostly empty objects ahead of successful getUserMedia call");
doTest(true, "enumerateDevices exposes expected objects after successful getUserMedia call");
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");
</script>
</body>
</html>

View File

@ -23,85 +23,36 @@
promise_test(async () => {
assert_not_equals(navigator.mediaDevices.enumerateDevices, undefined, "navigator.mediaDevices.enumerateDevices exists");
const devices = await navigator.mediaDevices.enumerateDevices();
for (const {kind, deviceId, label, groupId} of devices) {
assert_in_array(kind, ["videoinput", "audioinput", "audiooutput"]);
assert_equals(deviceId, "", "deviceId should be empty string if getUserMedia was never called successfully.");
assert_equals(label, "", "label should be empty string if getUserMedia was never called successfully.");
assert_equals(groupId, "", "groupId should be empty string if getUserMedia was never called successfully.");
}
assert_less_than_equal(devices.filter(({kind}) => kind == "audioinput").length,
1, "there should be zero or one audio input device.");
assert_less_than_equal(devices.filter(({kind}) => kind == "videoinput").length,
1, "there should be zero or one video input device.");
assert_equals(devices.filter(({kind}) => kind == "audiooutput").length,
0, "there should be no audio output devices.");
assert_less_than_equal(devices.length, 2,
"there should be no more than two devices.");
if (devices.length > 1) {
assert_equals(devices[0].kind, "audioinput", "audioinput is first");
assert_equals(devices[1].kind, "videoinput", "videoinput is second");
const deviceList = await navigator.mediaDevices.enumerateDevices();
for (const mediaInfo of deviceList) {
assert_not_equals(mediaInfo.kind, undefined, "mediaInfo's kind should exist.");
assert_equals(mediaInfo.deviceId, "", "mediaInfo's deviceId should exist and be empty if getUserMedia was never called successfully.");
assert_equals(mediaInfo.label, "", "mediaInfo's label should exist and be empty if getUserMedia was never called successfully.");
assert_equals(mediaInfo.groupId, "", "mediaInfo's groupId should exist and be empty if getUserMedia was never called successfully.");
assert_in_array(mediaInfo.kind, ["videoinput", "audioinput", "audiooutput"]);
}
assert_less_than_equal(deviceList.filter((item) => { return item.kind == "audioinput"; }).length, 1, "there should be zero or one audio input device ");
assert_less_than_equal(deviceList.filter((item) => { return item.kind == "videoinput"; }).length, 1, "there should be zero or one video input device ");
}, "mediaDevices.enumerateDevices() is present and working - before capture");
promise_test(async t => {
await setMediaPermission("granted");
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
stream.getTracks()[0].stop();
const devices = await navigator.mediaDevices.enumerateDevices();
const kinds = ["audioinput", "videoinput"];
for (const {kind, deviceId} of devices) {
assert_in_array(kind, kinds, "camera doesn't expose audiooutput");
assert_equals(typeof deviceId, "string", "deviceId is a string.");
switch (kind) {
case "videoinput":
assert_greater_than(deviceId.length, 0, "video deviceId should not be empty.");
break;
case "audioinput":
assert_equals(deviceId.length, 0, "audio deviceId should be empty.");
break;
}
promise_test(async () => {
await setMediaPermission("granted", ["microphone"]);
await navigator.mediaDevices.getUserMedia({ audio : true });
const deviceList = await navigator.mediaDevices.enumerateDevices();
for (const mediaInfo of deviceList) {
assert_not_equals(mediaInfo.kind, undefined, "mediaInfo's kind should exist.");
assert_not_equals(mediaInfo.deviceId, "", "mediaInfo's deviceId should exist and not be empty.");
assert_in_array(mediaInfo.kind, ["videoinput", "audioinput", "audiooutput"]);
}
}, "mediaDevices.enumerateDevices() is working - after video capture");
// This test is designed to come after its video counterpart directly above
promise_test(async t => {
const devices1 = await navigator.mediaDevices.enumerateDevices();
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stream.getTracks()[0].stop();
const devices = await navigator.mediaDevices.enumerateDevices();
assert_equals(devices.filter(({kind}) => kind == "videoinput").length,
devices1.filter(({kind}) => kind == "videoinput").length,
"same number of (previously exposed) videoinput devices");
assert_greater_than_equal(devices.filter(d => d.kind == "audioinput").length,
devices1.filter(d => d.kind == "audioinput").length,
"same number or more audioinput devices");
const order = ["audioinput", "videoinput", "audiooutput"];
for (const {kind, deviceId} of devices) {
assert_in_array(kind, order, "expected kind");
assert_equals(typeof deviceId, "string", "deviceId is a string.");
switch (kind) {
case "videoinput":
assert_greater_than(deviceId.length, 0, "video deviceId should not be empty.");
break;
case "audioinput":
assert_greater_than(deviceId.length, 0, "audio deviceId should not be empty.");
break;
}
}
const kinds = devices.map(({kind}) => kind);
const correct = [...kinds].sort((a, b) => order.indexOf(a) - order.indexOf(b));
assert_equals(JSON.stringify(kinds), JSON.stringify(correct), "correct order");
}, "mediaDevices.enumerateDevices() is working - after video then audio capture");
}, "mediaDevices.enumerateDevices() is present and working - after capture");
promise_test(async () => {
const devices = await navigator.mediaDevices.enumerateDevices();
for (const mediaInfo of devices) {
const deviceList = await navigator.mediaDevices.enumerateDevices();
for (const mediaInfo of deviceList) {
if (mediaInfo.kind == "audioinput" || mediaInfo.kind == "videoinput") {
assert_true("InputDeviceInfo" in window, "InputDeviceInfo exists");
assert_true(mediaInfo instanceof InputDeviceInfo);
} else if (mediaInfo.kind == "audiooutput") {
} else if ( mediaInfo.kind == "audiooutput" ) {
assert_true(mediaInfo instanceof MediaDeviceInfo);
} else {
assert_unreached("mediaInfo.kind should be one of 'audioinput', 'videoinput', or 'audiooutput'.")

View File

@ -43,10 +43,6 @@ test(function () {
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();
@ -70,10 +66,6 @@ promise_test(async t => {
}, '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();

View File

@ -67,25 +67,18 @@
}, '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(({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");
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);
}
}, 'groupId is correctly reported by getSettings() for all input devices');

View File

@ -1,2 +0,0 @@
<!DOCTYPE html>
<script src="message-enumerateddevices-pre-gum.js"></script>

View File

@ -1,4 +0,0 @@
onmessage = async ({source}) => {
const devices = await navigator.mediaDevices.enumerateDevices();
source.postMessage({devices: devices.map(d => d.toJSON())}, '*');
}