Bug 1753131 - Dispatch devicechange events even without an actively capturing MediaStreamTrack r=jib

"fully active and has focus" is now a sufficient condition for dispatching "devicechange" events if the change in devices should be visible from enumerateDevices().
https://github.com/w3c/mediacapture-main/pull/574/files#diff-1217ca1c44ff30a33dd50c49d03b5cadc9633c789df8ff9370ed4a42859e1211R3146

Permissions checks are replaced with [[canExposeCameraInfo]] and [[canExposeMicrophoneInfo]] slots set by getUserMedia().
https://github.com/w3c/mediacapture-main/pull/641
https://github.com/w3c/mediacapture-main/pull/773

The "media.navigator.permission.disabled" pref is no longer involved in "devicechange" dispatch decisions.

Differential Revision: https://phabricator.services.mozilla.com/D132908
This commit is contained in:
Karl Tomlinson 2022-02-02 19:46:19 +00:00
Родитель 559abbbdc0
Коммит ae8f8e7132
7 изменённых файлов: 329 добавлений и 206 удалений

Просмотреть файл

@ -6,8 +6,10 @@
#include "AudioDeviceInfo.h"
#include "MediaEngine.h"
#include "MediaEngineDefault.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/FeaturePolicyUtils.h"
#include "mozilla/dom/MediaStreamBinding.h"
#include "mozilla/dom/MediaDeviceInfo.h"
#include "mozilla/dom/MediaDevicesBinding.h"
@ -24,8 +26,10 @@
namespace mozilla::dom {
using EnumerationFlag = MediaManager::EnumerationFlag;
using ConstDeviceSetPromise = MediaManager::ConstDeviceSetPromise;
using LocalDeviceSetPromise = MediaManager::LocalDeviceSetPromise;
using LocalMediaDeviceSetRefCnt = MediaManager::LocalMediaDeviceSetRefCnt;
using MediaDeviceSetRefCnt = MediaManager::MediaDeviceSetRefCnt;
MediaDevices::MediaDevices(nsPIDOMWindowInner* aWindow)
: DOMEventTargetHelper(aWindow) {}
@ -97,18 +101,27 @@ already_AddRefed<Promise> MediaDevices::GetUserMedia(
(audio.IsBoolean()
? audio.GetAsBoolean()
: !audio.GetAsMediaTrackConstraints().mMediaSource.WasPassed());
bool isCamera =
!haveFake &&
(video.IsBoolean()
? video.GetAsBoolean()
: !video.GetAsMediaTrackConstraints().mMediaSource.WasPassed());
RefPtr<MediaDevices> self(this);
MediaManager::Get()
->GetUserMedia(owner, aConstraints, aCallerType)
->Then(
GetCurrentSerialEventTarget(), __func__,
[this, self, p, isMicrophone](RefPtr<DOMMediaStream>&& aStream) {
[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;
}
p->MaybeResolve(std::move(aStream));
},
[this, self, p](const RefPtr<MediaMgrError>& error) {
@ -161,82 +174,232 @@ void MediaDevices::MaybeResumeDeviceExposure() {
!bc->GetIsActiveBrowserWindow()) { // browser window does not have focus
return;
}
if (mHaveUnprocessedDeviceListChange) {
NS_DispatchToCurrentThread(
NS_NewRunnableFunction("devicechange", [self = RefPtr(this), this] {
DispatchTrustedEvent(u"devicechange"_ns);
}));
mHaveUnprocessedDeviceListChange = false;
}
auto pending = std::move(mPendingEnumerateDevicesPromises);
for (auto& promise : pending) {
ResumeEnumerateDevices(std::move(promise));
}
MediaManager::Get()->GetPhysicalDevices()->Then(
GetCurrentSerialEventTarget(), __func__,
[self = RefPtr(this), this,
haveDeviceListChange = mHaveUnprocessedDeviceListChange,
enumerateDevicesPromises = std::move(mPendingEnumerateDevicesPromises)](
RefPtr<const MediaDeviceSetRefCnt> aAllDevices) mutable {
RefPtr<MediaDeviceSetRefCnt> exposedDevices =
FilterExposedDevices(*aAllDevices);
if (haveDeviceListChange) {
if (ShouldQueueDeviceChange(*exposedDevices)) {
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
"devicechange", [self = RefPtr(this), this] {
DispatchTrustedEvent(u"devicechange"_ns);
}));
}
mLastPhysicalDevices = std::move(aAllDevices);
}
if (!enumerateDevicesPromises.IsEmpty()) {
ResumeEnumerateDevices(std::move(enumerateDevicesPromises),
std::move(exposedDevices));
}
},
[](RefPtr<MediaMgrError>&&) {
MOZ_ASSERT_UNREACHABLE("GetPhysicalDevices does not reject");
});
mHaveUnprocessedDeviceListChange = false;
}
void MediaDevices::ResumeEnumerateDevices(RefPtr<Promise> aPromise) {
RefPtr<MediaDeviceSetRefCnt> MediaDevices::FilterExposedDevices(
const MediaDeviceSet& aDevices) const {
nsPIDOMWindowInner* window = GetOwner();
RefPtr exposed = new MediaDeviceSetRefCnt();
if (!window) {
return exposed; // Promises will be left pending
}
Document* doc = window->GetExtantDoc();
if (!doc) {
return exposed;
}
// Only expose devices which are allowed to use:
// https://w3c.github.io/mediacapture-main/#dom-mediadevices-enumeratedevices
bool dropMics = !FeaturePolicyUtils::IsFeatureAllowed(doc, u"microphone"_ns);
bool dropCams = !FeaturePolicyUtils::IsFeatureAllowed(doc, u"camera"_ns);
bool dropSpeakers =
!Preferences::GetBool("media.setsinkid.enabled") ||
!FeaturePolicyUtils::IsFeatureAllowed(doc, u"speaker-selection"_ns);
bool resistFingerprinting = nsContentUtils::ShouldResistFingerprinting(doc);
if (resistFingerprinting) {
RefPtr fakeEngine = new MediaEngineDefault();
fakeEngine->EnumerateDevices(MediaSourceEnum::Microphone,
MediaSinkEnum::Other, exposed);
fakeEngine->EnumerateDevices(MediaSourceEnum::Camera, MediaSinkEnum::Other,
exposed);
dropMics = dropCams = true;
// Speakers are not handled specially with resistFingerprinting because
// they are exposed only when explicitly and individually allowed by the
// user.
}
nsTHashSet<nsString> exposedMicrophoneGroupIds;
for (const auto& device : aDevices) {
switch (device->mKind) {
case MediaDeviceKind::Audioinput:
if (dropMics) {
continue;
}
if (mCanExposeMicrophoneInfo) {
exposedMicrophoneGroupIds.Insert(device->mRawGroupID);
}
// Reducing to one mic or cam device when not mCanExposeMicrophoneInfo
// or not mCanExposeCameraInfo is bug 1528042.
break;
case MediaDeviceKind::Videoinput:
if (dropCams) {
continue;
}
break;
case MediaDeviceKind::Audiooutput:
if (dropSpeakers ||
(!mExplicitlyGrantedAudioOutputRawIds.Contains(device->mRawID) &&
// Assumes aDevices order has microphones before speakers.
!exposedMicrophoneGroupIds.Contains(device->mRawGroupID))) {
continue;
}
break;
case MediaDeviceKind::EndGuard_:
continue;
// Avoid `default:` so that `-Wswitch` catches missing
// enumerators at compile time.
}
exposed->AppendElement(device);
}
return exposed;
}
bool MediaDevices::ShouldQueueDeviceChange(
const MediaDeviceSet& aExposedDevices) const {
if (!mLastPhysicalDevices) { // SetupDeviceChangeListener not complete
return false;
}
RefPtr<MediaDeviceSetRefCnt> lastExposedDevices =
FilterExposedDevices(*mLastPhysicalDevices);
auto exposed = aExposedDevices.begin();
auto exposedEnd = aExposedDevices.end();
auto last = lastExposedDevices->begin();
auto lastEnd = lastExposedDevices->end();
// Lists from FilterExposedDevices may have multiple devices of the same
// kind even when only a single anonymous device of that kind should be
// 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`.
// A change between zero and non-zero numbers of microphone or camera
// devices triggers a devicechange event even if that kind of device is
// not yet exposed.
MediaDeviceKind kind = (*exposed)->mKind;
if (kind != (*last)->mKind) {
return true;
}
// `exposed` and `last` have matching 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) {
return true;
}
++exposed;
++last;
continue;
}
// `aExposedDevices` and `lastExposedDevices` both have non-zero numbers
// of devices of this unexposed kind.
// Skip remaining devices of this kind because all devices of this kind
// should be exposed as a single anonymous device.
do {
++exposed;
} while (exposed != exposedEnd && (*exposed)->mKind == kind);
do {
++last;
} while (last != lastEnd && (*last)->mKind == kind);
}
// Queue "devicechange" if the number of exposed devices differs.
return exposed < exposedEnd || last < lastEnd;
}
void MediaDevices::ResumeEnumerateDevices(
nsTArray<RefPtr<Promise>>&& aPromises,
RefPtr<const MediaDeviceSetRefCnt> aExposedDevices) const {
nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
MOZ_ASSERT(window, "Fully active document should have window");
RefPtr<MediaDevices> self(this);
MediaManager::Get()->EnumerateDevices(window)->Then(
GetCurrentSerialEventTarget(), __func__,
[this, self, aPromise](RefPtr<LocalMediaDeviceSetRefCnt>&& aDevices) {
nsPIDOMWindowInner* window = GetWindowIfCurrent();
if (!window) {
return; // Leave Promise pending after navigation by design.
if (!window) {
return; // Leave Promise pending after navigation by design.
}
MediaManager::Get()
->AnonymizeDevices(window, std::move(aExposedDevices))
->Then(GetCurrentSerialEventTarget(), __func__,
[self = RefPtr(this), this, promises = std::move(aPromises)](
const LocalDeviceSetPromise::ResolveOrRejectValue&
aLocalDevices) {
nsPIDOMWindowInner* window = GetWindowIfCurrent();
if (!window) {
return; // Leave Promises pending after navigation by design.
}
for (const RefPtr<Promise>& promise : promises) {
if (aLocalDevices.IsReject()) {
aLocalDevices.RejectValue()->Reject(promise);
} else {
ResolveEnumerateDevicesPromise(
promise, *aLocalDevices.ResolveValue());
}
}
});
}
void MediaDevices::ResolveEnumerateDevicesPromise(
Promise* aPromise, const LocalMediaDeviceSet& aDevices) const {
nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
auto windowId = window->WindowID();
nsTArray<RefPtr<MediaDeviceInfo>> infos;
bool allowLabel =
aDevices.Length() == 0 ||
MediaManager::Get()->IsActivelyCapturingOrHasAPermission(windowId);
for (const RefPtr<LocalMediaDevice>& device : aDevices) {
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;
}
auto windowId = window->WindowID();
nsTArray<RefPtr<MediaDeviceInfo>> infos;
bool allowLabel =
aDevices->Length() == 0 ||
MediaManager::Get()->IsActivelyCapturingOrHasAPermission(windowId);
nsTHashSet<nsString> exposedMicrophoneGroupIds;
for (auto& device : *aDevices) {
nsString label;
MOZ_ASSERT(device->Kind() < MediaDeviceKind::EndGuard_);
switch (device->Kind()) {
case MediaDeviceKind::Audioinput:
if (mCanExposeMicrophoneInfo) {
exposedMicrophoneGroupIds.Insert(device->mGroupID);
}
[[fallthrough]];
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:
if (!mExplicitlyGrantedAudioOutputIds.Contains(device->mID) &&
// Assumes aDevices order has microphones before speakers.
!exposedMicrophoneGroupIds.Contains(device->mGroupID)) {
continue;
}
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));
},
[this, self, aPromise](const RefPtr<MediaMgrError>& error) {
nsPIDOMWindowInner* window = GetWindowIfCurrent();
if (!window) {
return; // Leave Promise pending after navigation by design.
}
error->Reject(aPromise);
});
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));
}
already_AddRefed<Promise> MediaDevices::GetDisplayMedia(
@ -399,7 +562,7 @@ already_AddRefed<Promise> MediaDevices::SelectAudioOutput(
return; // Leave Promise pending after navigation by design.
}
MOZ_ASSERT(aDevice->Kind() == dom::MediaDeviceKind::Audiooutput);
mExplicitlyGrantedAudioOutputIds.Insert(aDevice->mID);
mExplicitlyGrantedAudioOutputRawIds.Insert(aDevice->RawID());
p->MaybeResolve(
MakeRefPtr<MediaDeviceInfo>(aDevice->mID, aDevice->Kind(),
aDevice->mName, aDevice->mGroupID));
@ -452,62 +615,55 @@ static RefPtr<AudioDeviceInfo> CopyWithNullDeviceId(
RefPtr<MediaDevices::SinkInfoPromise> MediaDevices::GetSinkDevice(
const nsString& aDeviceId) {
MOZ_ASSERT(NS_IsMainThread());
bool isExposed = aDeviceId.IsEmpty() ||
mExplicitlyGrantedAudioOutputIds.Contains(aDeviceId);
// If the device id is not exposed, then check microphone groupIds.
MediaSourceEnum audioInputType = isExposed || !mCanExposeMicrophoneInfo
? MediaSourceEnum::Other
: MediaSourceEnum::Microphone;
return MediaManager::Get()
->EnumerateDevicesImpl(GetOwner(), MediaSourceEnum::Other, audioInputType,
EnumerationFlag::EnumerateAudioOutputs)
->GetPhysicalDevices()
->Then(
GetCurrentSerialEventTarget(), __func__,
[aDeviceId,
isExposed](RefPtr<LocalMediaDeviceSetRefCnt> aDevices) mutable {
[self = RefPtr(this), this,
aDeviceId](RefPtr<const MediaDeviceSetRefCnt> aRawDevices) {
nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
if (!window) {
return LocalDeviceSetPromise::CreateAndReject(
new MediaMgrError(MediaMgrError::Name::AbortError), __func__);
}
// Don't filter if matching the preferred device, because that may
// not be exposed.
RefPtr devices = aDeviceId.IsEmpty()
? std::move(aRawDevices)
: FilterExposedDevices(*aRawDevices);
return MediaManager::Get()->AnonymizeDevices(window,
std::move(devices));
},
[](RefPtr<MediaMgrError>&& reason) {
MOZ_ASSERT_UNREACHABLE("GetPhysicalDevices does not reject");
return RefPtr<LocalDeviceSetPromise>();
})
->Then(
GetCurrentSerialEventTarget(), __func__,
[aDeviceId](RefPtr<LocalMediaDeviceSetRefCnt> aDevices) {
RefPtr<AudioDeviceInfo> outputInfo;
nsString groupId;
// Check for a matching device.
for (const RefPtr<LocalMediaDevice>& device : *aDevices) {
if (device->Kind() != dom::MediaDeviceKind::Audiooutput) {
continue;
}
if (aDeviceId.IsEmpty()) {
if (device->GetAudioDeviceInfo()->Preferred()) {
outputInfo =
CopyWithNullDeviceId(device->GetAudioDeviceInfo());
break;
}
MOZ_ASSERT(device->GetAudioDeviceInfo()->Preferred(),
"First Audiooutput should be preferred");
return SinkInfoPromise::CreateAndResolve(
CopyWithNullDeviceId(device->GetAudioDeviceInfo()),
__func__);
} else if (aDeviceId.Equals(device->mID)) {
outputInfo = device->GetAudioDeviceInfo();
groupId = device->mGroupID;
break;
}
}
if (outputInfo && !isExposed) {
// Check microphone groups.
MOZ_ASSERT(!groupId.IsEmpty());
for (const RefPtr<LocalMediaDevice>& device : *aDevices) {
if (device->Kind() != dom::MediaDeviceKind::Audioinput) {
continue;
}
if (groupId.Equals(device->mGroupID)) {
isExposed = true;
break;
}
return SinkInfoPromise::CreateAndResolve(
device->GetAudioDeviceInfo(), __func__);
}
}
/* If sinkId is not the empty string and does not match any audio
* output device identified by the result that would be provided
* by enumerateDevices(), reject p with a new DOMException whose
* name is NotFoundError and abort these substeps. */
if (!outputInfo || !isExposed) {
return SinkInfoPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
__func__);
}
return SinkInfoPromise::CreateAndResolve(outputInfo, __func__);
return SinkInfoPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
__func__);
},
// aRejectMethod =
[](RefPtr<MediaMgrError>&& aError) {
@ -528,12 +684,6 @@ void MediaDevices::OnDeviceChange() {
return;
}
if (!(MediaManager::Get()->IsActivelyCapturingOrHasAPermission(
GetOwner()->WindowID()) ||
Preferences::GetBool("media.navigator.permission.disabled", false))) {
return;
}
// Do not fire event to content script when
// privacy.resistFingerprinting is true.
if (nsContentUtils::ShouldResistFingerprinting()) {
@ -567,6 +717,15 @@ void MediaDevices::SetupDeviceChangeListener() {
mDeviceChangeListener = MediaManager::Get()->DeviceListChangeEvent().Connect(
mainThread, this, &MediaDevices::OnDeviceChange);
mIsDeviceChangeListenerSetUp = true;
MediaManager::Get()->GetPhysicalDevices()->Then(
GetCurrentSerialEventTarget(), __func__,
[self = RefPtr(this), this](RefPtr<const MediaDeviceSetRefCnt> aDevices) {
mLastPhysicalDevices = std::move(aDevices);
},
[](RefPtr<MediaMgrError>&& reason) {
MOZ_ASSERT_UNREACHABLE("GetPhysicalDevices does not reject");
});
}
void MediaDevices::SetOndevicechange(

Просмотреть файл

@ -20,9 +20,17 @@ class AudioDeviceInfo;
namespace mozilla {
class LocalMediaDevice;
class MediaDevice;
class MediaMgrError;
template <typename ResolveValueT, typename RejectValueT, bool IsExclusive>
class MozPromise;
namespace media {
template <typename T>
class Refcountable;
} // namespace media
namespace dom {
class Promise;
@ -84,22 +92,33 @@ class MediaDevices final : public DOMEventTargetHelper {
void BrowserWindowBecameActive() { MaybeResumeDeviceExposure(); }
private:
class GumResolver;
class EnumDevResolver;
class GumRejecter;
using MediaDeviceSet = nsTArray<RefPtr<MediaDevice>>;
using MediaDeviceSetRefCnt = media::Refcountable<MediaDeviceSet>;
using LocalMediaDeviceSet = nsTArray<RefPtr<LocalMediaDevice>>;
virtual ~MediaDevices();
void MaybeResumeDeviceExposure();
void ResumeEnumerateDevices(RefPtr<Promise> aPromise);
void ResumeEnumerateDevices(
nsTArray<RefPtr<Promise>>&& aPromises,
RefPtr<const MediaDeviceSetRefCnt> aExposedDevices) const;
RefPtr<MediaDeviceSetRefCnt> FilterExposedDevices(
const MediaDeviceSet& aDevices) const;
bool ShouldQueueDeviceChange(const MediaDeviceSet& aExposedDevices) const;
void ResolveEnumerateDevicesPromise(
Promise* aPromise, const LocalMediaDeviceSet& aDevices) const;
nsTHashSet<nsString> mExplicitlyGrantedAudioOutputIds;
nsTHashSet<nsString> mExplicitlyGrantedAudioOutputRawIds;
nsTArray<RefPtr<Promise>> mPendingEnumerateDevicesPromises;
// Connect/Disconnect on main thread only
MediaEventListener mDeviceChangeListener;
// Ordered set of the system physical devices when devicechange event
// decisions were last performed.
RefPtr<const MediaDeviceSetRefCnt> mLastPhysicalDevices;
bool mIsDeviceChangeListenerSetUp = false;
bool mHaveUnprocessedDeviceListChange = false;
bool mCanExposeMicrophoneInfo = false;
bool mCanExposeCameraInfo = false;
void RecordAccessTelemetry(const UseCounter counter) const;
};

Просмотреть файл

@ -2997,48 +2997,6 @@ RefPtr<LocalDeviceSetPromise> MediaManager::EnumerateDevicesImpl(
});
}
RefPtr<LocalDeviceSetPromise> MediaManager::EnumerateDevices(
nsPIDOMWindowInner* aWindow) {
MOZ_ASSERT(NS_IsMainThread());
if (sHasShutdown) {
return LocalDeviceSetPromise::CreateAndReject(
MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError,
"In shutdown"),
__func__);
}
Document* doc = aWindow->GetExtantDoc();
MOZ_ASSERT(doc);
// Only expose devices which are allowed to use:
// https://w3c.github.io/mediacapture-main/#dom-mediadevices-enumeratedevices
MediaSourceEnum videoType =
FeaturePolicyUtils::IsFeatureAllowed(doc, u"camera"_ns)
? MediaSourceEnum::Camera
: MediaSourceEnum::Other;
MediaSourceEnum audioType =
FeaturePolicyUtils::IsFeatureAllowed(doc, u"microphone"_ns)
? MediaSourceEnum::Microphone
: MediaSourceEnum::Other;
EnumerationFlags flags;
if (Preferences::GetBool("media.setsinkid.enabled") &&
FeaturePolicyUtils::IsFeatureAllowed(doc, u"speaker-selection"_ns)) {
flags += EnumerationFlag::EnumerateAudioOutputs;
}
if (audioType == MediaSourceEnum::Other &&
videoType == MediaSourceEnum::Other && flags.isEmpty()) {
return LocalDeviceSetPromise::CreateAndResolve(
new LocalMediaDeviceSetRefCnt(), __func__);
}
bool resistFingerprinting = nsContentUtils::ShouldResistFingerprinting(doc);
if (resistFingerprinting) {
flags += EnumerationFlag::ForceFakes;
}
return EnumerateDevicesImpl(aWindow, videoType, audioType, flags);
}
RefPtr<LocalDevicePromise> MediaManager::SelectAudioOutput(
nsPIDOMWindowInner* aWindow, const dom::AudioOutputOptions& aOptions,
CallerType aCallerType) {

Просмотреть файл

@ -278,18 +278,6 @@ class MediaManager final : public nsIMediaManagerService,
const dom::MediaStreamConstraints& aConstraints,
dom::CallerType aCallerType);
RefPtr<LocalDeviceSetPromise> EnumerateDevices(nsPIDOMWindowInner* aWindow);
enum class EnumerationFlag {
AllowPermissionRequest,
EnumerateAudioOutputs,
ForceFakes,
};
using EnumerationFlags = EnumSet<EnumerationFlag>;
RefPtr<LocalDeviceSetPromise> EnumerateDevicesImpl(
nsPIDOMWindowInner* aWindow, dom::MediaSourceEnum aVideoInputType,
dom::MediaSourceEnum aAudioInputType, EnumerationFlags aFlags);
RefPtr<LocalDevicePromise> SelectAudioOutput(
nsPIDOMWindowInner* aWindow, const dom::AudioOutputOptions& aOptions,
dom::CallerType aCallerType);
@ -330,6 +318,16 @@ class MediaManager final : public nsIMediaManagerService,
const MediaDeviceSet& aAudios);
private:
enum class EnumerationFlag {
AllowPermissionRequest,
EnumerateAudioOutputs,
ForceFakes,
};
using EnumerationFlags = EnumSet<EnumerationFlag>;
RefPtr<LocalDeviceSetPromise> EnumerateDevicesImpl(
nsPIDOMWindowInner* aWindow, dom::MediaSourceEnum aVideoInputType,
dom::MediaSourceEnum aAudioInputType, EnumerationFlags aFlags);
RefPtr<DeviceSetPromise> EnumerateRawDevices(
dom::MediaSourceEnum aVideoInputType,
dom::MediaSourceEnum aAudioInputType, EnumerationFlags aFlags);

Просмотреть файл

@ -47,7 +47,7 @@ BaseMediaMgrError::BaseMediaMgrError(Name aName, const nsACString& aMessage,
NS_IMPL_ISUPPORTS0(MediaMgrError)
void MediaMgrError::Reject(dom::Promise* aPromise) {
void MediaMgrError::Reject(dom::Promise* aPromise) const {
switch (mName) {
case Name::AbortError:
aPromise->MaybeRejectWithAbortError(mMessage);

Просмотреть файл

@ -71,7 +71,7 @@ class MediaMgrError final : public nsISupports, public BaseMediaMgrError {
NS_DECL_THREADSAFE_ISUPPORTS
void Reject(dom::Promise* aPromise);
void Reject(dom::Promise* aPromise) const;
private:
~MediaMgrError() = default;

Просмотреть файл

@ -47,31 +47,29 @@ runTest(async () => {
// Make fake devices count as real, permission-wise, or devicechange
// events won't be exposed
["media.navigator.permission.fake", true],
// Initial state for gUM.
// For gUM.
["media.navigator.permission.disabled", true]
),
]);
const topDevices = navigator.mediaDevices;
const frameDevices = iframe.contentWindow.navigator.mediaDevices;
const streams = await Promise.all(
[topDevices, frameDevices].map(d => d.getUserMedia({video: true}))
);
const [topTrack, frameTrack] = streams.map(s => s.getVideoTracks()[0]);
topTrack.stop();
// permission.disabled would expose devicechange events without gUM
await pushPrefs(["media.navigator.permission.disabled", false]);
// 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.
frameDevices.ondevicechange = () => {};
const topEventPromise = resolveOnEvent(topDevices, "devicechange");
const frameStream = await frameDevices.getUserMedia({video: true});
frameStream.getVideoTracks()[0].stop();
await doDevicechanges(frameDevices);
ok(true,
"devicechange event is fired when gUM is in use without permanent " +
"permission granted");
"devicechange event is fired when gUM has been in use");
// Race a settled Promise to check that the event has not been received in
// the toplevel Window.
const racer = {type: "racer"};
const racer = {};
is(await Promise.race([topEventPromise, racer]), racer,
"devicechange event is NOT fired when gUM is NO LONGER in use and " +
"permanent permission is NOT granted");
"devicechange event is NOT fired when gUM has NOT been in use");
if (navigator.userAgent.includes("Android")) {
todo(false, "test assumes Firefox-for-Desktop specific API and behavior");
@ -95,10 +93,8 @@ runTest(async () => {
is(document.visibilityState, 'hidden', 'visibilityState')
const frameEventPromise = resolveOnEvent(frameDevices, "devicechange");
const tabDevices = tab.navigator.mediaDevices;
const tabStream = await withPrefs(
[["media.navigator.permission.disabled", true]],
() => tabDevices.getUserMedia({video: true})
);
tabDevices.ondevicechange = () => {};
const tabStream = await tabDevices.getUserMedia({video: true});
await doDevicechanges(tabDevices);
is(await Promise.race([frameEventPromise, racer]), racer,
"devicechange event is NOT fired while tab is in background");
@ -107,13 +103,6 @@ runTest(async () => {
is(document.visibilityState, 'visible', 'visibilityState')
await frameEventPromise;
ok(true, "devicechange event IS fired when tab returns to foreground");
frameTrack.stop();
await pushPrefs(["media.navigator.permission.disabled", true]);
await doDevicechanges(frameDevices);
is((await Promise.race([topEventPromise, racer])).type, "devicechange",
"devicechange event IS fired when gUM is NO LONGER in use and " +
"permanent permission IS granted");
});
</script>