Bug 1436523 - Rework media manager enumeration to prefer loopback to fake devices, allow mixing of fake and loopback. r?achronop draft
authorBryce Van Dyk <bvandyk@mozilla.com>
Mon, 12 Feb 2018 16:56:26 -0500
changeset 773432 7389ca2dddb11f8ec99f0f8e6b0942f75d57ec79
parent 773431 afbdb685280535353cdd678673d960cfe9f6e74e
child 773433 a55255734449384822f6cfaa4036f67e0881f791
push id104235
push userbvandyk@mozilla.com
push dateTue, 27 Mar 2018 22:55:40 +0000
reviewersachronop
bugs1436523
milestone61.0a1
Bug 1436523 - Rework media manager enumeration to prefer loopback to fake devices, allow mixing of fake and loopback. r?achronop Change the media manager so that if fake and loopback devices are requested, loopback is preferred. With this change loopback and fake devices can also be mixed. Since the fake flag is coarse, and does not specify fake audio or video, we would previously just make everything fake. As loopback sets flags for video and audio separately, we can now request a single loopback device, while also setting the fake flag to get a mix. E.g. if we request a loopback audio device, and set the fake flag, we should get loopback audio and fake video. This change also attempts to somewhat consolidate where these settings take place. Previously, EnumerateRawDevices did much of the loopback setup. However, other steps around fingerprint resistance or fake devices were done in earlier functions (EnumerateDevices and GetUserMedia). This changeset moves the loopback setup so that it's more consolidated with the other setup code in these functions. MozReview-Commit-ID: FF0bR0Nyws9
dom/media/MediaManager.cpp
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1859,67 +1859,81 @@ MediaManager::EnumerateRawDevices(uint64
                                   MediaSourceEnum aVideoType,
                                   MediaSourceEnum aAudioType,
                                   DeviceEnumerationType aVideoEnumType /* = Normal */,
                                   DeviceEnumerationType aAudioEnumType /* = Normal */)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aVideoType != MediaSourceEnum::Other ||
              aAudioType != MediaSourceEnum::Other);
+  // Since the enums can take one of several values, the following asserts rely
+  // on short circuting behavior. E.g. aVideoEnumType != Fake will be true if
+  // the requested device is not fake and thus the assert will pass. However,
+  // if the device is fake, aVideoType == MediaSourceEnum::Camera will be
+  // checked as well, ensuring that fake devices are of the camera type.
+  MOZ_ASSERT(aVideoEnumType != Fake || aVideoType == MediaSourceEnum::Camera,
+             "If fake cams are requested video type should be camera!");
+  MOZ_ASSERT(aVideoEnumType != Loopback || aVideoType == MediaSourceEnum::Camera,
+             "If loopback video is requested video type should be camera!");
+  MOZ_ASSERT(aAudioEnumType != Fake || aAudioType == MediaSourceEnum::Microphone,
+             "If fake mics are requested audio type should be microphone!");
+  MOZ_ASSERT(aAudioEnumType != Loopback || aAudioType == MediaSourceEnum::Microphone,
+             "If loopback audio is requested audio type should be microphone!");
+
   RefPtr<PledgeSourceSet> p = new PledgeSourceSet();
   uint32_t id = mOutstandingPledges.Append(*p);
 
-  nsAutoCString audioLoopDev, videoLoopDev;
-  if (aVideoEnumType != Fake && aAudioEnumType != Fake) {
-    // Fake stream not requested. The entire device stack is available.
-    // Loop in loopback devices if they are set, and their respective type is
-    // requested. This is currently used for automated media tests only.
-    if (aVideoType == MediaSourceEnum::Camera) {
-      Preferences::GetCString("media.video_loopback_dev", videoLoopDev);
-    }
-    if (aAudioType == MediaSourceEnum::Microphone) {
-      Preferences::GetCString("media.audio_loopback_dev", audioLoopDev);
-    }
-  }
-
   bool hasVideo = aVideoType != MediaSourceEnum::Other;
   bool hasAudio = aAudioType != MediaSourceEnum::Other;
