Bug 1037389 - add support for deviceId in gUM constraints (merged 11 patches). r=smaug, r=jesup
authorJan-Ivar Bruaroey <jib@mozilla.com>
Thu, 02 Jul 2015 18:01:52 -0400
changeset 284613 0a8484884b6b45119eca37704ec07aca51631948
parent 284612 7a874cf3bf8e1378092bec6cc05a88738e1e0483
child 284614 3faf3368ce6f68b80d363eafc407117fb3523723
push id934
push userraliiev@mozilla.com
push dateMon, 26 Oct 2015 12:58:05 +0000
treeherdermozilla-release@05704e35c1d0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, jesup
bugs1037389
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1037389 - add support for deviceId in gUM constraints (merged 11 patches). r=smaug, r=jesup
dom/media/MediaManager.cpp
dom/media/MediaManager.h
dom/media/systemservices/MediaParent.cpp
dom/media/test/test_streams_individual_pause.html
dom/media/tests/mochitest/head.js
dom/media/tests/mochitest/test_enumerateDevices.html
dom/media/webrtc/MediaEngine.h
dom/media/webrtc/MediaEngineCameraVideoSource.cpp
dom/media/webrtc/MediaEngineCameraVideoSource.h
dom/media/webrtc/MediaEngineDefault.cpp
dom/media/webrtc/MediaEngineDefault.h
dom/media/webrtc/MediaEngineGonkVideoSource.cpp
dom/media/webrtc/MediaEngineGonkVideoSource.h
dom/media/webrtc/MediaEngineTabVideoSource.cpp
dom/media/webrtc/MediaEngineTabVideoSource.h
dom/media/webrtc/MediaEngineWebRTC.h
dom/media/webrtc/MediaEngineWebRTCAudio.cpp
dom/media/webrtc/MediaEngineWebRTCVideo.cpp
dom/media/webrtc/MediaTrackConstraints.cpp
dom/media/webrtc/MediaTrackConstraints.h
dom/webidl/MediaStream.webidl
dom/webidl/MediaTrackConstraintSet.webidl
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -293,61 +293,109 @@ protected:
   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
 };
 
 /**
  * nsIMediaDevice implementation.
  */
 NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
 
-MediaDevice::MediaDevice(MediaEngineSource* aSource)
-  : mSource(aSource) {
+MediaDevice::MediaDevice(MediaEngineSource* aSource, bool aIsVideo)
+  : mSource(aSource)
+  , mIsVideo(aIsVideo)
+{
   mSource->GetName(mName);
   nsCString id;
   mSource->GetUUID(id);
   CopyUTF8toUTF16(id, mID);
 }
 
 VideoDevice::VideoDevice(MediaEngineVideoSource* aSource)
-  : MediaDevice(aSource) {
+  : MediaDevice(aSource, true)
+{
   mMediaSource = aSource->GetMediaSource();
 }
 
 /**
  * Helper functions that implement the constraints algorithm from
  * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
  */
 
+bool
+MediaDevice::StringsContain(const OwningStringOrStringSequence& aStrings,
+                            nsString aN)
+{
+  return aStrings.IsString() ? aStrings.GetAsString() == aN
+                             : aStrings.GetAsStringSequence().Contains(aN);
+}
+
+/* static */ uint32_t
+MediaDevice::FitnessDistance(nsString aN,
+                             const ConstrainDOMStringParameters& aParams)
+{
+  if (aParams.mExact.WasPassed() && !StringsContain(aParams.mExact.Value(), aN)) {
+    return UINT32_MAX;
+  }
+  if (aParams.mIdeal.WasPassed() && !StringsContain(aParams.mIdeal.Value(), aN)) {
+    return 1;
+  }
+  return 0;
+}
+
+// Binding code doesn't templatize well...
+
+/* static */ uint32_t
+MediaDevice::FitnessDistance(nsString aN,
+    const OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint)
+{
+  if (aConstraint.IsString()) {
+    ConstrainDOMStringParameters params;
+    params.mIdeal.Construct();
+    params.mIdeal.Value().SetAsString() = aConstraint.GetAsString();
+    return FitnessDistance(aN, params);
+  } else if (aConstraint.IsStringSequence()) {
+    ConstrainDOMStringParameters params;
+    params.mIdeal.Construct();
+    params.mIdeal.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence();
+    return FitnessDistance(aN, params);
+  } else {
+    return FitnessDistance(aN, aConstraint.GetAsConstrainDOMStringParameters());
+  }
+}
+
 // Reminder: add handling for new constraints both here and in GetSources below!
 
 uint32_t
-VideoDevice::GetBestFitnessDistance(
+MediaDevice::GetBestFitnessDistance(
     const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets)
 {
-  // Interrogate device-inherent properties first.
-  for (const auto& constraint : aConstraintSets) {
-    nsString s;
-    GetMediaSource(s);
-    if (s != constraint->mMediaSource) {
-      return UINT32_MAX;
+  nsString mediaSource;
+  GetMediaSource(mediaSource);
+
+  // This code is reused for audio, where the mediaSource constraint does
+  // not currently have a function, but because it defaults to "camera" in
+  // webidl, we ignore it for audio here.
+  if (!mediaSource.EqualsASCII("microphone")) {
+    for (const auto& constraint : aConstraintSets) {
+      if (mediaSource != constraint->mMediaSource) {
+        return UINT32_MAX;
+      }
     }
   }
   // Forward request to underlying object to interrogate per-mode capabilities.
-  return GetSource()->GetBestFitnessDistance(aConstraintSets);
+  // Pass in device's origin-specific id for deviceId constraint comparison.
+  nsString id;
+  GetId(id);
+  return mSource->GetBestFitnessDistance(aConstraintSets, id);
 }
 
 AudioDevice::AudioDevice(MediaEngineAudioSource* aSource)
-  : MediaDevice(aSource) {}
-
-uint32_t
-AudioDevice::GetBestFitnessDistance(
-    const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets)
+  : MediaDevice(aSource, false)
 {
-  // TODO: Add audio-specific constraints
-  return 0;
+  mMediaSource = aSource->GetMediaSource();
 }
 
 NS_IMETHODIMP
 MediaDevice::GetName(nsAString& aName)
 {
   aName.Assign(mName);
   return NS_OK;
 }
@@ -406,16 +454,26 @@ VideoDevice::GetSource()
 }
 
 AudioDevice::Source*
 AudioDevice::GetSource()
 {
   return static_cast<Source*>(&*mSource);
 }
 
+nsresult VideoDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
+                               const MediaEnginePrefs &aPrefs) {
+  return GetSource()->Allocate(aConstraints, aPrefs, mID);
+}
+
+nsresult AudioDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
+                               const MediaEnginePrefs &aPrefs) {
+  return GetSource()->Allocate(aConstraints, aPrefs, mID);
+}
+
 /**
  * A subclass that we only use to stash internal pointers to MediaStreamGraph objects
  * that need to be cleaned up.
  */
 class nsDOMUserMediaStream : public DOMLocalMediaStream
 {
 public:
   static already_AddRefed<nsDOMUserMediaStream>
@@ -820,111 +878,153 @@ IsOn(const OwningBooleanOrMediaTrackCons
 
 static const MediaTrackConstraints&
 GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) {
   static const MediaTrackConstraints empty;
   return aUnion.IsMediaTrackConstraints() ?
       aUnion.GetAsMediaTrackConstraints() : empty;
 }
 
-// Source getter that constrains list returned
+// Source getter returning full list
 
-template<class DeviceType, class ConstraintsType>
+template<class DeviceType>
 static void
-  GetSources(MediaEngine *engine,
-             ConstraintsType &aConstraints,
-             void (MediaEngine::* aEnumerate)(dom::MediaSourceEnum,
-                 nsTArray<nsRefPtr<typename DeviceType::Source> >*),
-             nsTArray<nsRefPtr<DeviceType>>& aResult,
-             const char* media_device_name = nullptr)
+GetSources(MediaEngine *engine, dom::MediaSourceEnum aSrcType,
+           void (MediaEngine::* aEnumerate)(dom::MediaSourceEnum,
+               nsTArray<nsRefPtr<typename DeviceType::Source> >*),
+           nsTArray<nsRefPtr<DeviceType>>& aResult,
+           const char* media_device_name = nullptr)
 {
-  typedef nsTArray<nsRefPtr<DeviceType>> SourceSet;
-
-  nsString deviceName;
-  // First collect sources
-  SourceSet candidateSet;
-  {
-    nsTArray<nsRefPtr<typename DeviceType::Source> > sources;
+  nsTArray<nsRefPtr<typename DeviceType::Source>> sources;
 
-    MediaSourceEnum src = StringToEnum(dom::MediaSourceEnumValues::strings,
-                                       aConstraints.mMediaSource,
-                                       dom::MediaSourceEnum::Other);
-    (engine->*aEnumerate)(src, &sources);
-    /**
-      * We're allowing multiple tabs to access the same camera for parity
-      * with Chrome.  See bug 811757 for some of the issues surrounding
-      * this decision.  To disallow, we'd filter by IsAvailable() as we used
-      * to.
-      */
-    for (uint32_t len = sources.Length(), i = 0; i < len; i++) {
-      sources[i]->GetName(deviceName);
-      if (media_device_name && strlen(media_device_name) > 0)  {
-        if (deviceName.EqualsASCII(media_device_name)) {
-          candidateSet.AppendElement(new DeviceType(sources[i]));
-          break;
-        }
-      } else {
-        candidateSet.AppendElement(new DeviceType(sources[i]));
+  (engine->*aEnumerate)(aSrcType, &sources);
+  /**
+    * We're allowing multiple tabs to access the same camera for parity
+    * with Chrome.  See bug 811757 for some of the issues surrounding
+    * this decision.  To disallow, we'd filter by IsAvailable() as we used
+    * to.
+    */
+  if (media_device_name && *media_device_name)  {
+    for (auto& source : sources) {
+      nsString deviceName;
+      source->GetName(deviceName);
+      if (deviceName.EqualsASCII(media_device_name)) {
+        aResult.AppendElement(new DeviceType(source));
+        break;
       }
     }
+  } else {
+    for (auto& source : sources) {
+      aResult.AppendElement(new DeviceType(source));
+    }
   }
+}
 
-  // Apply constraints to the list of sources.
+// Apply constrains to a supplied list of sources (removes items from the list)
 
+template<class DeviceType>
+static void
+ApplyConstraints(const MediaTrackConstraints &aConstraints,
+                 nsTArray<nsRefPtr<DeviceType>>& aSources)
+{
   auto& c = aConstraints;
 
   // First apply top-level constraints.
 
   // Stack constraintSets that pass, starting with the required one, because the
   // whole stack must be re-satisfied each time a capability-set is ruled out
   // (this avoids storing state or pushing algorithm into the lower-level code).
   nsTArray<const MediaTrackConstraintSet*> aggregateConstraints;
   aggregateConstraints.AppendElement(&c);
 
   std::multimap<uint32_t, nsRefPtr<DeviceType>> ordered;
 
-  for (uint32_t i = 0; i < candidateSet.Length();) {
-    uint32_t distance = candidateSet[i]->GetBestFitnessDistance(aggregateConstraints);
+  for (uint32_t i = 0; i < aSources.Length();) {
+    uint32_t distance = aSources[i]->GetBestFitnessDistance(aggregateConstraints);
     if (distance == UINT32_MAX) {
-      candidateSet.RemoveElementAt(i);
+      aSources.RemoveElementAt(i);
     } else {
       ordered.insert(std::pair<uint32_t, nsRefPtr<DeviceType>>(distance,
-                                                               candidateSet[i]));
+                                                               aSources[i]));
       ++i;
     }
   }
   // Order devices by shortest distance
   for (auto& ordinal : ordered) {
-    candidateSet.RemoveElement(ordinal.second);
-    candidateSet.AppendElement(ordinal.second);
+    aSources.RemoveElement(ordinal.second);
+    aSources.AppendElement(ordinal.second);
   }
 
   // Then apply advanced constraints.
 
   if (c.mAdvanced.WasPassed()) {
     auto &array = c.mAdvanced.Value();
 
     for (int i = 0; i < int(array.Length()); i++) {
       aggregateConstraints.AppendElement(&array[i]);
-      SourceSet rejects;
-      for (uint32_t j = 0; j < candidateSet.Length();) {
-        if (candidateSet[j]->GetBestFitnessDistance(aggregateConstraints) == UINT32_MAX) {
-          rejects.AppendElement(candidateSet[j]);
-          candidateSet.RemoveElementAt(j);
+      nsTArray<nsRefPtr<DeviceType>> rejects;
+      for (uint32_t j = 0; j < aSources.Length();) {
+        if (aSources[j]->GetBestFitnessDistance(aggregateConstraints) == UINT32_MAX) {
+          rejects.AppendElement(aSources[j]);
+          aSources.RemoveElementAt(j);
         } else {
           ++j;
         }
       }
-      if (!candidateSet.Length()) {
-        candidateSet.MoveElementsFrom(rejects);
+      if (!aSources.Length()) {
+        aSources.MoveElementsFrom(rejects);
         aggregateConstraints.RemoveElementAt(aggregateConstraints.Length() - 1);
       }
     }
   }
-  aResult.MoveElementsFrom(candidateSet);
+}
+
+static bool
+ApplyConstraints(MediaStreamConstraints &aConstraints,
+                 nsTArray<nsRefPtr<MediaDevice>>& aSources)
+{
+  // Since the advanced part of the constraints algorithm needs to know when
+  // a candidate set is overconstrained (zero members), we must split up the
+  // list into videos and audios, and put it back together again at the end.
+
+  bool overconstrained = false;
+  nsTArray<nsRefPtr<VideoDevice>> videos;
+  nsTArray<nsRefPtr<AudioDevice>> audios;
+
+  for (auto& source : aSources) {
+    if (source->mIsVideo) {
+      nsRefPtr<VideoDevice> video = static_cast<VideoDevice*>(source.get());
+      videos.AppendElement(video);
+    } else {
+      nsRefPtr<AudioDevice> audio = static_cast<AudioDevice*>(source.get());
+      audios.AppendElement(audio);
+    }
+  }
+  aSources.Clear();
+  MOZ_ASSERT(!aSources.Length());
+
+  if (IsOn(aConstraints.mVideo)) {
+    ApplyConstraints(GetInvariant(aConstraints.mVideo), videos);
+    if (!videos.Length()) {
+      overconstrained = true;
+    }
+    for (auto& video : videos) {
+      aSources.AppendElement(video);
+    }
+  }
+  if (IsOn(aConstraints.mAudio)) {
+    ApplyConstraints(GetInvariant(aConstraints.mAudio), audios);
+    if (!audios.Length()) {
+      overconstrained = true;
+    }
+    for (auto& audio : audios) {
+      aSources.AppendElement(audio);
+    }
+  }
+  return !overconstrained;
 }
 
 /**
  * Runs on a seperate thread and is responsible for enumerating devices.
  * Depending on whether a picture or stream was asked for, either
  * ProcessGetUserMedia is called, and the results are sent back to the DOM.
  *
  * Do not run this on the main thread. The success and error callbacks *MUST*
@@ -933,56 +1033,34 @@ static void
 class GetUserMediaTask : public Task
 {
 public:
   GetUserMediaTask(
     const MediaStreamConstraints& aConstraints,
     already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aOnSuccess,
     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aOnFailure,
     uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
-    MediaEnginePrefs &aPrefs)
+    MediaEnginePrefs &aPrefs,
+    MediaManager::SourceSet* aSourceSet)
     : mConstraints(aConstraints)
     , mOnSuccess(aOnSuccess)
     , mOnFailure(aOnFailure)
     , mWindowID(aWindowID)
     , mListener(aListener)
     , mPrefs(aPrefs)
     , mDeviceChosen(false)
-    , mBackend(nullptr)
-    , mManager(MediaManager::GetInstance())
-  {}
-
-  /**
-   * The caller can also choose to provide their own backend instead of
-   * using the one provided by MediaManager::GetBackend.
-   */
-  GetUserMediaTask(
-    const MediaStreamConstraints& aConstraints,
-    already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aOnSuccess,
-    already_AddRefed<nsIDOMGetUserMediaErrorCallback> aOnFailure,
-    uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
-    MediaEnginePrefs &aPrefs,
-    MediaEngine* aBackend)
-    : mConstraints(aConstraints)
-    , mOnSuccess(aOnSuccess)
-    , mOnFailure(aOnFailure)
-    , mWindowID(aWindowID)
-    , mListener(aListener)
-    , mPrefs(aPrefs)
-    , mDeviceChosen(false)
-    , mBackend(aBackend)
+    , mSourceSet(aSourceSet)
     , mManager(MediaManager::GetInstance())
   {}
 
   ~GetUserMediaTask() {
   }
 
   void
-  Fail(const nsAString& aName,
-       const nsAString& aMessage = EmptyString()) {
+  Fail(const nsAString& aName, const nsAString& aMessage = EmptyString()) {
     nsRefPtr<MediaMgrError> error = new MediaMgrError(aName, aMessage);
     nsRefPtr<ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>> runnable =
       new ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>(mOnSuccess,
                                                                    mOnFailure,
                                                                    *error,
                                                                    mWindowID);
     // These should be empty now
     MOZ_ASSERT(!mOnSuccess);
@@ -994,36 +1072,58 @@ public:
   }
 
   void
   Run()
   {
     MOZ_ASSERT(!NS_IsMainThread());
     MOZ_ASSERT(mOnSuccess);
     MOZ_ASSERT(mOnFailure);
+    MOZ_ASSERT(mDeviceChosen);
 
-    MediaEngine* backend = mBackend;
-    // Was a backend provided?
-    if (!backend) {
-      backend = mManager->GetBackend(mWindowID);
-    }
+    // Allocate a video or audio device and return a MediaStream via
+    // a GetUserMediaStreamRunnable.
+
+    nsresult rv;
 
-    // Was a device provided?
-    if (!mDeviceChosen) {
-      nsresult rv = SelectDevice(backend);
-      if (rv != NS_OK) {
+    if (mAudioDevice) {
+      rv = mAudioDevice->Allocate(GetInvariant(mConstraints.mAudio), mPrefs);
+      if (NS_FAILED(rv)) {
+        LOG(("Failed to allocate audiosource %d",rv));
+        Fail(NS_LITERAL_STRING("SourceUnavailableError"),
+             NS_LITERAL_STRING("Failed to allocate audiosource"));
         return;
       }
     }
+    if (mVideoDevice) {
+      rv = mVideoDevice->Allocate(GetInvariant(mConstraints.mVideo), mPrefs);
+      if (NS_FAILED(rv)) {
+        LOG(("Failed to allocate videosource %d\n",rv));
+        if (mAudioDevice) {
+          mAudioDevice->GetSource()->Deallocate();
+        }
+        Fail(NS_LITERAL_STRING("SourceUnavailableError"),
+             NS_LITERAL_STRING("Failed to allocate videosource"));
+        return;
+      }
+    }
+    PeerIdentity* peerIdentity = nullptr;
+    if (!mConstraints.mPeerIdentity.IsEmpty()) {
+      peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity);
+    }
 
-    // There's a bug in the permission code that can leave us with mAudio but no audio device
-    ProcessGetUserMedia(((IsOn(mConstraints.mAudio) && mAudioDevice) ?
-                         mAudioDevice->GetSource() : nullptr),
-                        ((IsOn(mConstraints.mVideo) && mVideoDevice) ?
-                         mVideoDevice->GetSource() : nullptr));
+    NS_DispatchToMainThread(new GetUserMediaStreamRunnable(
+      mOnSuccess, mOnFailure, mWindowID, mListener,
+      (mAudioDevice? mAudioDevice->GetSource() : nullptr),
+      (mVideoDevice? mVideoDevice->GetSource() : nullptr),
+      peerIdentity
+    ));
+
+    MOZ_ASSERT(!mOnSuccess);
+    MOZ_ASSERT(!mOnFailure);
   }
 
   nsresult
   Denied(const nsAString& aName,
          const nsAString& aMessage = EmptyString())
   {
     MOZ_ASSERT(mOnSuccess);
     MOZ_ASSERT(mOnFailure);
@@ -1075,121 +1175,31 @@ public:
   nsresult
   SetVideoDevice(VideoDevice* aVideoDevice)
   {
     mVideoDevice = aVideoDevice;
     mDeviceChosen = true;
     return NS_OK;
   }
 
-  nsresult
-  SelectDevice(MediaEngine* backend)
-  {
-    MOZ_ASSERT(mOnSuccess);
-    MOZ_ASSERT(mOnFailure);
-
-    if (!IsOn(mConstraints.mVideo) && !IsOn(mConstraints.mAudio)) {
-      Fail(NS_LITERAL_STRING("NotSupportedError"));
-      return NS_ERROR_FAILURE;
-    }
-    if (IsOn(mConstraints.mVideo)) {
-      nsTArray<nsRefPtr<VideoDevice>> sources;
-      GetSources(backend, GetInvariant(mConstraints.mVideo),
-                 &MediaEngine::EnumerateVideoDevices, sources);
-      if (!sources.Length()) {
-        Fail(NS_LITERAL_STRING("NotFoundError"));
-        return NS_ERROR_FAILURE;
-      }
-      // Pick the first available device.
-      mVideoDevice = sources[0];
-      LOG(("Selected video device"));
-    }
-    if (IsOn(mConstraints.mAudio)) {
-      nsTArray<nsRefPtr<AudioDevice>> sources;
-      GetSources(backend, GetInvariant(mConstraints.mAudio),
-                 &MediaEngine::EnumerateAudioDevices, sources);
-      if (!sources.Length()) {
-        Fail(NS_LITERAL_STRING("NotFoundError"));
-        return NS_ERROR_FAILURE;
-      }
-      // Pick the first available device.
-      mAudioDevice = sources[0];
-      LOG(("Selected audio device"));
-    }
-
-    if (!mAudioDevice && !mVideoDevice) {
-      Fail(NS_LITERAL_STRING("NotFoundError"));
-      return NS_ERROR_FAILURE;
-    }
-
-    return NS_OK;
-  }
-
-  /**
-   * Allocates a video or audio device and returns a MediaStream via
-   * a GetUserMediaStreamRunnable. Runs off the main thread.
-   */
-  void
-  ProcessGetUserMedia(MediaEngineAudioSource* aAudioSource,
-                      MediaEngineVideoSource* aVideoSource)
-  {
-    MOZ_ASSERT(mOnSuccess);
-    MOZ_ASSERT(mOnFailure);
-    nsresult rv;
-    if (aAudioSource) {
-      rv = aAudioSource->Allocate(GetInvariant(mConstraints.mAudio), mPrefs);
-      if (NS_FAILED(rv)) {
-        LOG(("Failed to allocate audiosource %d",rv));
-        Fail(NS_LITERAL_STRING("SourceUnavailableError"),
-             NS_LITERAL_STRING("Failed to allocate audiosource"));
-        return;
-      }
-    }
-    if (aVideoSource) {
-      rv = aVideoSource->Allocate(GetInvariant(mConstraints.mVideo), mPrefs);
-      if (NS_FAILED(rv)) {
-        LOG(("Failed to allocate videosource %d\n",rv));
-        if (aAudioSource) {
-          aAudioSource->Deallocate();
-        }
-        Fail(NS_LITERAL_STRING("SourceUnavailableError"),
-             NS_LITERAL_STRING("Failed to allocate videosource"));
-        return;
-      }
-    }
-    PeerIdentity* peerIdentity = nullptr;
-    if (!mConstraints.mPeerIdentity.IsEmpty()) {
-      peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity);
-    }
-
-    NS_DispatchToMainThread(new GetUserMediaStreamRunnable(
-      mOnSuccess, mOnFailure, mWindowID, mListener, aAudioSource, aVideoSource,
-      peerIdentity
-    ));
-
-    MOZ_ASSERT(!mOnSuccess);
-    MOZ_ASSERT(!mOnFailure);
-
-    return;
-  }
-
 private:
   MediaStreamConstraints mConstraints;
 
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
   uint64_t mWindowID;
   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   nsRefPtr<AudioDevice> mAudioDevice;
   nsRefPtr<VideoDevice> mVideoDevice;
   MediaEnginePrefs mPrefs;
 
   bool mDeviceChosen;
-
-  RefPtr<MediaEngine> mBackend;
+public:
+  nsAutoPtr<MediaManager::SourceSet> mSourceSet;
+private:
   nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
 };
 
 #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
 class GetUserMediaRunnableWrapper : public nsRunnable
 {
 public:
   // This object must take ownership of task
@@ -1218,60 +1228,74 @@ static auto& MediaManager_ToJSArray = Me
 static auto& MediaManager_AnonymizeDevices = MediaManager::AnonymizeDevices;
 
 /**
  * EnumerateRawDevices - Enumerate a list of audio & video devices that
  * satisfy passed-in constraints. List contains raw id's.
  */
 
 already_AddRefed<MediaManager::PledgeSourceSet>
-MediaManager::EnumerateRawDevices(uint64_t aWindowId,
-                                  const MediaStreamConstraints& aConstraints)
+MediaManager::EnumerateRawDevices(uint64_t aWindowId, MediaSourceEnum aVideoType,
+                                  bool aFake, bool aFakeTracks)
 {
   MOZ_ASSERT(NS_IsMainThread());
   nsRefPtr<PledgeSourceSet> p = new PledgeSourceSet();
   uint32_t id = mOutstandingPledges.Append(*p);
 
   // Check if the preference for using audio/video loopback devices is
   // enabled. This is currently used for automated media tests only.
-  auto audioLoopDev = Preferences::GetCString("media.audio_loopback_dev");
-  auto videoLoopDev = Preferences::GetCString("media.video_loopback_dev");
-  bool fake = Preferences::GetBool("media.navigator.streams.fake", false);
+  //
+  // If present (and we're doing non-exotic cameras and microphones) use them
+  // instead of our built-in fake devices, except if fake tracks are requested
+  // (a feature of the built-in ones only).
+
+  nsAdoptingCString audioLoopDev, videoLoopDev;
+  if (!aFakeTracks) {
+    if (aVideoType == dom::MediaSourceEnum::Camera) {
+      audioLoopDev = Preferences::GetCString("media.audio_loopback_dev");
+      videoLoopDev = Preferences::GetCString("media.video_loopback_dev");
 
-  MediaManager::PostTask(FROM_HERE, NewTaskFrom([id, aConstraints, aWindowId,
-                                                 audioLoopDev,  videoLoopDev,
-                                                 fake]() mutable {
+      if (aFake && !audioLoopDev.IsEmpty() && !videoLoopDev.IsEmpty()) {
+        aFake = false;
+      }
+    } else {
+      aFake = false;
+    }
+  }
+
+  MediaManager::PostTask(FROM_HERE, NewTaskFrom([id, aWindowId, audioLoopDev,
+                                                 videoLoopDev, aVideoType,
+                                                 aFake, aFakeTracks]() mutable {
     nsRefPtr<MediaEngine> backend;
-    if (aConstraints.mFake || fake) {
-      backend = new MediaEngineDefault(aConstraints.mFakeTracks);
+    if (aFake) {
+      backend = new MediaEngineDefault(aFakeTracks);
     } else {
       nsRefPtr<MediaManager> manager = MediaManager_GetInstance();
       backend = manager->GetBackend(aWindowId);
     }
 
     ScopedDeletePtr<SourceSet> result(new SourceSet);
-    if (IsOn(aConstraints.mVideo)) {
-      nsTArray<nsRefPtr<VideoDevice>> sources;
-      GetSources(backend, GetInvariant(aConstraints.mVideo),
-                 &MediaEngine::EnumerateVideoDevices, sources, videoLoopDev);
-      for (auto& source : sources) {
-        result->AppendElement(source);
-      }
+
+    nsTArray<nsRefPtr<VideoDevice>> videos;
+    GetSources(backend, aVideoType, &MediaEngine::EnumerateVideoDevices, videos,
+               videoLoopDev);
+    for (auto& source : videos) {
+      result->AppendElement(source);
     }
-    if (IsOn(aConstraints.mAudio)) {
-      nsTArray<nsRefPtr<AudioDevice>> sources;
-      GetSources(backend, GetInvariant(aConstraints.mAudio),
-                 &MediaEngine::EnumerateAudioDevices, sources, audioLoopDev);
-      for (auto& source : sources) {
-        result->AppendElement(source);
-      }
+
+    nsTArray<nsRefPtr<AudioDevice>> audios;
+    GetSources(backend, dom::MediaSourceEnum::Microphone,
+               &MediaEngine::EnumerateAudioDevices, audios, audioLoopDev);
+    for (auto& source : audios) {
+      result->AppendElement(source);
     }
+
     SourceSet* handoff = result.forget();
     NS_DispatchToMainThread(NewRunnableFrom([id, handoff]() mutable {
-      ScopedDeletePtr<SourceSet> result(handoff);
+      ScopedDeletePtr<SourceSet> result(handoff); // grab result
       nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
       if (!mgr) {
         return NS_OK;
       }
       nsRefPtr<PledgeSourceSet> p = mgr->mOutstandingPledges.Remove(id);
       if (p) {
         p->Resolve(result.forget());
       }
@@ -1340,29 +1364,34 @@ MediaManager::Get() {
       MOZ_CRASH();
     }
 
     LOG(("New Media thread for gum"));
 
     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
     if (obs) {
       obs->AddObserver(sSingleton, "xpcom-will-shutdown", false);
+      obs->AddObserver(sSingleton, "getUserMedia:privileged:allow", false);
       obs->AddObserver(sSingleton, "getUserMedia:response:allow", false);
       obs->AddObserver(sSingleton, "getUserMedia:response:deny", false);
       obs->AddObserver(sSingleton, "getUserMedia:revoke", false);
       obs->AddObserver(sSingleton, "phone-state-changed", false);
     }
     // else MediaManager won't work properly and will leak (see bug 837874)
     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
     if (prefs) {
       prefs->AddObserver("media.navigator.video.default_width", sSingleton, false);
       prefs->AddObserver("media.navigator.video.default_height", sSingleton, false);
       prefs->AddObserver("media.navigator.video.default_fps", sSingleton, false);
       prefs->AddObserver("media.navigator.video.default_minfps", sSingleton, false);
     }
+#ifdef MOZ_B2G
+    // Init MediaPermissionManager before sending out any permission requests.
+    (void) MediaPermissionManager::GetInstance();
+#endif //MOZ_B2G
   }
   return sSingleton;
 }
 
 /* static */  MediaManager*
 MediaManager::GetIfExists() {
   return sSingleton;
 }
@@ -1455,114 +1484,184 @@ MediaManager::NotifyRecordingStatusChang
                                                                    requestURL,
                                                                    aIsAudio,
                                                                    aIsVideo);
   }
 
   return NS_OK;
 }
 
+bool MediaManager::IsPrivileged()
+{
+  bool permission = nsContentUtils::IsCallerChrome();
+
+  // Developer preference for turning off permission check.
+  if (Preferences::GetBool("media.navigator.permission.disabled", false)) {
+    permission = true;
+  }
+  return permission;
+}
+
+bool MediaManager::IsLoop(nsIURI* aDocURI)
+{
+  MOZ_ASSERT(aDocURI);
+
+  nsCOMPtr<nsIURI> loopURI;
+  nsresult rv = NS_NewURI(getter_AddRefs(loopURI), "about:loopconversation");
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+  bool result = false;
+  rv = aDocURI->EqualsExceptRef(loopURI, &result);
+  NS_ENSURE_SUCCESS(rv, false);
+  return result;
+}
+
+bool MediaManager::IsPrivateBrowsing(nsPIDOMWindow *window)
+{
+  nsCOMPtr<nsIDocument> doc = window->GetDoc();
+  nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
+  return loadContext && loadContext->UsePrivateBrowsing();
+}
+
+nsresult MediaManager::GenerateUUID(nsAString& aResult)
+{
+  nsresult rv;
+  nsCOMPtr<nsIUUIDGenerator> uuidgen =
+      do_GetService("@mozilla.org/uuid-generator;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Generate a call ID.
+  nsID id;
+  rv = uuidgen->GenerateUUIDInPlace(&id);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  char buffer[NSID_LENGTH];
+  id.ToProvidedString(buffer);
+  aResult.Assign(NS_ConvertUTF8toUTF16(buffer));
+  return NS_OK;
+}
+
 /**
  * The entry point for this file. A call from Navigator::mozGetUserMedia
  * will end up here. MediaManager is a singleton that is responsible
  * for handling all incoming getUserMedia calls from every window.
  */
 nsresult
 MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
-                           const MediaStreamConstraints& aConstraints,
+                           const MediaStreamConstraints& aConstraintsPassedIn,
                            nsIDOMGetUserMediaSuccessCallback* aOnSuccess,
                            nsIDOMGetUserMediaErrorCallback* aOnFailure)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aWindow);
   MOZ_ASSERT(aOnFailure);
   MOZ_ASSERT(aOnSuccess);
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess(aOnSuccess);
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
+  uint64_t windowID = aWindow->WindowID();
 
-  bool privileged = nsContentUtils::IsCallerChrome();
+  MediaStreamConstraints c(aConstraintsPassedIn); // use a modifiable copy
 
-  MediaStreamConstraints c(aConstraints); // copy
+  // Do all the validation we can while we're sync (to return an
+  // already-rejected promise on failure).
 
-  static bool created = false;
-  if (!created) {
-    // Force MediaManager to startup before we try to access it from other threads
-    // Hack: should init singleton earlier unless it's expensive (mem or CPU)
-    (void) MediaManager::Get();
-#ifdef MOZ_B2G
-    // Initialize MediaPermissionManager before send out any permission request.
-    (void) MediaPermissionManager::GetInstance();
-#endif //MOZ_B2G
+  if (!IsOn(c.mVideo) && !IsOn(c.mAudio)) {
+    nsRefPtr<MediaStreamError> error =
+        new MediaStreamError(aWindow,
+                             NS_LITERAL_STRING("NotSupportedError"),
+                             NS_LITERAL_STRING("audio and/or video is required"));
+    onFailure->OnError(error);
+    return NS_OK;
+  }
+  if (sInShutdown) {
+    nsRefPtr<MediaStreamError> error =
+        new MediaStreamError(aWindow,
+                             NS_LITERAL_STRING("AbortError"),
+                             NS_LITERAL_STRING("In shutdown"));
+    onFailure->OnError(error);
+    return NS_OK;
   }
 
-  uint64_t windowID = aWindow->WindowID();
-  StreamListeners* listeners = AddWindowID(windowID);
-
-  // Create a disabled listener to act as a placeholder
-  GetUserMediaCallbackMediaStreamListener* listener =
-    new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowID);
+  // Determine permissions early (while we still have a stack).
 
-  // No need for locking because we always do this in the main thread.
-  listeners->AppendElement(listener);
+  nsIURI* docURI = aWindow->GetDocumentURI();
+  bool loop = IsLoop(docURI);
+  bool privileged = loop || IsPrivileged();
+  bool isHTTPS = false;
+  if (docURI) {
+    docURI->SchemeIs("https", &isHTTPS);
+  }
 
-  // Developer preference for turning off permission check.
-  if (Preferences::GetBool("media.navigator.permission.disabled", false)) {
-    privileged = true;
-  }
   if (!Preferences::GetBool("media.navigator.video.enabled", true)) {
     c.mVideo.SetAsBoolean() = false;
   }
-  bool fake = true;
-  if (!c.mFake &&
-      !Preferences::GetBool("media.navigator.streams.fake", false)) {
-    fake = false;
-  }
 
-  // Pass callbacks and MediaStreamListener along to GetUserMediaTask.
-  nsAutoPtr<GetUserMediaTask> task;
-  if (fake) {
-    // Fake stream from default backend.
-    task = new GetUserMediaTask(c, onSuccess.forget(),
-      onFailure.forget(), windowID, listener, mPrefs, new MediaEngineDefault(c.mFakeTracks));
-  } else {
-    // Stream from default device from WebRTC backend.
-    task = new GetUserMediaTask(c, onSuccess.forget(),
-      onFailure.forget(), windowID, listener, mPrefs);
-  }
-  if (sInShutdown) {
-    return task->Denied(NS_LITERAL_STRING("In shutdown"));
-  }
-
-  nsIURI* docURI = aWindow->GetDocumentURI();
-
-  bool isLoop = false;
-  nsCOMPtr<nsIURI> loopURI;
-  nsresult rv = NS_NewURI(getter_AddRefs(loopURI), "about:loopconversation");
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = docURI->EqualsExceptRef(loopURI, &isLoop);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (isLoop) {
-    privileged = true;
-  }
+  MediaSourceEnum videoType = dom::MediaSourceEnum::Camera;
 
   if (c.mVideo.IsMediaTrackConstraints()) {
     auto& vc = c.mVideo.GetAsMediaTrackConstraints();
-    MediaSourceEnum src = StringToEnum(dom::MediaSourceEnumValues::strings,
-                                       vc.mMediaSource,
-                                       dom::MediaSourceEnum::Other);
-    if (vc.mAdvanced.WasPassed()) {
-      if (src != dom::MediaSourceEnum::Camera) {
-        // iterate through advanced, forcing mediaSource to match "root"
-        const char *camera = EnumToASCII(dom::MediaSourceEnumValues::strings,
-                                         dom::MediaSourceEnum::Camera);
-        for (MediaTrackConstraintSet& cs : vc.mAdvanced.Value()) {
-          if (cs.mMediaSource.EqualsASCII(camera)) {
-            cs.mMediaSource = vc.mMediaSource;
-          }
+    videoType = StringToEnum(dom::MediaSourceEnumValues::strings,
+                             vc.mMediaSource,
+                             videoType);
+    switch (videoType) {
+      case dom::MediaSourceEnum::Camera:
+        break;
+
+      case dom::MediaSourceEnum::Browser:
+      case dom::MediaSourceEnum::Screen:
+      case dom::MediaSourceEnum::Application:
+      case dom::MediaSourceEnum::Window:
+        // Deny screensharing request if support is disabled, or
+        // the requesting document is not from a host on the whitelist, or
+        // we're on Mac OSX 10.6 and WinXP until proved that they work
+        if (!Preferences::GetBool(((videoType == dom::MediaSourceEnum::Browser)?
+                                   "media.getusermedia.browser.enabled" :
+                                   "media.getusermedia.screensharing.enabled"),
+                                  false) ||
+#if defined(XP_MACOSX) || defined(XP_WIN)
+            (
+              // Allow tab sharing for all platforms including XP and OSX 10.6
+              (videoType != dom::MediaSourceEnum::Browser) &&
+              !Preferences::GetBool("media.getusermedia.screensharing.allow_on_old_platforms",
+                                    false) &&
+#if defined(XP_MACOSX)
+              !nsCocoaFeatures::OnLionOrLater()
+#endif
+#if defined (XP_WIN)
+              !IsVistaOrLater()
+#endif
+              ) ||
+#endif
+            (!privileged && !HostHasPermission(*docURI))) {
+          nsRefPtr<MediaStreamError> error =
+              new MediaStreamError(aWindow,
+                                   NS_LITERAL_STRING("PermissionDeniedError"));
+          onFailure->OnError(error);
+          return NS_OK;
+        }
+        break;
+
+      case dom::MediaSourceEnum::Microphone:
+      case dom::MediaSourceEnum::Other:
+      default: {
+        nsRefPtr<MediaStreamError> error =
+            new MediaStreamError(aWindow, NS_LITERAL_STRING("NotFoundError"));
+        onFailure->OnError(error);
+        return NS_OK;
+      }
+    }
+
+    if (vc.mAdvanced.WasPassed() && videoType != dom::MediaSourceEnum::Camera) {
+      // iterate through advanced, forcing mediaSource to match "root"
+      const char *camera = EnumToASCII(dom::MediaSourceEnumValues::strings,
+                                       dom::MediaSourceEnum::Camera);
+      for (MediaTrackConstraintSet& cs : vc.mAdvanced.Value()) {
+        if (cs.mMediaSource.EqualsASCII(camera)) {
+          cs.mMediaSource = vc.mMediaSource;
         }
       }
     }
     if (!privileged) {
       // only allow privileged content to set the window id
       if (vc.mBrowserWindow.WasPassed()) {
         vc.mBrowserWindow.Construct(-1);
       }
@@ -1570,85 +1669,37 @@ MediaManager::GetUserMedia(nsPIDOMWindow
         for (MediaTrackConstraintSet& cs : vc.mAdvanced.Value()) {
           if (cs.mBrowserWindow.WasPassed()) {
             cs.mBrowserWindow.Construct(-1);
           }
         }
       }
     }
 
-    switch (src) {
-    case dom::MediaSourceEnum::Camera:
-      break;
-
-    case dom::MediaSourceEnum::Browser:
-    case dom::MediaSourceEnum::Screen:
-    case dom::MediaSourceEnum::Application:
-    case dom::MediaSourceEnum::Window:
-      // Deny screensharing request if support is disabled, or
-      // the requesting document is not from a host on the whitelist, or
-      // we're on Mac OSX 10.6 and WinXP until proved that they work
-      if (!Preferences::GetBool(((src == dom::MediaSourceEnum::Browser)?
-                                "media.getusermedia.browser.enabled" :
-                                "media.getusermedia.screensharing.enabled"),
-                                false) ||
-#if defined(XP_MACOSX) || defined(XP_WIN)
-          (
-            // Allow tab sharing for all platforms including XP and OSX 10.6
-            (src != dom::MediaSourceEnum::Browser) &&
-            !Preferences::GetBool("media.getusermedia.screensharing.allow_on_old_platforms",
-                                  false) &&
-#if defined(XP_MACOSX)
-            !nsCocoaFeatures::OnLionOrLater()
-#endif
-#if defined (XP_WIN)
-            !IsVistaOrLater()
-#endif
-            ) ||
-#endif
-          (!privileged && !HostHasPermission(*docURI))) {
-        return task->Denied(NS_LITERAL_STRING("PermissionDeniedError"));
-      }
-      break;
-
-    case dom::MediaSourceEnum::Microphone:
-    case dom::MediaSourceEnum::Other:
-    default:
-      return task->Denied(NS_LITERAL_STRING("NotFoundError"));
-    }
-
     // For all but tab sharing, Loop needs to prompt as we are using the
     // permission menu for selection of the device currently. For tab sharing,
     // Loop has implicit permissions within Firefox, as it is built-in,
     // and will manage the active tab and provide appropriate UI.
-    if (isLoop &&
-        (src == dom::MediaSourceEnum::Window ||
-         src == dom::MediaSourceEnum::Application ||
-         src == dom::MediaSourceEnum::Screen)) {
+    if (loop && (videoType == dom::MediaSourceEnum::Window ||
+                 videoType == dom::MediaSourceEnum::Application ||
+                 videoType == dom::MediaSourceEnum::Screen)) {
        privileged = false;
     }
   }
+  StreamListeners* listeners = AddWindowID(windowID);
 
-#if defined(MOZ_B2G_CAMERA) && defined(MOZ_WIDGET_GONK)
-  if (mCameraManager == nullptr) {
-    mCameraManager = nsDOMCameraManager::CreateInstance(aWindow);
-  }
-#endif
+  // Create a disabled listener to act as a placeholder
+  nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener =
+    new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowID);
 
-  // XXX No full support for picture in Desktop yet (needs proper UI)
-  if (privileged ||
-      (fake && !Preferences::GetBool("media.navigator.permission.fake"))) {
-    MediaManager::PostTask(FROM_HERE, task.forget());
-  } else {
-    bool isHTTPS = false;
-    if (docURI) {
-      docURI->SchemeIs("https", &isHTTPS);
-    }
+  // No need for locking because we always do this in the main thread.
+  listeners->AppendElement(listener);
 
-    // Check if this site has persistent permissions.
+  if (!privileged) {
+    // Check if this site has had persistent permissions denied.
     nsresult rv;
     nsCOMPtr<nsIPermissionManager> permManager =
       do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     uint32_t audioPerm = nsIPermissionManager::UNKNOWN_ACTION;
     if (IsOn(c.mAudio)) {
       rv = permManager->TestExactPermissionFromPrincipal(
@@ -1660,57 +1711,116 @@ MediaManager::GetUserMedia(nsPIDOMWindow
     if (IsOn(c.mVideo)) {
       rv = permManager->TestExactPermissionFromPrincipal(
         aWindow->GetExtantDoc()->NodePrincipal(), "camera", &videoPerm);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     if ((!IsOn(c.mAudio) || audioPerm == nsIPermissionManager::DENY_ACTION) &&
         (!IsOn(c.mVideo) || videoPerm == nsIPermissionManager::DENY_ACTION)) {
-      return task->Denied(NS_LITERAL_STRING("PermissionDeniedError"));
+      nsRefPtr<MediaStreamError> error =
+          new MediaStreamError(aWindow, NS_LITERAL_STRING("PermissionDeniedError"));
+      onFailure->OnError(error);
+      RemoveFromWindowList(windowID, listener);
+      return NS_OK;
+    }
+  }
+
+#if defined(MOZ_B2G_CAMERA) && defined(MOZ_WIDGET_GONK)
+  if (mCameraManager == nullptr) {
+    mCameraManager = nsDOMCameraManager::CreateInstance(aWindow);
+  }
+#endif
+
+  // Get list of all devices, with origin-specific device ids.
+
+  MediaEnginePrefs prefs = mPrefs;
+
+  nsString callID;
+  nsresult rv = GenerateUUID(callID);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool fake = c.mFake.WasPassed()? c.mFake.Value() :
+      Preferences::GetBool("media.navigator.streams.fake");
+
+  bool fakeTracks = c.mFakeTracks.WasPassed()? c.mFakeTracks.Value() : false;
+
+  bool askPermission = !privileged &&
+      (!fake || Preferences::GetBool("media.navigator.permission.fake"));
+
+  nsRefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowID, videoType,
+                                                     fake, fakeTracks);
+  p->Then([this, onSuccess, onFailure, windowID, c, listener,
+           askPermission, prefs, isHTTPS, callID](SourceSet*& aDevices) mutable {
+    ScopedDeletePtr<SourceSet> devices(aDevices); // grab result
+
+    // Ensure this pointer is still valid, and window is still alive.
+    nsRefPtr<MediaManager> mgr = MediaManager::GetInstance();
+    nsRefPtr<nsPIDOMWindow> window = static_cast<nsPIDOMWindow*>
+        (nsGlobalWindow::GetInnerWindowWithId(windowID));
+    if (!mgr || !window) {
+      return;
     }
 
-    // Ask for user permission, and dispatch task (or not) when a response
-    // is received via an observer notification. Each call is paired with its
-    // task by a GUID.
-    nsCOMPtr<nsIUUIDGenerator> uuidgen =
-      do_GetService("@mozilla.org/uuid-generator;1", &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
+    // Apply any constraints. This modifies the list.
+
+    if (!ApplyConstraints(c, *devices)) {
+      nsRefPtr<MediaStreamError> error =
+          new MediaStreamError(window, NS_LITERAL_STRING("NotFoundError"));
+      onFailure->OnError(error);
+      return;
+    }
 
-    // Generate a call ID.
-    nsID id;
-    rv = uuidgen->GenerateUUIDInPlace(&id);
-    NS_ENSURE_SUCCESS(rv, rv);
+    nsCOMPtr<nsISupportsArray> devicesCopy; // before we give up devices below
+    if (!askPermission) {
+      nsresult rv = NS_NewISupportsArray(getter_AddRefs(devicesCopy));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return;
+      }
+      for (auto& device : *devices) {
+        rv = devicesCopy->AppendElement(device);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return;
+        }
+      }
+    }
 
-    char buffer[NSID_LENGTH];
-    id.ToProvidedString(buffer);
-    NS_ConvertUTF8toUTF16 callID(buffer);
-
-    // Store the current unarmed task w/callbacks.
+    // Pass callbacks and MediaStreamListener along to GetUserMediaTask.
+    nsAutoPtr<GetUserMediaTask> task (new GetUserMediaTask(c, onSuccess.forget(),
+                                                           onFailure.forget(),
+                                                           windowID, listener,
+                                                           prefs,
+                                                           devices.forget()));
+    // Store the task w/callbacks.
     mActiveCallbacks.Put(callID, task.forget());
 
     // Add a WindowID cross-reference so OnNavigation can tear things down
     nsTArray<nsString>* array;
     if (!mCallIds.Get(windowID, &array)) {
       array = new nsTArray<nsString>();
-      array->AppendElement(callID);
       mCallIds.Put(windowID, array);
-    } else {
-      array->AppendElement(callID);
     }
+    array->AppendElement(callID);
+
     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-    nsRefPtr<GetUserMediaRequest> req = new GetUserMediaRequest(aWindow,
-                                                                callID, c, isHTTPS);
-    obs->NotifyObservers(req, "getUserMedia:request", nullptr);
-  }
+    if (!askPermission) {
+      obs->NotifyObservers(devicesCopy, "getUserMedia:privileged:allow",
+                           callID.BeginReading());
+    } else {
+      nsRefPtr<GetUserMediaRequest> req =
+          new GetUserMediaRequest(window, callID, c, isHTTPS);
+      obs->NotifyObservers(req, "getUserMedia:request", nullptr);
+    }
 
 #ifdef MOZ_WEBRTC
-  EnableWebRtcLog();
+    EnableWebRtcLog();
 #endif
-
+  }, [onFailure](MediaStreamError& reason) mutable {
+    onFailure->OnError(&reason);
+  });
   return NS_OK;
 }
 
 /* static */ void
 MediaManager::AnonymizeDevices(SourceSet& aDevices, const nsACString& aOriginKey)
 {
   if (!aOriginKey.IsEmpty()) {
     for (auto& device : aDevices) {
@@ -1788,52 +1898,49 @@ MediaManager::ToJSArray(SourceSet& aDevi
     }
   } else {
     var->SetAsEmptyArray(); // because SetAsArray() fails on zero length arrays.
   }
   return var.forget();
 }
 
 already_AddRefed<MediaManager::PledgeSourceSet>
-MediaManager::EnumerateDevicesImpl(uint64_t aWindowId,
-                                   const MediaStreamConstraints& aConstraints)
+MediaManager::EnumerateDevicesImpl(uint64_t aWindowId, MediaSourceEnum aVideoType,
+                                   bool aFake, bool aFakeTracks)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
+      (nsGlobalWindow::GetInnerWindowWithId(aWindowId));
 
   // This function returns a pledge, a promise-like object with the future result
   nsRefPtr<PledgeSourceSet> pledge = new PledgeSourceSet();
   uint32_t id = mOutstandingPledges.Append(*pledge);
 
   // To get a device list anonymized for a particular origin, we must:
   // 1. Get an origin-key (for either regular or private browsing)
   // 2. Get the raw devices list
   // 3. Anonymize the raw list with the origin-key.
 
+  bool privateBrowsing = IsPrivateBrowsing(window);
   nsCString origin;
-  bool privateBrowsing;
-  {
-    nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
-        (nsGlobalWindow::GetInnerWindowWithId(aWindowId));
-    nsPrincipal::GetOriginForURI(window->GetDocumentURI(), origin);
+  nsPrincipal::GetOriginForURI(window->GetDocumentURI(), origin);
 
-    nsCOMPtr<nsIDocument> doc = window->GetDoc();
-    nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
-    privateBrowsing = loadContext && loadContext->UsePrivateBrowsing();
-  }
   // GetOriginKey is an async API that returns a pledge (a promise-like
   // pattern). We use .Then() to pass in a lambda to run back on this same
   // thread later once GetOriginKey resolves. Needed variables are "captured"
   // (passed by value) safely into the lambda.
 
   nsRefPtr<Pledge<nsCString>> p = media::GetOriginKey(origin, privateBrowsing);
-  p->Then([id, aWindowId, aConstraints](const nsCString& aOriginKey) mutable {
+  p->Then([id, aWindowId, aVideoType,
+           aFake, aFakeTracks](const nsCString& aOriginKey) mutable {
     MOZ_ASSERT(NS_IsMainThread());
     nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
 
-    nsRefPtr<PledgeSourceSet> p = mgr->EnumerateRawDevices(aWindowId, aConstraints);
+    nsRefPtr<PledgeSourceSet> p = mgr->EnumerateRawDevices(aWindowId, aVideoType,
+                                                           aFake, aFakeTracks);
     p->Then([id, aWindowId, aOriginKey](SourceSet*& aDevices) mutable {
       ScopedDeletePtr<SourceSet> devices(aDevices); // secondary result
 
       // Only run if window is still on our active list.
       nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
       if (!mgr) {
         return NS_OK;
       }
@@ -1857,21 +1964,21 @@ MediaManager::EnumerateDevices(nsPIDOMWi
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE(!sInShutdown, NS_ERROR_FAILURE);
   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
   uint64_t windowId = aWindow->WindowID();
 
   AddWindowID(windowId);
 
-  MediaStreamConstraints c;
-  c.mVideo.SetAsBoolean() = true;
-  c.mAudio.SetAsBoolean() = true;
+  bool fake = Preferences::GetBool("media.navigator.streams.fake");
 
-  nsRefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowId, c);
+  nsRefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowId,
+                                                     dom::MediaSourceEnum::Camera,
+                                                     fake);
   p->Then([onSuccess](SourceSet*& aDevices) mutable {
     ScopedDeletePtr<SourceSet> devices(aDevices); // grab result
     nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
     onSuccess->OnSuccess(array);
   }, [onFailure](MediaStreamError& reason) mutable {
     onFailure->OnError(&reason);
   });
   return NS_OK;
@@ -1890,37 +1997,32 @@ MediaManager::GetUserMediaDevices(nsPIDO
 {
   MOZ_ASSERT(NS_IsMainThread());
   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
   if (!aWindowId) {
     aWindowId = aWindow->WindowID();
   }
 
-  nsRefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(aWindowId, aConstraints);
-  p->Then([aWindowId, onSuccess, onFailure](SourceSet*& aDevices) mutable {
-    ScopedDeletePtr<SourceSet> devices(aDevices); // grab result
+  // Ignore passed-in constraints, instead locate + return already-constrained list.
 
-    if (devices->Length()) {
-      nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
+  nsTArray<nsString>* callIDs;
+  if (!mCallIds.Get(aWindowId, &callIDs)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  for (auto& callID : *callIDs) {
+    GetUserMediaTask* task;
+    if (mActiveCallbacks.Get(callID, &task)) {
+      nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*task->mSourceSet);
       onSuccess->OnSuccess(array);
-    } else {
-      nsRefPtr<nsGlobalWindow> window = nsGlobalWindow::GetInnerWindowWithId(aWindowId);
-      if (!window) {
-        return NS_ERROR_UNEXPECTED;
-      }
-      nsRefPtr<MediaStreamError> reason =
-          new MediaStreamError(window, NS_LITERAL_STRING("NotFoundError"));
-      onFailure->OnError(reason);
+      return NS_OK;
     }
-    return NS_OK;
-  }, [onFailure](MediaStreamError& reason) mutable {
-    onFailure->OnError(&reason);
-  });
-  return NS_OK;
+  }
+  return NS_ERROR_UNEXPECTED;
 }
 
 MediaEngine*
 MediaManager::GetBackend(uint64_t aWindowId)
 {
   // Plugin backends as appropriate. The default engine also currently
   // includes picture support for Android.
   // This IS called off main-thread.
@@ -1963,20 +2065,20 @@ void
 MediaManager::OnNavigation(uint64_t aWindowID)
 {
   NS_ASSERTION(NS_IsMainThread(), "OnNavigation called off main thread");
   LOG(("OnNavigation for %llu", aWindowID));
 
   // Invalidate this window. The runnables check this value before making
   // a call to content.
 
-  nsTArray<nsString>* callIds;
-  if (mCallIds.Get(aWindowID, &callIds)) {
-    for (int i = 0, len = callIds->Length(); i < len; ++i) {
-      mActiveCallbacks.Remove((*callIds)[i]);
+  nsTArray<nsString>* callIDs;
+  if (mCallIds.Get(aWindowID, &callIDs)) {
+    for (auto& callID : *callIDs) {
+      mActiveCallbacks.Remove(callID);
     }
     mCallIds.Remove(aWindowID);
   }
 
   // This is safe since we're on main-thread, and the windowlist can only
   // be added to from the main-thread
   nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
       (nsGlobalWindow::GetInnerWindowWithId(aWindowID));
@@ -2101,16 +2203,17 @@ MediaManager::Observe(nsISupports* aSubj
       GetPrefs(branch,NS_ConvertUTF16toUTF8(aData).get());
       LOG(("%s: %dx%d @%dfps (min %d)", __FUNCTION__,
            mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS));
     }
   } else if (!strcmp(aTopic, "xpcom-will-shutdown")) {
     sInShutdown = true;
 
     obs->RemoveObserver(this, "xpcom-will-shutdown");
+    obs->RemoveObserver(this, "getUserMedia:privileged:allow");
     obs->RemoveObserver(this, "getUserMedia:response:allow");
     obs->RemoveObserver(this, "getUserMedia:response:deny");
     obs->RemoveObserver(this, "getUserMedia:revoke");
 
     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
     if (prefs) {
       prefs->RemoveObserver("media.navigator.video.default_width", this);
       prefs->RemoveObserver("media.navigator.video.default_height", this);
@@ -2182,49 +2285,56 @@ MediaManager::Observe(nsISupports* aSubj
       }
       // we hold a ref to 'that' which is the same as sSingleton
       sSingleton = nullptr;
 
       return NS_OK;
     })));
     return NS_OK;
 
-  } else if (!strcmp(aTopic, "getUserMedia:response:allow")) {
+  } else if (!strcmp(aTopic, "getUserMedia:privileged:allow") ||
+             !strcmp(aTopic, "getUserMedia:response:allow")) {
     nsString key(aData);
     nsAutoPtr<GetUserMediaTask> task;
     mActiveCallbacks.RemoveAndForget(key, task);
     if (!task) {
       return NS_OK;
     }
 
     if (aSubject) {
       // A particular device or devices were chosen by the user.
       // NOTE: does not allow setting a device to null; assumes nullptr
       nsCOMPtr<nsISupportsArray> array(do_QueryInterface(aSubject));
       MOZ_ASSERT(array);
       uint32_t len = 0;
       array->Count(&len);
-      MOZ_ASSERT(len);
       if (!len) {
         // neither audio nor video were selected
         task->Denied(NS_LITERAL_STRING("PermissionDeniedError"));
         return NS_OK;
       }
+      bool videoFound = false, audioFound = false;
       for (uint32_t i = 0; i < len; i++) {
         nsCOMPtr<nsISupports> supports;
         array->GetElementAt(i,getter_AddRefs(supports));
         nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supports));
         MOZ_ASSERT(device); // shouldn't be returning anything else...
         if (device) {
           nsString type;
           device->GetType(type);
           if (type.EqualsLiteral("video")) {
-            task->SetVideoDevice(static_cast<VideoDevice*>(device.get()));
+            if (!videoFound) {
+              task->SetVideoDevice(static_cast<VideoDevice*>(device.get()));
+              videoFound = true;
+            }
           } else if (type.EqualsLiteral("audio")) {
-            task->SetAudioDevice(static_cast<AudioDevice*>(device.get()));
+            if (!audioFound) {
+              task->SetAudioDevice(static_cast<AudioDevice*>(device.get()));
+              audioFound = true;
+            }
           } else {
             NS_WARNING("Unknown device type in getUserMedia");
           }
         }
       }
     }
 
     if (sInShutdown) {
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -460,47 +460,59 @@ typedef nsClassHashtable<nsUint64HashKey
 
 class MediaDevice : public nsIMediaDevice
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIMEDIADEVICE
 
   void SetId(const nsAString& aID);
+  virtual uint32_t GetBestFitnessDistance(
+      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets);
 protected:
   virtual ~MediaDevice() {}
-  explicit MediaDevice(MediaEngineSource* aSource);
+  explicit MediaDevice(MediaEngineSource* aSource, bool aIsVideo);
+  static uint32_t FitnessDistance(nsString aN,
+    const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint);
+private:
+  static bool StringsContain(const dom::OwningStringOrStringSequence& aStrings,
+                             nsString aN);
+  static uint32_t FitnessDistance(nsString aN,
+      const dom::ConstrainDOMStringParameters& aParams);
+protected:
   nsString mName;
   nsString mID;
   dom::MediaSourceEnum mMediaSource;
   nsRefPtr<MediaEngineSource> mSource;
+public:
+  bool mIsVideo;
 };
 
 class VideoDevice : public MediaDevice
 {
 public:
   typedef MediaEngineVideoSource Source;
 
   explicit VideoDevice(Source* aSource);
   NS_IMETHOD GetType(nsAString& aType);
   Source* GetSource();
-  uint32_t GetBestFitnessDistance(
-    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets);
+  nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
+                    const MediaEnginePrefs &aPrefs);
 };
 
 class AudioDevice : public MediaDevice
 {
 public:
   typedef MediaEngineAudioSource Source;
 
   explicit AudioDevice(Source* aSource);
   NS_IMETHOD GetType(nsAString& aType);
   Source* GetSource();
-  uint32_t GetBestFitnessDistance(
-    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets);
+  nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
+                    const MediaEnginePrefs &aPrefs);
 };
 
 // we could add MediaManager if needed
 typedef void (*WindowListenerCallback)(MediaManager *aThis,
                                        uint64_t aWindowID,
                                        StreamListeners *aListeners,
                                        void *aData);
 
@@ -566,31 +578,35 @@ public:
                             nsIDOMGetUserMediaErrorCallback* aOnFailure);
 
   nsresult EnumerateDevices(nsPIDOMWindow* aWindow, dom::Promise& aPromise);
   void OnNavigation(uint64_t aWindowID);
   bool IsWindowActivelyCapturing(uint64_t aWindowId);
 
   MediaEnginePrefs mPrefs;
 
+  typedef nsTArray<nsRefPtr<MediaDevice>> SourceSet;
 private:
-  typedef nsTArray<nsRefPtr<MediaDevice>> SourceSet;
   typedef media::Pledge<SourceSet*, dom::MediaStreamError> PledgeSourceSet;
 
+  static bool IsPrivileged();
+  static bool IsLoop(nsIURI* aDocURI);
+  static bool IsPrivateBrowsing(nsPIDOMWindow *window);
+  static nsresult GenerateUUID(nsAString& aResult);
   static nsresult AnonymizeId(nsAString& aId, const nsACString& aOriginKey);
 public: // TODO: make private once we upgrade to GCC 4.8+ on linux.
   static void AnonymizeDevices(SourceSet& aDevices, const nsACString& aOriginKey);
   static already_AddRefed<nsIWritableVariant> ToJSArray(SourceSet& aDevices);
 private:
   already_AddRefed<PledgeSourceSet>
-  EnumerateRawDevices(uint64_t aWindowId,
-                      const dom::MediaStreamConstraints& aConstraints);
+  EnumerateRawDevices(uint64_t aWindowId, dom::MediaSourceEnum aSrcType,
+                      bool aFake, bool aFakeTracks);
   already_AddRefed<PledgeSourceSet>
-  EnumerateDevicesImpl(uint64_t aWindowId,
-                       const dom::MediaStreamConstraints& aConstraints);
+  EnumerateDevicesImpl(uint64_t aWindowId, dom::MediaSourceEnum aSrcType,
+                       bool aFake = false, bool aFakeTracks = false);
 
   StreamListeners* AddWindowID(uint64_t aWindowId);
   WindowTable *GetActiveWindows() {
     NS_ASSERTION(NS_IsMainThread(), "Only access windowlist on main thread");
     return &mActiveWindows;
   }
 
   void GetPref(nsIPrefBranch *aBranch, const char *aPref,
--- a/dom/media/systemservices/MediaParent.cpp
+++ b/dom/media/systemservices/MediaParent.cpp
@@ -498,26 +498,22 @@ template<class Super>
 Parent<Super>::Parent(bool aSameProcess)
   : mOriginKeyStore(OriginKeyStore::Get())
   , mDestroyed(false)
   , mSameProcess(aSameProcess)
 {
   if (!gMediaParentLog)
     gMediaParentLog = PR_NewLogModule("MediaParent");
   LOG(("media::Parent: %p", this));
-
-  MOZ_COUNT_CTOR(Parent);
 }
 
 template<class Super>
 Parent<Super>::~Parent()
 {
   LOG(("~media::Parent: %p", this));
-
-  MOZ_COUNT_DTOR(Parent);
 }
 
 PMediaParent*
 AllocPMediaParent()
 {
   MOZ_ASSERT(!sIPCServingParent);
   sIPCServingParent = new Parent<PMediaParent>();
   return sIPCServingParent;
--- a/dom/media/test/test_streams_individual_pause.html
+++ b/dom/media/test/test_streams_individual_pause.html
@@ -18,17 +18,24 @@ var getVideoImagePixelData = function(v)
   ctx.drawImage(v, 0, 0);
   var imgData = ctx.getImageData(canvas.width/2, canvas.height/2, 1, 1).data;
   return "r" + imgData[0] +
          "g" + imgData[1] +
          "b" + imgData[2] +
          "a" + imgData[3];
 }
 
-navigator.mozGetUserMedia({video: true, fake: true}, function(stream) {
+// This test does not appear to work with the "Dummy video source" provided on
+// linux through the "media.video_loopback_dev" pref in the tree test environment.
+// To force the built-in fake streams to always be used instead, we specify
+// fakeTracks, a feature solely of the built-in fake streams (even though we
+// don't use the extra tracks).
+
+navigator.mozGetUserMedia({video: true, fake: true, fakeTracks: true },
+                          function(stream) {
   var stream = stream;
   var video1 = document.getElementById('video1');
   var video2 = document.getElementById('video2');
 
   var src = URL.createObjectURL(stream);
   video1.src = src;
   video2.src = src;
 
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -94,27 +94,23 @@ function createMediaElement(type, label)
   element.setAttribute('autoplay', 'autoplay');
   document.getElementById('content').appendChild(element);
 
   return element;
 }
 
 
 /**
- * Wrapper function for mozGetUserMedia to allow a singular area of control
- * for determining whether we run this with fake devices or not.
+ * Wrapper function for mediaDevices.getUserMedia used by some tests. Whether
+ * to use fake devices or not is now determined in pref further below instead.
  *
  * @param {Dictionary} constraints
  *        The constraints for this mozGetUserMedia callback
  */
 function getUserMedia(constraints) {
-  if (!("fake" in constraints) && FAKE_ENABLED) {
-    constraints["fake"] = FAKE_ENABLED;
-  }
-
   info("Call getUserMedia for " + JSON.stringify(constraints));
   return navigator.mediaDevices.getUserMedia(constraints);
 }
 
 // These are the promises we use to track that the prerequisites for the test
 // are in place before running it.
 var setTestOptions;
 var testConfigured = new Promise(r => setTestOptions = r);
@@ -133,16 +129,17 @@ function setupEnvironment() {
       ['canvas.capturestream.enabled', true],
       ['dom.messageChannel.enabled', true],
       ['media.peerconnection.enabled', true],
       ['media.peerconnection.identity.enabled', true],
       ['media.peerconnection.identity.timeout', 120000],
       ['media.peerconnection.ice.stun_client_maximum_transmits', 14],
       ['media.peerconnection.ice.trickle_grace_period', 30000],
       ['media.navigator.permission.disabled', true],
+      ['media.navigator.streams.fake', FAKE_ENABLED],
       ['media.getusermedia.screensharing.enabled', true],
       ['media.getusermedia.screensharing.allowed_domains', "mochi.test"]
     ]
   }, setTestOptions);
 
   // We don't care about waiting for this to complete, we just want to ensure
   // that we don't build up a huge backlog of GC work.
   SpecialPowers.exactGC(window);
--- a/dom/media/tests/mochitest/test_enumerateDevices.html
+++ b/dom/media/tests/mochitest/test_enumerateDevices.html
@@ -3,26 +3,61 @@
 <head>
   <script src="mediaStreamPlayback.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
 createHTML({ title: "Run enumerateDevices code", bug: "1046245" });
 /**
-  Tests covering enumerateDevices API. Exercise code.
+  Tests covering enumerateDevices API and deviceId constraint. Exercise code.
 */
 
-runTest(() => navigator.mediaDevices.enumerateDevices()
+function mustSucceed(msg, f) {
+  return f().then(() => ok(true, msg + " must succeed"),
+                  e => is(e.name, null, msg + " must succeed: " + e.message));
+}
+
+function mustFailWith(msg, reason, f) {
+  return f().then(() => ok(false, msg + " must fail"),
+                  e => is(e.name, reason, msg + " must fail: " + e.message));
+}
+
+var pushPrefs = dict => new Promise(res => SpecialPowers.pushPrefEnv(dict, res));
+
+runTest(() =>
+  pushPrefs({ set : [["media.navigator.streams.fake", true]] })
+  .then(() => navigator.mediaDevices.enumerateDevices())
   .then(devices => {
     ok(devices.length > 0, "At least one device found");
     devices.forEach(d => {
       ok(d.kind == "videoinput" || d.kind == "audioinput", "Known device kind");
       is(d.deviceId.length, 44, "Correct device id length");
       ok(d.label.length !== undefined, "Device label: " + d.label);
       is(d.groupId, "", "Don't support groupId yet");
     });
-  }));
+  })
+  // Check deviceId failure paths for video.
+  .then(() => mustSucceed("unknown plain deviceId on video",
+                          () => navigator.mediaDevices.getUserMedia({
+    video: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" },
+    fake: true,
+  })))
+  .then(() => mustSucceed("unknown plain deviceId on audio",
+                          () => navigator.mediaDevices.getUserMedia({
+    audio: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" },
+    fake: true,
+  })))
+  .then(() => mustFailWith("unknown exact deviceId on video", "NotFoundError",
+                       () => navigator.mediaDevices.getUserMedia({
+    video: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
+    fake: true,
+  })))
+  .then(() => mustFailWith("unknown exact deviceId on audio", "NotFoundError",
+                       () => navigator.mediaDevices.getUserMedia({
+    audio: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
+    fake: true,
+  }))));
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -159,16 +159,25 @@ public:
 
   /* It is an error to call Start() before an Allocate(), and Stop() before
    * a Start(). Only Allocate() may be called after a Deallocate(). */
 
   void SetHasFakeTracks(bool aHasFakeTracks) {
     mHasFakeTracks = aHasFakeTracks;
   }
 
+  /* This call reserves but does not start the device. */
+  virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
+                            const MediaEnginePrefs &aPrefs,
+                            const nsString& aDeviceId) = 0;
+
+  virtual uint32_t GetBestFitnessDistance(
+      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+      const nsString& aDeviceId) = 0;
+
 protected:
   // Only class' own members can be initialized in constructor initializer list.
   explicit MediaEngineSource(MediaEngineState aState)
     : mState(aState)
     , mHasFakeTracks(false)
   {}
   MediaEngineState mState;
   bool mHasFakeTracks;
@@ -219,42 +228,31 @@ private:
   }
 };
 
 class MediaEngineVideoSource : public MediaEngineSource
 {
 public:
   virtual ~MediaEngineVideoSource() {}
 
-  /* This call reserves but does not start the device. */
-  virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
-                            const MediaEnginePrefs &aPrefs) = 0;
-
-  virtual uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) = 0;
-
 protected:
   explicit MediaEngineVideoSource(MediaEngineState aState)
     : MediaEngineSource(aState) {}
   MediaEngineVideoSource()
     : MediaEngineSource(kReleased) {}
 };
 
 /**
  * Audio source and friends.
  */
 class MediaEngineAudioSource : public MediaEngineSource
 {
 public:
   virtual ~MediaEngineAudioSource() {}
 
-  /* This call reserves but does not start the device. */
-  virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
-                            const MediaEnginePrefs &aPrefs) = 0;
-
 protected:
   explicit MediaEngineAudioSource(MediaEngineState aState)
     : MediaEngineSource(aState) {}
   MediaEngineAudioSource()
     : MediaEngineSource(kReleased) {}
 
 };
 
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
@@ -46,131 +46,27 @@ MediaEngineCameraVideoSource::NumCapabil
 void
 MediaEngineCameraVideoSource::GetCapability(size_t aIndex,
                                             webrtc::CaptureCapability& aOut)
 {
   MOZ_ASSERT(aIndex < mHardcodedCapabilities.Length());
   aOut = mHardcodedCapabilities[aIndex];
 }
 
-// The full algorithm for all cameras. Sources that don't list capabilities
-// need to fake it and hardcode some by populating mHardcodedCapabilities above.
-
-// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
-
-template<class ValueType, class ConstrainRange>
-/* static */ uint32_t
-MediaEngineCameraVideoSource::FitnessDistance(ValueType aN,
-                                              const ConstrainRange& aRange)
-{
-  if ((aRange.mExact.WasPassed() && aRange.mExact.Value() != aN) ||
-      (aRange.mMin.WasPassed() && aRange.mMin.Value() > aN) ||
-      (aRange.mMax.WasPassed() && aRange.mMax.Value() < aN)) {
-    return UINT32_MAX;
-  }
-  if (!aRange.mIdeal.WasPassed() || aN == aRange.mIdeal.Value()) {
-    return 0;
-  }
-  return uint32_t(ValueType((std::abs(aN - aRange.mIdeal.Value()) * 1000) /
-                            std::max(std::abs(aN), std::abs(aRange.mIdeal.Value()))));
-}
-
-// Binding code doesn't templatize well...
-
-/*static*/ uint32_t
-MediaEngineCameraVideoSource::FitnessDistance(int32_t aN,
-    const OwningLongOrConstrainLongRange& aConstraint, bool aAdvanced)
-{
-  if (aConstraint.IsLong()) {
-    ConstrainLongRange range;
-    (aAdvanced ? range.mExact : range.mIdeal).Construct(aConstraint.GetAsLong());
-    return FitnessDistance(aN, range);
-  } else {
-    return FitnessDistance(aN, aConstraint.GetAsConstrainLongRange());
-  }
-}
-
-/*static*/ uint32_t
-MediaEngineCameraVideoSource::FitnessDistance(double aN,
-    const OwningDoubleOrConstrainDoubleRange& aConstraint,
-    bool aAdvanced)
-{
-  if (aConstraint.IsDouble()) {
-    ConstrainDoubleRange range;
-    (aAdvanced ? range.mExact : range.mIdeal).Construct(aConstraint.GetAsDouble());
-    return FitnessDistance(aN, range);
-  } else {
-    return FitnessDistance(aN, aConstraint.GetAsConstrainDoubleRange());
-  }
-}
-
-// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
-
-/* static */ uint32_t
-MediaEngineCameraVideoSource::FitnessDistance(nsString aN,
-                             const ConstrainDOMStringParameters& aParams)
-{
-  struct Func
-  {
-    static bool
-    Contains(const OwningStringOrStringSequence& aStrings, nsString aN)
-    {
-      return aStrings.IsString() ? aStrings.GetAsString() == aN
-                                 : aStrings.GetAsStringSequence().Contains(aN);
-    }
-  };
-
-  if (aParams.mExact.WasPassed() && !Func::Contains(aParams.mExact.Value(), aN)) {
-    return UINT32_MAX;
-  }
-  if (aParams.mIdeal.WasPassed() && !Func::Contains(aParams.mIdeal.Value(), aN)) {
-    return 1000;
-  }
-  return 0;
-}
-
-/* static */ uint32_t
-MediaEngineCameraVideoSource::FitnessDistance(nsString aN,
-    const OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint,
-    bool aAdvanced)
-{
-  if (aConstraint.IsString()) {
-    ConstrainDOMStringParameters params;
-    if (aAdvanced) {
-      params.mExact.Construct();
-      params.mExact.Value().SetAsString() = aConstraint.GetAsString();
-    } else {
-      params.mIdeal.Construct();
-      params.mIdeal.Value().SetAsString() = aConstraint.GetAsString();
-    }
-    return FitnessDistance(aN, params);
-  } else if (aConstraint.IsStringSequence()) {
-    ConstrainDOMStringParameters params;
-    if (aAdvanced) {
-      params.mExact.Construct();
-      params.mExact.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence();
-    } else {
-      params.mIdeal.Construct();
-      params.mIdeal.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence();
-    }
-    return FitnessDistance(aN, params);
-  } else {
-    return FitnessDistance(aN, aConstraint.GetAsConstrainDOMStringParameters());
-  }
-}
-
 uint32_t
 MediaEngineCameraVideoSource::GetFitnessDistance(const webrtc::CaptureCapability& aCandidate,
                                                  const MediaTrackConstraintSet &aConstraints,
-                                                 bool aAdvanced)
+                                                 bool aAdvanced,
+                                                 const nsString& aDeviceId)
 {
   // Treat width|height|frameRate == 0 on capability as "can do any".
   // This allows for orthogonal capabilities that are not in discrete steps.
 
   uint64_t distance =
+    uint64_t(FitnessDistance(aDeviceId, aConstraints.mDeviceId, aAdvanced)) +
     uint64_t(FitnessDistance(mFacingMode, aConstraints.mFacingMode, aAdvanced)) +
     uint64_t(aCandidate.width? FitnessDistance(int32_t(aCandidate.width),
                                                aConstraints.mWidth,
                                                aAdvanced) : 0) +
     uint64_t(aCandidate.height? FitnessDistance(int32_t(aCandidate.height),
                                                 aConstraints.mHeight,
                                                 aAdvanced) : 0) +
     uint64_t(aCandidate.maxFPS? FitnessDistance(double(aCandidate.maxFPS),
@@ -204,32 +100,33 @@ MediaEngineCameraVideoSource::TrimLessFi
 // Ideal values are considered in the first ConstraintSet only.
 // Plain values are treated as Ideal in the first ConstraintSet.
 // Plain values are treated as Exact in subsequent ConstraintSets.
 // Infinity = UINT32_MAX e.g. device cannot satisfy accumulated ConstraintSets.
 // A finite result may be used to calculate this device's ranking as a choice.
 
 uint32_t
 MediaEngineCameraVideoSource::GetBestFitnessDistance(
-    const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets)
+    const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets,
+    const nsString& aDeviceId)
 {
   size_t num = NumCapabilities();
 
   CapabilitySet candidateSet;
   for (size_t i = 0; i < num; i++) {
     candidateSet.AppendElement(i);
   }
 
   bool first = true;
   for (const MediaTrackConstraintSet* cs : aConstraintSets) {
     for (size_t i = 0; i < candidateSet.Length();  ) {
       auto& candidate = candidateSet[i];
       webrtc::CaptureCapability cap;
       GetCapability(candidate.mIndex, cap);
-      uint32_t distance = GetFitnessDistance(cap, *cs, !first);
+      uint32_t distance = GetFitnessDistance(cap, *cs, !first, aDeviceId);
       if (distance == UINT32_MAX) {
         candidateSet.RemoveElementAt(i);
       } else {
         ++i;
         if (first) {
           candidate.mDistance = distance;
         }
       }
@@ -263,17 +160,18 @@ MediaEngineCameraVideoSource::LogConstra
         "             frameRate: { min: %f, max: %f }"),
        c.mFrameRate.mMin, c.mFrameRate.mMax,
        c.mFrameRate.mIdeal.WasPassed()? c.mFrameRate.mIdeal.Value() : 0));
 }
 
 bool
 MediaEngineCameraVideoSource::ChooseCapability(
     const MediaTrackConstraints &aConstraints,
-    const MediaEnginePrefs &aPrefs)
+    const MediaEnginePrefs &aPrefs,
+    const nsString& aDeviceId)
 {
   if (MOZ_LOG_TEST(GetMediaManagerLog(), LogLevel::Debug)) {
     LOG(("ChooseCapability: prefs: %dx%d @%d-%dfps",
          aPrefs.GetWidth(), aPrefs.GetHeight(),
          aPrefs.mFPS, aPrefs.mMinFPS));
     LogConstraints(aConstraints, false);
     if (aConstraints.mAdvanced.WasPassed()) {
       LOG(("Advanced array[%u]:", aConstraints.mAdvanced.Value().Length()));
@@ -291,34 +189,34 @@ MediaEngineCameraVideoSource::ChooseCapa
   }
 
   // First, filter capabilities by required constraints (min, max, exact).
 
   for (size_t i = 0; i < candidateSet.Length();) {
     auto& candidate = candidateSet[i];
     webrtc::CaptureCapability cap;
     GetCapability(candidate.mIndex, cap);
-    candidate.mDistance = GetFitnessDistance(cap, aConstraints, false);
+    candidate.mDistance = GetFitnessDistance(cap, aConstraints, false, aDeviceId);
     if (candidate.mDistance == UINT32_MAX) {
       candidateSet.RemoveElementAt(i);
     } else {
       ++i;
     }
   }
 
   // Filter further with all advanced constraints (that don't overconstrain).
 
   if (aConstraints.mAdvanced.WasPassed()) {
     for (const MediaTrackConstraintSet &cs : aConstraints.mAdvanced.Value()) {
       CapabilitySet rejects;
       for (size_t i = 0; i < candidateSet.Length();) {
         auto& candidate = candidateSet[i];
         webrtc::CaptureCapability cap;
         GetCapability(candidate.mIndex, cap);
-        if (GetFitnessDistance(cap, cs, true) == UINT32_MAX) {
+        if (GetFitnessDistance(cap, cs, true, aDeviceId) == UINT32_MAX) {
           rejects.AppendElement(candidate);
           candidateSet.RemoveElementAt(i);
         } else {
           ++i;
         }
       }
       if (!candidateSet.Length()) {
         candidateSet.MoveElementsFrom(rejects);
@@ -340,17 +238,17 @@ MediaEngineCameraVideoSource::ChooseCapa
     MediaTrackConstraintSet prefs;
     prefs.mWidth.SetAsLong() = aPrefs.GetWidth();
     prefs.mHeight.SetAsLong() = aPrefs.GetHeight();
     prefs.mFrameRate.SetAsDouble() = aPrefs.mFPS;
 
     for (auto& candidate : candidateSet) {
       webrtc::CaptureCapability cap;
       GetCapability(candidate.mIndex, cap);
-      candidate.mDistance = GetFitnessDistance(cap, prefs, false);
+      candidate.mDistance = GetFitnessDistance(cap, prefs, false, aDeviceId);
     }
     TrimLessFitCandidates(candidateSet);
   }
 
   // Any remaining multiples all have the same distance, but may vary on
   // format. Some formats are more desirable for certain use like WebRTC.
   // E.g. I420 over RGB24 can remove a needless format conversion.
 
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.h
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.h
@@ -11,17 +11,18 @@
 #include "nsDirectoryServiceDefs.h"
 
 // conflicts with #include of scoped_ptr.h
 #undef FF
 #include "webrtc/video_engine/include/vie_capture.h"
 
 namespace mozilla {
 
-class MediaEngineCameraVideoSource : public MediaEngineVideoSource
+class MediaEngineCameraVideoSource : public MediaEngineVideoSource,
+                                     private MediaConstraintsHelper
 {
 public:
   explicit MediaEngineCameraVideoSource(int aIndex,
                                         const char* aMonitorName = "Camera.Monitor")
     : MediaEngineVideoSource(kReleased)
     , mMonitor(aMonitorName)
     , mWidth(0)
     , mHeight(0)
@@ -54,17 +55,18 @@ public:
   }
 
   virtual nsresult TakePhoto(PhotoCallback* aCallback) override
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) override;
+      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+      const nsString& aDeviceId) override;
 
   virtual void Shutdown() override {};
 
 protected:
   struct CapabilityCandidate {
     explicit CapabilityCandidate(uint8_t index, uint32_t distance = 0)
     : mIndex(index), mDistance(distance) {}
 
@@ -75,38 +77,28 @@ protected:
 
   ~MediaEngineCameraVideoSource() {}
 
   // guts for appending data to the MSG track
   virtual bool AppendToTrack(SourceMediaStream* aSource,
                              layers::Image* aImage,
                              TrackID aID,
                              StreamTime delta);
-  template<class ValueType, class ConstrainRange>
-  static uint32_t FitnessDistance(ValueType aN, const ConstrainRange& aRange);
-  static uint32_t FitnessDistance(int32_t aN,
-      const dom::OwningLongOrConstrainLongRange& aConstraint, bool aAdvanced);
-  static uint32_t FitnessDistance(double aN,
-      const dom::OwningDoubleOrConstrainDoubleRange& aConstraint, bool aAdvanced);
-  static uint32_t FitnessDistance(nsString aN,
-    const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint,
-    bool aAdvanced);
-  static uint32_t FitnessDistance(nsString aN,
-      const dom::ConstrainDOMStringParameters& aParams);
-
   uint32_t GetFitnessDistance(const webrtc::CaptureCapability& aCandidate,
                               const dom::MediaTrackConstraintSet &aConstraints,
-                              bool aAdvanced);
+                              bool aAdvanced,
+                              const nsString& aDeviceId);
   static void TrimLessFitCandidates(CapabilitySet& set);
   static void LogConstraints(const dom::MediaTrackConstraintSet& aConstraints,
                              bool aAdvanced);
   virtual size_t NumCapabilities();
   virtual void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut);
   bool ChooseCapability(const dom::MediaTrackConstraints &aConstraints,
-                        const MediaEnginePrefs &aPrefs);
+                        const MediaEnginePrefs &aPrefs,
+                        const nsString& aDeviceId);
   void SetName(nsString aName);
   void SetUUID(const char* aUUID);
   const nsCString& GetUUID(); // protected access
 
   // Engine variables.
 
   // mMonitor protects mImage access/changes, and transitions of mState
   // from kStarted to kStopped (which are combined with EndTrack() and
--- a/dom/media/webrtc/MediaEngineDefault.cpp
+++ b/dom/media/webrtc/MediaEngineDefault.cpp
@@ -63,19 +63,34 @@ MediaEngineDefaultVideoSource::GetName(n
 
 void
 MediaEngineDefaultVideoSource::GetUUID(nsACString& aUUID)
 {
   aUUID.AssignLiteral("1041FCBD-3F12-4F7B-9E9B-1EC556DD5676");
   return;
 }
 
+uint32_t
+MediaEngineDefaultVideoSource::GetBestFitnessDistance(
+    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+    const nsString& aDeviceId)
+{
+  uint32_t distance = 0;
+
+  for (const MediaTrackConstraintSet* cs : aConstraintSets) {
+    distance = GetMinimumFitnessDistance(*cs, false, aDeviceId);
+    break; // distance is read from first entry only
+  }
+  return distance;
+}
+
 nsresult
 MediaEngineDefaultVideoSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
-                                        const MediaEnginePrefs &aPrefs)
+                                        const MediaEnginePrefs &aPrefs,
+                                        const nsString& aDeviceId)
 {
   if (mState != kReleased) {
     return NS_ERROR_FAILURE;
   }
 
   mOpts = aPrefs;
   mOpts.mWidth = mOpts.mWidth ? mOpts.mWidth : MediaEngine::DEFAULT_43_VIDEO_WIDTH;
   mOpts.mHeight = mOpts.mHeight ? mOpts.mHeight : MediaEngine::DEFAULT_43_VIDEO_HEIGHT;
@@ -343,19 +358,34 @@ MediaEngineDefaultAudioSource::GetName(n
 
 void
 MediaEngineDefaultAudioSource::GetUUID(nsACString& aUUID)
 {
   aUUID.AssignLiteral("B7CBD7C1-53EF-42F9-8353-73F61C70C092");
   return;
 }
 
+uint32_t
+MediaEngineDefaultAudioSource::GetBestFitnessDistance(
+    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+    const nsString& aDeviceId)
+{
+  uint32_t distance = 0;
+
+  for (const MediaTrackConstraintSet* cs : aConstraintSets) {
+    distance = GetMinimumFitnessDistance(*cs, false, aDeviceId);
+    break; // distance is read from first entry only
+  }
+  return distance;
+}
+
 nsresult
 MediaEngineDefaultAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
-                                        const MediaEnginePrefs &aPrefs)
+                                        const MediaEnginePrefs &aPrefs,
+                                        const nsString& aDeviceId)
 {
   if (mState != kReleased) {
     return NS_ERROR_FAILURE;
   }
 
   mState = kAllocated;
   // generate 1Khz sine wave
   mSineGenerator = new SineWaveGenerator(AUDIO_RATE);
--- a/dom/media/webrtc/MediaEngineDefault.h
+++ b/dom/media/webrtc/MediaEngineDefault.h
@@ -26,45 +26,45 @@ class ImageContainer;
 }
 
 class MediaEngineDefault;
 
 /**
  * The default implementation of the MediaEngine interface.
  */
 class MediaEngineDefaultVideoSource : public nsITimerCallback,
-                                      public MediaEngineVideoSource
+                                      public MediaEngineVideoSource,
+                                      private MediaConstraintsHelper
 {
 public:
   MediaEngineDefaultVideoSource();
 
   virtual void Shutdown() override {};
 
   virtual void GetName(nsAString&) override;
   virtual void GetUUID(nsACString&) override;
 
   virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
-                            const MediaEnginePrefs &aPrefs) override;
+                            const MediaEnginePrefs &aPrefs,
+                            const nsString& aDeviceId) override;
   virtual nsresult Deallocate() override;
   virtual nsresult Start(SourceMediaStream*, TrackID) override;
   virtual nsresult Stop(SourceMediaStream*, TrackID) override;
   virtual void SetDirectListeners(bool aHasDirectListeners) override {};
   virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
                           bool aAgcOn, uint32_t aAGC,
                           bool aNoiseOn, uint32_t aNoise,
                           int32_t aPlayoutDelay) override { return NS_OK; };
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream *aSource,
                           TrackID aId,
                           StreamTime aDesiredTime) override;
   virtual uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) override
-  {
-    return true;
-  }
+      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+      const nsString& aDeviceId) override;
 
   virtual bool IsFake() override {
     return true;
   }
 
   virtual const dom::MediaSourceEnum GetMediaSource() override {
     return dom::MediaSourceEnum::Camera;
   }
@@ -96,28 +96,30 @@ protected:
   MediaEnginePrefs mOpts;
   int mCb;
   int mCr;
 };
 
 class SineWaveGenerator;
 
 class MediaEngineDefaultAudioSource : public nsITimerCallback,
-                                      public MediaEngineAudioSource
+                                      public MediaEngineAudioSource,
+                                      private MediaConstraintsHelper
 {
 public:
   MediaEngineDefaultAudioSource();
 
   virtual void Shutdown() override {};
 
   virtual void GetName(nsAString&) override;
   virtual void GetUUID(nsACString&) override;
 
   virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
-                            const MediaEnginePrefs &aPrefs) override;
+                            const MediaEnginePrefs &aPrefs,
+                            const nsString& aDeviceId) override;
   virtual nsresult Deallocate() override;
   virtual nsresult Start(SourceMediaStream*, TrackID) override;
   virtual nsresult Stop(SourceMediaStream*, TrackID) override;
   virtual void SetDirectListeners(bool aHasDirectListeners) override {};
   virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
                           bool aAgcOn, uint32_t aAGC,
                           bool aNoiseOn, uint32_t aNoise,
                           int32_t aPlayoutDelay) override { return NS_OK; };
@@ -134,16 +136,20 @@ public:
     return dom::MediaSourceEnum::Microphone;
   }
 
   virtual nsresult TakePhoto(PhotoCallback* aCallback) override
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
+  virtual uint32_t GetBestFitnessDistance(
+      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+      const nsString& aDeviceId) override;
+
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSITIMERCALLBACK
 
 protected:
   ~MediaEngineDefaultAudioSource();
 
   TrackID mTrackID;
   nsCOMPtr<nsITimer> mTimer;
--- a/dom/media/webrtc/MediaEngineGonkVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineGonkVideoSource.cpp
@@ -142,23 +142,24 @@ MediaEngineGonkVideoSource::NumCapabilit
       }
     }
   }
   return mHardcodedCapabilities.Length();
 }
 
 nsresult
 MediaEngineGonkVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
-                                     const MediaEnginePrefs& aPrefs)
+                                     const MediaEnginePrefs& aPrefs,
+                                     const nsString& aDeviceId)
 {
   LOG((__FUNCTION__));
 
   ReentrantMonitorAutoEnter sync(mCallbackMonitor);
   if (mState == kReleased && mInitDone) {
-    ChooseCapability(aConstraints, aPrefs);
+    ChooseCapability(aConstraints, aPrefs, aDeviceId);
     NS_DispatchToMainThread(WrapRunnable(nsRefPtr<MediaEngineGonkVideoSource>(this),
                                          &MediaEngineGonkVideoSource::AllocImpl));
     mCallbackMonitor.Wait();
     if (mState != kAllocated) {
       return NS_ERROR_FAILURE;
     }
   }
 
--- a/dom/media/webrtc/MediaEngineGonkVideoSource.h
+++ b/dom/media/webrtc/MediaEngineGonkVideoSource.h
@@ -56,17 +56,18 @@ public:
     , mRotation(0)
     , mBackCamera(false)
     , mOrientationChanged(true) // Correct the orientation at first time takePhoto.
     {
       Init();
     }
 
   virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
-                            const MediaEnginePrefs &aPrefs) override;
+                            const MediaEnginePrefs &aPrefs,
+                            const nsString& aDeviceId) override;
   virtual nsresult Deallocate() override;
   virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) override;
   virtual nsresult Stop(SourceMediaStream* aSource, TrackID aID) override;
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream* aSource,
                           TrackID aId,
                           StreamTime aDesiredTime) override;
 
--- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -115,17 +115,18 @@ MediaEngineTabVideoSource::GetUUID(nsACS
 }
 
 #define DEFAULT_TABSHARE_VIDEO_MAX_WIDTH 4096
 #define DEFAULT_TABSHARE_VIDEO_MAX_HEIGHT 4096
 #define DEFAULT_TABSHARE_VIDEO_FRAMERATE 30
 
 nsresult
 MediaEngineTabVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
-                                    const MediaEnginePrefs& aPrefs)
+                                    const MediaEnginePrefs& aPrefs,
+                                    const nsString& aDeviceId)
 {
   // windowId and scrollWithPage are not proper constraints, so just read them.
   // They have no well-defined behavior in advanced, so ignore them there.
 
   mWindowId = aConstraints.mBrowserWindow.WasPassed() ?
               aConstraints.mBrowserWindow.Value() : -1;
   mScrollWithPage = aConstraints.mScrollWithPage.WasPassed() ?
                     aConstraints.mScrollWithPage.Value() : true;
--- a/dom/media/webrtc/MediaEngineTabVideoSource.h
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.h
@@ -17,29 +17,31 @@ class MediaEngineTabVideoSource : public
     NS_DECL_NSIDOMEVENTLISTENER
     NS_DECL_NSITIMERCALLBACK
     MediaEngineTabVideoSource();
 
     virtual void Shutdown() override {};
     virtual void GetName(nsAString_internal&) override;
     virtual void GetUUID(nsACString_internal&) override;
     virtual nsresult Allocate(const dom::MediaTrackConstraints &,
-                              const mozilla::MediaEnginePrefs&) override;
+                              const mozilla::MediaEnginePrefs&,
+                              const nsString& aDeviceId) override;
     virtual nsresult Deallocate() override;
     virtual nsresult Start(mozilla::SourceMediaStream*, mozilla::TrackID) override;
     virtual void SetDirectListeners(bool aHasDirectListeners) override {};
     virtual void NotifyPull(mozilla::MediaStreamGraph*, mozilla::SourceMediaStream*, mozilla::TrackID, mozilla::StreamTime) override;
     virtual nsresult Stop(mozilla::SourceMediaStream*, mozilla::TrackID) override;
     virtual nsresult Config(bool, uint32_t, bool, uint32_t, bool, uint32_t, int32_t) override;
     virtual bool IsFake() override;
     virtual const dom::MediaSourceEnum GetMediaSource() override {
       return dom::MediaSourceEnum::Browser;
     }
     virtual uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) override