-  bool fakeCams = aVideoEnumType == Fake && aVideoType == MediaSourceEnum::Camera;
-  bool fakeMics = aAudioEnumType == Fake && aAudioType == MediaSourceEnum::Microphone;
-  bool realDevicesRequested = (!fakeCams && hasVideo) || (!fakeMics && hasAudio);
-
-  RefPtr<Runnable> task = NewTaskFrom([id, aWindowId, audioLoopDev,
-                                       videoLoopDev, aVideoType,
-                                       aAudioType, hasVideo, hasAudio,
-                                       fakeCams, fakeMics, realDevicesRequested]() {
+
+  // True of at least one of video or audio is a fake device
+  bool fakeDeviceRequested = (aVideoEnumType == Fake && hasVideo) ||
+                             (aAudioEnumType == Fake && hasAudio);
+  // True if at least one of video or audio is a real device
+  bool realDeviceRequested = (aVideoEnumType != Fake && hasVideo) ||
+                             (aAudioEnumType != Fake && hasAudio);
+
+  nsAutoCString videoLoopDev, audioLoopDev;
+  if (hasVideo && aVideoEnumType == Loopback) {
+    Preferences::GetCString("media.video_loopback_dev", videoLoopDev);
+  }
+  if (hasAudio && aAudioEnumType == Loopback) {
+    Preferences::GetCString("media.audio_loopback_dev", audioLoopDev);
+  }
+
+  RefPtr<Runnable> task = NewTaskFrom([id, aWindowId, aVideoType, aAudioType,
+                                       aVideoEnumType, aAudioEnumType,
+                                       videoLoopDev, audioLoopDev,
+                                       hasVideo, hasAudio, fakeDeviceRequested,
+                                       realDeviceRequested]() {
     // Only enumerate what's asked for, and only fake cams and mics.
     RefPtr<MediaEngine> fakeBackend, realBackend;
-    if (fakeCams || fakeMics) {
+    if (fakeDeviceRequested) {
       fakeBackend = new MediaEngineDefault();
     }
-    if (realDevicesRequested) {
+    if (realDeviceRequested) {
       MediaManager* manager = MediaManager::GetIfExists();
       MOZ_RELEASE_ASSERT(manager); // Must exist while media thread is alive
       realBackend = manager->GetBackend(aWindowId);
     }
 
     auto result = MakeUnique<SourceSet>();
 
     if (hasVideo) {
-      nsTArray<RefPtr<MediaDevice>> videos;
-      GetSources(fakeCams? fakeBackend : realBackend, aWindowId, aVideoType,
-                 videos, videoLoopDev.get());
+      SourceSet videos;
+      GetSources(aVideoEnumType == Fake ? fakeBackend : realBackend,
+                 aWindowId, aVideoType, videos, videoLoopDev.get());
       for (auto& source : videos) {
         result->AppendElement(source);
       }
     }
     if (hasAudio) {
-      nsTArray<RefPtr<MediaDevice>> audios;
-      GetSources(fakeMics? fakeBackend : realBackend, aWindowId, aAudioType,
-                 audios, audioLoopDev.get());
+      SourceSet audios;
+      GetSources(aAudioEnumType == Fake ? fakeBackend : realBackend,
+                 aWindowId, aAudioType, audios, audioLoopDev.get());
       for (auto& source : audios) {
         result->AppendElement(source);
       }
     }
     NS_DispatchToMainThread(NewRunnableFrom([id, result = Move(result)]() mutable {
       MediaManager* mgr = MediaManager::GetIfExists();
       if (!mgr) {
         return NS_OK;
@@ -1927,17 +1941,17 @@ MediaManager::EnumerateRawDevices(uint64
       RefPtr<PledgeSourceSet> p = mgr->mOutstandingPledges.Remove(id);
       if (p) {
         p->Resolve(result.release());
       }
       return NS_OK;
     }));
   });
 
-  if (realDevicesRequested &&
+  if (realDeviceRequested &&
       Preferences::GetBool("media.navigator.permission.device", false)) {
     // Need to ask permission to retrieve list of all devices;
     // notify frontend observer and wait for callback notification to post task.
     const char16_t* const type =
       (aVideoType != MediaSourceEnum::Camera)     ? u"audio" :
       (aAudioType != MediaSourceEnum::Microphone) ? u"video" :
                                                     u"all";
     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
@@ -2720,33 +2734,57 @@ MediaManager::GetUserMedia(nsPIDOMWindow
   // Get list of all devices, with origin-specific device ids.
 
   MediaEnginePrefs prefs = mPrefs;
 
   nsString callID;
   rv = GenerateUUID(callID);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  bool fake = c.mFake.WasPassed()? c.mFake.Value() :
-      Preferences::GetBool("media.navigator.streams.fake");
-
   bool hasVideo = videoType != MediaSourceEnum::Other;
   bool hasAudio = audioType != MediaSourceEnum::Other;
-  bool fakeCams = fake && videoType == MediaSourceEnum::Camera;
-  bool fakeMics = fake && audioType == MediaSourceEnum::Microphone;
-  bool realDevicesRequested = (!fakeCams && hasVideo) || (!fakeMics && hasAudio);
-
+  DeviceEnumerationType videoEnumerationType = Normal;
+  DeviceEnumerationType audioEnumerationType = Normal;
+
+  // Handle loopback and fake requests. For gUM we don't consider resist
+  // fingerprinting as users should be prompted anyway.
+  bool wantFakes = c.mFake.WasPassed() ? c.mFake.Value() :
+    Preferences::GetBool("media.navigator.streams.fake");
+  nsAutoCString videoLoopDev, audioLoopDev;
+  // Video
+  if (videoType == MediaSourceEnum::Camera) {
+    Preferences::GetCString("media.video_loopback_dev", videoLoopDev);
+    // Loopback prefs take precedence over fake prefs
+    if (!videoLoopDev.IsEmpty()) {
+      videoEnumerationType = Loopback;
+    } else if (wantFakes) {
+      videoEnumerationType = Fake;
+    }
+  }
+  // Audio
+  if (audioType == MediaSourceEnum::Microphone) {
+    Preferences::GetCString("media.audio_loopback_dev", audioLoopDev);
+    // Loopback prefs take precedence over fake prefs
+    if (!audioLoopDev.IsEmpty()) {
+      audioEnumerationType = Loopback;
+    } else if (wantFakes) {
+      audioEnumerationType = Fake;
+    }
+  }
+
+  bool realDevicesRequested = (videoEnumerationType != Fake && hasVideo) ||
+                              (audioEnumerationType != Fake && hasAudio);
   bool askPermission =
     (!privileged || Preferences::GetBool("media.navigator.permission.force")) &&
     (realDevicesRequested || Preferences::GetBool("media.navigator.permission.fake"));
 
   RefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowID, videoType,
                                                    audioType,
-                                                   fake ? Fake : Normal,
-                                                   fake ? Fake : Normal);
+                                                   videoEnumerationType,
+                                                   audioEnumerationType);
   RefPtr<MediaManager> self = this;
   p->Then([self, onSuccess, onFailure, windowID, c, windowListener,
            sourceListener, askPermission, prefs, isHTTPS, isHandlingUserInput,
            callID, principalInfo, isChrome, resistFingerprinting](SourceSet*& aDevices) mutable {
     LOG(("GetUserMedia: post enumeration pledge success callback starting"));
     // grab result
     auto devices = MakeRefPtr<Refcountable<UniquePtr<SourceSet>>>(aDevices);
 
@@ -3078,24 +3116,51 @@ MediaManager::EnumerateDevices(nsPIDOMWi
     AddWindowID(windowId, windowListener);
   }
 
   // Create an inactive SourceListener to act as a placeholder, so the
   // window listener doesn't clean itself up until we're done.
   RefPtr<SourceListener> sourceListener = new SourceListener();
   windowListener->Register(sourceListener);
 
-  bool fake = Preferences::GetBool("media.navigator.streams.fake") ||
-              nsContentUtils::ResistFingerprinting(aCallerType);
+  DeviceEnumerationType videoEnumerationType = Normal;
+  DeviceEnumerationType audioEnumerationType = Normal;
+  bool resistFingerprinting = nsContentUtils::ResistFingerprinting(aCallerType);
+
+  // In order of precedence: resist fingerprinting > loopback > fake pref
+  if (resistFingerprinting) {
+    videoEnumerationType = Fake;
+    audioEnumerationType = Fake;
+  } else {
+    // Handle loopback and fake requests
+    nsAutoCString videoLoopDev, audioLoopDev;
+    bool wantFakes = Preferences::GetBool("media.navigator.streams.fake");
+    // Video
+    Preferences::GetCString("media.video_loopback_dev", videoLoopDev);
+    // Loopback prefs take precedence over fake prefs
+    if (!videoLoopDev.IsEmpty()) {
+      videoEnumerationType = Loopback;
+    } else if (wantFakes) {
+      videoEnumerationType = Fake;
+    }
+    // Audio
+    Preferences::GetCString("media.audio_loopback_dev", audioLoopDev);
+    // Loopback prefs take precedence over fake prefs
+    if (!audioLoopDev.IsEmpty()) {
+      audioEnumerationType = Loopback;
+    } else if (wantFakes) {
+      audioEnumerationType = Fake;
+    }
+  }
 
   RefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowId,
                                                    MediaSourceEnum::Camera,
                                                    MediaSourceEnum::Microphone,
-                                                   fake ? Fake : Normal,
-                                                   fake ? Fake : Normal);
+                                                   videoEnumerationType,
+                                                   audioEnumerationType);
   p->Then([onSuccess, windowListener, sourceListener](SourceSet*& aDevices) mutable {
     UniquePtr<SourceSet> devices(aDevices); // grab result
     DebugOnly<bool> rv = windowListener->Remove(sourceListener);
     MOZ_ASSERT(rv);
     nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
     onSuccess->OnSuccess(array);
   }, [onFailure, windowListener, sourceListener](MediaStreamError*& reason) mutable {
     DebugOnly<bool> rv = windowListener->Remove(sourceListener);