+      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+      const nsString& aDeviceId) override
     {
       return 0;
     }
 
     virtual nsresult TakePhoto(PhotoCallback* aCallback) override
     {
       return NS_ERROR_NOT_IMPLEMENTED;
     }
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -81,21 +81,23 @@ public:
   MediaEngineWebRTCVideoSource(webrtc::VideoEngine* aVideoEnginePtr, int aIndex,
                                dom::MediaSourceEnum aMediaSource = dom::MediaSourceEnum::Camera)
     : MediaEngineCameraVideoSource(aIndex, "WebRTCCamera.Monitor")
     , mVideoEngine(aVideoEnginePtr)
     , mMinFps(-1)
     , mMediaSource(aMediaSource)
   {
     MOZ_ASSERT(aVideoEnginePtr);
+    MOZ_ASSERT(aMediaSource != dom::MediaSourceEnum::Other);
     Init();
   }
 
   virtual nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
-                            const MediaEnginePrefs& aPrefs) override;
+                            const MediaEnginePrefs& aPrefs,
+                            const nsString& aDeviceId) override;
   virtual nsresult Deallocate() override;
   virtual nsresult Start(SourceMediaStream*, TrackID) override;
   virtual nsresult Stop(SourceMediaStream*, TrackID) override;
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream* aSource,
                           TrackID aId,
                           StreamTime aDesiredTime) override;
 
@@ -127,17 +129,18 @@ private:
   int mMinFps; // Min rate we want to accept
   dom::MediaSourceEnum mMediaSource; // source of media (camera | application | screen)
 
   size_t NumCapabilities() override;
   void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut) override;
 };
 
 class MediaEngineWebRTCAudioSource : public MediaEngineAudioSource,
-                                     public webrtc::VoEMediaProcess
+                                     public webrtc::VoEMediaProcess,
+                                     private MediaConstraintsHelper
 {
 public:
   MediaEngineWebRTCAudioSource(nsIThread* aThread, webrtc::VoiceEngine* aVoiceEnginePtr,
                                int aIndex, const char* name, const char* uuid)
     : MediaEngineAudioSource(kReleased)
     , mVoiceEngine(aVoiceEnginePtr)
     , mMonitor("WebRTCMic.Monitor")
     , mThread(aThread)
@@ -156,17 +159,18 @@ public:
     mDeviceUUID.Assign(uuid);
     Init();
   }
 
   virtual void GetName(nsAString& aName) override;
   virtual void GetUUID(nsACString& aUUID) override;
 
   virtual nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
-                            const MediaEnginePrefs& aPrefs) override;
+                            const MediaEnginePrefs& aPrefs,
+                            const nsString& aDeviceId) override;
   virtual nsresult Deallocate() override;
   virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) override;
   virtual nsresult Stop(SourceMediaStream* aSource, TrackID aID) override;
   virtual void SetDirectListeners(bool aHasDirectListeners) override {};
   virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
                           bool aAgcOn, uint32_t aAGC,
                           bool aNoiseOn, uint32_t aNoise,
                           int32_t aPlayoutDelay) override;
@@ -184,16 +188,20 @@ public:
     return dom::MediaSourceEnum::Microphone;
   }
 
   virtual nsresult TakePhoto(PhotoCallback* aCallback) override
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
+  virtual uint32_t GetBestFitnessDistance(
+      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+      const nsString& aDeviceId) override;
+
   // VoEMediaProcess.
   void Process(int channel, webrtc::ProcessingTypes type,
                int16_t audio10ms[], int length,
                int samplingFreq, bool isStereo) override;
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   virtual void Shutdown() override;
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -254,19 +254,41 @@ MediaEngineWebRTCAudioSource::Config(boo
     if (update_noise &&
       0 != (error = mVoEProcessing->SetNsStatus(mNoiseOn, (webrtc::NsModes) aNoise))) {
       LOG(("%s Error setting NoiseSuppression Status: %d ",__FUNCTION__, error));
     }
   }
   return NS_OK;
 }
 
+// GetBestFitnessDistance returns the best distance the capture device can offer
+// as a whole, given an accumulated number of ConstraintSets.
+// Ideal values are considered in the first ConstraintSet only.
+// Plain values are treated as Ideal in the first ConstraintSet.
+// Plain values are treated as Exact in subsequent ConstraintSets.
+// Infinity = UINT32_MAX e.g. device cannot satisfy accumulated ConstraintSets.
+// A finite result may be used to calculate this device's ranking as a choice.
+
+uint32_t MediaEngineWebRTCAudioSource::GetBestFitnessDistance(
+    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+    const nsString& aDeviceId)
+{
+  uint32_t distance = 0;
+
+  for (const MediaTrackConstraintSet* cs : aConstraintSets) {
+    distance = GetMinimumFitnessDistance(*cs, false, aDeviceId);
+    break; // distance is read from first entry only
+  }
+  return distance;
+}
+
 nsresult
 MediaEngineWebRTCAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
-                                       const MediaEnginePrefs &aPrefs)
+                                       const MediaEnginePrefs &aPrefs,
+                                       const nsString& aDeviceId)
 {
   if (mState == kReleased) {
     if (mInitDone) {
       ScopedCustomReleasePtr<webrtc::VoEHardware> ptrVoEHw(webrtc::VoEHardware::GetInterface(mVoiceEngine));
       if (!ptrVoEHw || ptrVoEHw->SetRecordingDevice(mCapIndex)) {
         return NS_ERROR_FAILURE;
       }
       mState = kAllocated;
--- a/dom/media/webrtc/MediaEngineWebRTCVideo.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCVideo.cpp
@@ -203,24 +203,25 @@ MediaEngineWebRTCVideoSource::GetCapabil
   if (!mHardcodedCapabilities.IsEmpty()) {
     MediaEngineCameraVideoSource::GetCapability(aIndex, aOut);
   }
   mViECapture->GetCaptureCapability(GetUUID().get(), kMaxUniqueIdLength, aIndex, aOut);
 }
 
 nsresult
 MediaEngineWebRTCVideoSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
-                                       const MediaEnginePrefs &aPrefs)
+                                       const MediaEnginePrefs &aPrefs,
+                                       const nsString& aDeviceId)
 {
   LOG((__FUNCTION__));
   if (mState == kReleased && mInitDone) {
     // Note: if shared, we don't allow a later opener to affect the resolution.
     // (This may change depending on spec changes for Constraints/settings)
 
-    if (!ChooseCapability(aConstraints, aPrefs)) {
+    if (!ChooseCapability(aConstraints, aPrefs, aDeviceId)) {
       return NS_ERROR_UNEXPECTED;
     }
     if (mViECapture->AllocateCaptureDevice(GetUUID().get(),
                                            kMaxUniqueIdLength, mCaptureIndex)) {
       return NS_ERROR_FAILURE;
     }
     mState = kAllocated;
     LOG(("Video device %d allocated", mCaptureIndex));
--- a/dom/media/webrtc/MediaTrackConstraints.cpp
+++ b/dom/media/webrtc/MediaTrackConstraints.cpp
@@ -76,9 +76,135 @@ FlattenedConstraints::FlattenedConstrain
         mWidth.Intersect(set.mWidth);
         mHeight.Intersect(set.mHeight);
         mFrameRate.Intersect(set.mFrameRate);
       }
     }
   }
 }
 
+// MediaEngine helper
+//
+// The full algorithm for all devices. Sources that don't list capabilities
+// need to fake it and hardcode some by populating mHardcodedCapabilities above.
+//
+// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
+
+// First, all devices have a minimum distance based on their deviceId.
+// If you have no other constraints, use this one. Reused by all device types.
+
+uint32_t
+MediaConstraintsHelper::GetMinimumFitnessDistance(
+    const dom::MediaTrackConstraintSet &aConstraints,
+    bool aAdvanced,
+    const nsString& aDeviceId)
+{
+  uint64_t distance =
+    uint64_t(FitnessDistance(aDeviceId, aConstraints.mDeviceId, aAdvanced));
+
+  // This function is modeled on MediaEngineCameraVideoSource::GetFitnessDistance
+  // and will make more sense once more audio constraints are added.
+
+  return uint32_t(std::min(distance, uint64_t(UINT32_MAX)));
 }
+
+template<class ValueType, class ConstrainRange>
+/* static */ uint32_t
+MediaConstraintsHelper::FitnessDistance(ValueType aN,
+                                        const ConstrainRange& aRange)
+{
+  if ((aRange.mExact.WasPassed() && aRange.mExact.Value() != aN) ||
+      (aRange.mMin.WasPassed() && aRange.mMin.Value() > aN) ||
+      (aRange.mMax.WasPassed() && aRange.mMax.Value() < aN)) {
+    return UINT32_MAX;
+  }
+  if (!aRange.mIdeal.WasPassed() || aN == aRange.mIdeal.Value()) {
+    return 0;
+  }
+  return uint32_t(ValueType((std::abs(aN - aRange.mIdeal.Value()) * 1000) /
+                            std::max(std::abs(aN), std::abs(aRange.mIdeal.Value()))));
+}
+
+// Binding code doesn't templatize well...
+
+/*static*/ uint32_t
+MediaConstraintsHelper::FitnessDistance(int32_t aN,
+    const OwningLongOrConstrainLongRange& aConstraint, bool aAdvanced)
+{
+  if (aConstraint.IsLong()) {
+    ConstrainLongRange range;
+    (aAdvanced ? range.mExact : range.mIdeal).Construct(aConstraint.GetAsLong());
+    return FitnessDistance(aN, range);
+  } else {
+    return FitnessDistance(aN, aConstraint.GetAsConstrainLongRange());
+  }
+}
+
+/*static*/ uint32_t
+MediaConstraintsHelper::FitnessDistance(double aN,
+    const OwningDoubleOrConstrainDoubleRange& aConstraint,
+    bool aAdvanced)
+{
+  if (aConstraint.IsDouble()) {
+    ConstrainDoubleRange range;
+    (aAdvanced ? range.mExact : range.mIdeal).Construct(aConstraint.GetAsDouble());
+    return FitnessDistance(aN, range);
+  } else {
+    return FitnessDistance(aN, aConstraint.GetAsConstrainDoubleRange());
+  }
+}
+
+// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
+
+/* static */ uint32_t
+MediaConstraintsHelper::FitnessDistance(nsString aN,
+                             const ConstrainDOMStringParameters& aParams)
+{
+  struct Func
+  {
+    static bool
+    Contains(const OwningStringOrStringSequence& aStrings, nsString aN)
+    {
+      return aStrings.IsString() ? aStrings.GetAsString() == aN
+                                 : aStrings.GetAsStringSequence().Contains(aN);
+    }
+  };
+
+  if (aParams.mExact.WasPassed() && !Func::Contains(aParams.mExact.Value(), aN)) {
+    return UINT32_MAX;
+  }
+  if (aParams.mIdeal.WasPassed() && !Func::Contains(aParams.mIdeal.Value(), aN)) {
+    return 1000;
+  }
+  return 0;
+}
+
+/* static */ uint32_t
+MediaConstraintsHelper::FitnessDistance(nsString aN,
+    const OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint,
+    bool aAdvanced)
+{
+  if (aConstraint.IsString()) {
+    ConstrainDOMStringParameters params;
+    if (aAdvanced) {
+      params.mExact.Construct();
+      params.mExact.Value().SetAsString() = aConstraint.GetAsString();
+    } else {
+      params.mIdeal.Construct();
+      params.mIdeal.Value().SetAsString() = aConstraint.GetAsString();
+    }
+    return FitnessDistance(aN, params);
+  } else if (aConstraint.IsStringSequence()) {
+    ConstrainDOMStringParameters params;
+    if (aAdvanced) {
+      params.mExact.Construct();
+      params.mExact.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence();
+    } else {
+      params.mIdeal.Construct();
+      params.mIdeal.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence();
+    }
+    return FitnessDistance(aN, params);
+  } else {
+    return FitnessDistance(aN, aConstraint.GetAsConstrainDOMStringParameters());
+  }
+}
+
+}
--- a/dom/media/webrtc/MediaTrackConstraints.h
+++ b/dom/media/webrtc/MediaTrackConstraints.h
@@ -78,11 +78,34 @@ struct NormalizedConstraintSet
   , mFrameRate(aOther.mFrameRate, advanced) {}
 };
 
 struct FlattenedConstraints : public NormalizedConstraintSet
 {
   explicit FlattenedConstraints(const dom::MediaTrackConstraints& aOther);
 };
 
+// A helper class for MediaEngines
+
+class MediaConstraintsHelper
+{
+protected:
+  template<class ValueType, class ConstrainRange>
+  static uint32_t FitnessDistance(ValueType aN, const ConstrainRange& aRange);
+  static uint32_t FitnessDistance(int32_t aN,
+      const dom::OwningLongOrConstrainLongRange& aConstraint, bool aAdvanced);
+  static uint32_t FitnessDistance(double aN,
+      const dom::OwningDoubleOrConstrainDoubleRange& aConstraint, bool aAdvanced);
+  static uint32_t FitnessDistance(nsString aN,
+    const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint,
+    bool aAdvanced);
+  static uint32_t FitnessDistance(nsString aN,
+      const dom::ConstrainDOMStringParameters& aParams);
+
+  static uint32_t
+  GetMinimumFitnessDistance(const dom::MediaTrackConstraintSet &aConstraints,
+                            bool aAdvanced,
+                            const nsString& aDeviceId);
+};
+
 }
 
 #endif /* MEDIATRACKCONSTRAINTS_H_ */
--- a/dom/webidl/MediaStream.webidl
+++ b/dom/webidl/MediaStream.webidl
@@ -12,23 +12,23 @@
 
 // These dictionaries need to be in a separate file from their
 // MediaTrackConstraints* counterparts due to a webidl compiler limitation.
 
 dictionary MediaStreamConstraints {
     (boolean or MediaTrackConstraints) audio = false;
     (boolean or MediaTrackConstraints) video = false;
     boolean picture = false; // Mozilla legacy
-    boolean fake = false; // For testing purpose. Generates frames of solid
-                          // colors if video is enabled, and sound of 1Khz sine
-                          // wave if audio is enabled.
-    boolean fakeTracks = false; // For testing purpose, works only if fake is
-                                // enabled. Enable fakeTracks returns a stream
-                                // with two extra empty video tracks and three
-                                // extra empty audio tracks.
+    boolean fake;       // For testing purpose. Generates frames of solid
+                        // colors if video is enabled, and sound of 1Khz sine
+                        // wave if audio is enabled.
+    boolean fakeTracks; // For testing purpose, works only if fake is
+                        // enabled. Enable fakeTracks returns a stream
+                        // with two extra empty video tracks and three
+                        // extra empty audio tracks.
     DOMString? peerIdentity = null;
 };
 
 interface MediaStream : EventTarget {
     readonly attribute DOMString    id;
     sequence<AudioStreamTrack> getAudioTracks();
     sequence<VideoStreamTrack> getVideoTracks();
     sequence<MediaStreamTrack> getTracks();
--- a/dom/webidl/MediaTrackConstraintSet.webidl
+++ b/dom/webidl/MediaTrackConstraintSet.webidl
@@ -10,29 +10,31 @@
 enum SupportedVideoConstraints {
     "other",
     "facingMode",
     "width",
     "height",
     "frameRate",
     "mediaSource",
     "browserWindow",
-    "scrollWithPage"
+    "scrollWithPage",
+    "deviceId"
 };
 
 enum SupportedAudioConstraints {
     "other"
 };
 
 dictionary MediaTrackConstraintSet {
     ConstrainLong width;
     ConstrainLong height;
     ConstrainDouble frameRate;
     ConstrainDOMString facingMode;
     DOMString mediaSource = "camera";
     long long browserWindow;
     boolean scrollWithPage;
+    ConstrainDOMString deviceId;
 };
 
 typedef (long or ConstrainLongRange) ConstrainLong;
 typedef (double or ConstrainDoubleRange) ConstrainDouble;
 typedef (boolean or ConstrainBooleanParameters) ConstrainBoolean;
 typedef (DOMString or sequence<DOMString> or ConstrainDOMStringParameters) ConstrainDOMString;