Bug 1213517 - Report correct constraint in OverconstrainedError when constraints conflict directly. draft
authorJan-Ivar Bruaroey <jib@mozilla.com>
Wed, 15 Jun 2016 19:25:07 -0400
changeset 388774 ae00b87697d231279b8c9a1f478e4cbbdebb8ee3
parent 388773 4394fe7cd5eab13510526812f7d6edde014ab33f
child 388775 3ba1a9ed8871131440c083e89619908bebeb0299
push id23232
push userjbruaroey@mozilla.com
push dateSun, 17 Jul 2016 21:00:46 +0000
bugs1213517
milestone50.0a1
Bug 1213517 - Report correct constraint in OverconstrainedError when constraints conflict directly. MozReview-Commit-ID: 2bVaSvntc8g
dom/media/MediaManager.cpp
dom/media/MediaManager.h
dom/media/webrtc/MediaEngine.h
dom/media/webrtc/MediaEngineDefault.cpp
dom/media/webrtc/MediaEngineDefault.h
dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
dom/media/webrtc/MediaEngineRemoteVideoSource.h
dom/media/webrtc/MediaEngineTabVideoSource.cpp
dom/media/webrtc/MediaEngineTabVideoSource.h
dom/media/webrtc/MediaEngineWebRTC.h
dom/media/webrtc/MediaEngineWebRTCAudio.cpp
dom/media/webrtc/MediaTrackConstraints.cpp
dom/media/webrtc/MediaTrackConstraints.h
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -883,24 +883,28 @@ VideoDevice::GetSource()
 AudioDevice::Source*
 AudioDevice::GetSource()
 {
   return static_cast<Source*>(&*mSource);
 }
 
 nsresult MediaDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
                                const MediaEnginePrefs &aPrefs,
-                               const nsACString& aOrigin) {
+                               const nsACString& aOrigin,
+                               const char** aOutBadConstraint) {
   return GetSource()->Allocate(aConstraints, aPrefs, mID, aOrigin,
-                               getter_AddRefs(mAllocationHandle));
+                               getter_AddRefs(mAllocationHandle),
+                               aOutBadConstraint);
 }
 
 nsresult MediaDevice::Restart(const dom::MediaTrackConstraints &aConstraints,
-                              const MediaEnginePrefs &aPrefs) {
-  return GetSource()->Restart(mAllocationHandle, aConstraints, aPrefs, mID);
+                              const MediaEnginePrefs &aPrefs,
+                              const char** aOutBadConstraint) {
+  return GetSource()->Restart(mAllocationHandle, aConstraints, aPrefs, mID,
+                              aOutBadConstraint);
 }
 
 nsresult MediaDevice::Deallocate() {
   return GetSource()->Deallocate(mAllocationHandle);
 }
 
 void
 MediaOperationTask::ReturnCallbackError(nsresult rv, const char* errorLog)
@@ -1423,33 +1427,33 @@ public:
     // a GetUserMediaStreamRunnable.
 
     nsresult rv;
     const char* errorMsg = nullptr;
     const char* badConstraint = nullptr;
 
     if (mAudioDevice) {
       auto& constraints = GetInvariant(mConstraints.mAudio);
-      rv = mAudioDevice->Allocate(constraints, mPrefs, mOrigin);
+      rv = mAudioDevice->Allocate(constraints, mPrefs, mOrigin, &badConstraint);
       if (NS_FAILED(rv)) {
         errorMsg = "Failed to allocate audiosource";
-        if (rv == NS_ERROR_NOT_AVAILABLE) {
+        if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
           nsTArray<RefPtr<AudioDevice>> audios;
           audios.AppendElement(mAudioDevice);
           badConstraint = MediaConstraintsHelper::SelectSettings(constraints,
                                                                  audios);
         }
       }
     }
     if (!errorMsg && mVideoDevice) {
       auto& constraints = GetInvariant(mConstraints.mVideo);
-      rv = mVideoDevice->Allocate(constraints, mPrefs, mOrigin);
+      rv = mVideoDevice->Allocate(constraints, mPrefs, mOrigin, &badConstraint);
       if (NS_FAILED(rv)) {
         errorMsg = "Failed to allocate videosource";
-        if (rv == NS_ERROR_NOT_AVAILABLE) {
+        if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
           nsTArray<RefPtr<VideoDevice>> videos;
           videos.AppendElement(mVideoDevice);
           badConstraint = MediaConstraintsHelper::SelectSettings(constraints,
                                                                  videos);
         }
         if (mAudioDevice) {
           mAudioDevice->Deallocate();
         }
@@ -3430,26 +3434,26 @@ GetUserMediaCallbackMediaStreamListener:
                                       audioDevice, videoDevice,
                                       aConstraints]() mutable {
     MOZ_ASSERT(MediaManager::IsInMediaThread());
     RefPtr<MediaManager> mgr = MediaManager::GetInstance();
     const char* badConstraint = nullptr;
     nsresult rv = NS_OK;
 
     if (audioDevice) {
-      rv = audioDevice->Restart(aConstraints, mgr->mPrefs);
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+      rv = audioDevice->Restart(aConstraints, mgr->mPrefs, &badConstraint);
+      if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
         nsTArray<RefPtr<AudioDevice>> audios;
         audios.AppendElement(audioDevice);
         badConstraint = MediaConstraintsHelper::SelectSettings(aConstraints,
                                                                audios);
       }
     } else {
-      rv = videoDevice->Restart(aConstraints, mgr->mPrefs);
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+      rv = videoDevice->Restart(aConstraints, mgr->mPrefs, &badConstraint);
+      if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
         nsTArray<RefPtr<VideoDevice>> videos;
         videos.AppendElement(videoDevice);
         badConstraint = MediaConstraintsHelper::SelectSettings(aConstraints,
                                                                videos);
       }
     }
     NS_DispatchToMainThread(NewRunnableFrom([id, windowId, rv,
                                              badConstraint]() mutable {
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -69,19 +69,21 @@ public:
   NS_DECL_NSIMEDIADEVICE
 
   void SetId(const nsAString& aID);
   virtual uint32_t GetBestFitnessDistance(
       const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets);
   virtual Source* GetSource() = 0;
   nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
                     const MediaEnginePrefs &aPrefs,
-                    const nsACString& aOrigin);
+                    const nsACString& aOrigin,
+                    const char** aOutBadConstraint);
   nsresult Restart(const dom::MediaTrackConstraints &aConstraints,
-                   const MediaEnginePrefs &aPrefs);
+                   const MediaEnginePrefs &aPrefs,
+                   const char** aOutBadConstraint);
   nsresult Deallocate();
 protected:
   virtual ~MediaDevice() {}
   explicit MediaDevice(MediaEngineSource* aSource, bool aIsVideo);
 
   static uint32_t FitnessDistance(nsString aN,
     const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint);
 private:
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -147,17 +147,18 @@ public:
 
   /* Stop the device and release the corresponding MediaStream */
   virtual nsresult Stop(SourceMediaStream *aSource, TrackID aID) = 0;
 
   /* Restart with new capability */
   virtual nsresult Restart(BaseAllocationHandle* aHandle,
                            const dom::MediaTrackConstraints& aConstraints,
                            const MediaEnginePrefs &aPrefs,
-                           const nsString& aDeviceId) = 0;
+                           const nsString& aDeviceId,
+                           const char** aOutBadConstraint) = 0;
 
   /* Returns true if a source represents a fake capture device and
    * false otherwise
    */
   virtual bool IsFake() = 0;
 
   /* Returns the type of media source (camera, microphone, screen, window, etc) */
   virtual dom::MediaSourceEnum GetMediaSource() const = 0;
@@ -184,17 +185,18 @@ public:
     mHasFakeTracks = aHasFakeTracks;
   }
 
   /* This call reserves but does not start the device. */
   virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
                             const MediaEnginePrefs &aPrefs,
                             const nsString& aDeviceId,
                             const nsACString& aOrigin,
-                            BaseAllocationHandle** aOutHandle) = 0;
+                            BaseAllocationHandle** aOutHandle,
+                            const char** aOutBadConstraint) = 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)
--- a/dom/media/webrtc/MediaEngineDefault.cpp
+++ b/dom/media/webrtc/MediaEngineDefault.cpp
@@ -85,17 +85,18 @@ MediaEngineDefaultVideoSource::GetBestFi
   return distance;
 }
 
 nsresult
 MediaEngineDefaultVideoSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
                                         const MediaEnginePrefs &aPrefs,
                                         const nsString& aDeviceId,
                                         const nsACString& aOrigin,
-                                        BaseAllocationHandle** aOutHandle)
+                                        BaseAllocationHandle** aOutHandle,
+                                        const char** aOutBadConstraint)
 {
   if (mState != kReleased) {
     return NS_ERROR_FAILURE;
   }
 
   // Mock failure for automated tests.
   if (aConstraints.mDeviceId.IsString() &&
       aConstraints.mDeviceId.GetAsString().EqualsASCII("bad device")) {
@@ -216,17 +217,18 @@ MediaEngineDefaultVideoSource::Stop(Sour
   return NS_OK;
 }
 
 nsresult
 MediaEngineDefaultVideoSource::Restart(
     BaseAllocationHandle* aHandle,
     const dom::MediaTrackConstraints& aConstraints,
     const MediaEnginePrefs &aPrefs,
-    const nsString& aDeviceId)
+    const nsString& aDeviceId,
+    const char** aOutBadConstraint)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MediaEngineDefaultVideoSource::Notify(nsITimer* aTimer)
 {
   // Update the target color
@@ -411,17 +413,18 @@ MediaEngineDefaultAudioSource::GetBestFi
   return distance;
 }
 
 nsresult
 MediaEngineDefaultAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
                                         const MediaEnginePrefs &aPrefs,
                                         const nsString& aDeviceId,
                                         const nsACString& aOrigin,
-                                        BaseAllocationHandle** aOutHandle)
+                                        BaseAllocationHandle** aOutHandle,
+                                        const char** aOutBadConstraint)
 {
   if (mState != kReleased) {
     return NS_ERROR_FAILURE;
   }
 
   // Mock failure for automated tests.
   if (aConstraints.mDeviceId.IsString() &&
       aConstraints.mDeviceId.GetAsString().EqualsASCII("bad device")) {
@@ -525,17 +528,18 @@ MediaEngineDefaultAudioSource::Stop(Sour
   mState = kStopped;
   return NS_OK;
 }
 
 nsresult
 MediaEngineDefaultAudioSource::Restart(BaseAllocationHandle* aHandle,
                                        const dom::MediaTrackConstraints& aConstraints,
                                        const MediaEnginePrefs &aPrefs,
-                                       const nsString& aDeviceId)
+                                       const nsString& aDeviceId,
+                                       const char** aOutBadConstraint)
 {
   return NS_OK;
 }
 
 void
 MediaEngineDefaultAudioSource::AppendToSegment(AudioSegment& aSegment,
                                                TrackTicks aSamples)
 {
--- a/dom/media/webrtc/MediaEngineDefault.h
+++ b/dom/media/webrtc/MediaEngineDefault.h
@@ -43,24 +43,26 @@ public:
 
   void GetName(nsAString&) override;
   void GetUUID(nsACString&) override;
 
   nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
                     const MediaEnginePrefs &aPrefs,
                     const nsString& aDeviceId,
                     const nsACString& aOrigin,
-                    BaseAllocationHandle** aOutHandle) override;
+                    BaseAllocationHandle** aOutHandle,
+                    const char** aOutBadConstraint) override;
   nsresult Deallocate(BaseAllocationHandle* aHandle) override;
   nsresult Start(SourceMediaStream*, TrackID, const PrincipalHandle&) override;
   nsresult Stop(SourceMediaStream*, TrackID) override;
   nsresult Restart(BaseAllocationHandle* aHandle,
                    const dom::MediaTrackConstraints& aConstraints,
                    const MediaEnginePrefs &aPrefs,
-                   const nsString& aDeviceId) override;
+                   const nsString& aDeviceId,
+                   const char** aOutBadConstraint) override;
   void SetDirectListeners(bool aHasDirectListeners) override {};
   void NotifyPull(MediaStreamGraph* aGraph,
                   SourceMediaStream *aSource,
                   TrackID aId,
                   StreamTime aDesiredTime,
                   const PrincipalHandle& aPrincipalHandle) override;
   uint32_t GetBestFitnessDistance(
       const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
@@ -116,24 +118,26 @@ public:
 
   void GetName(nsAString&) override;
   void GetUUID(nsACString&) override;
 
   nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
                     const MediaEnginePrefs &aPrefs,
                     const nsString& aDeviceId,
                     const nsACString& aOrigin,
-                    BaseAllocationHandle** aOutHandle) override;
+                    BaseAllocationHandle** aOutHandle,
+                    const char** aOutBadConstraint) override;
   nsresult Deallocate(BaseAllocationHandle* aHandle) override;
   nsresult Start(SourceMediaStream*, TrackID, const PrincipalHandle&) override;
   nsresult Stop(SourceMediaStream*, TrackID) override;
   nsresult Restart(BaseAllocationHandle* aHandle,
                    const dom::MediaTrackConstraints& aConstraints,
                    const MediaEnginePrefs &aPrefs,
-                   const nsString& aDeviceId) override;
+                   const nsString& aDeviceId,
+                   const char** aOutBadConstraint) override;
   void SetDirectListeners(bool aHasDirectListeners) override {};
   void AppendToSegment(AudioSegment& aSegment,
                        TrackTicks aSamples);
   void NotifyPull(MediaStreamGraph* aGraph,
                   SourceMediaStream *aSource,
                   TrackID aId,
                   StreamTime aDesiredTime,
                   const PrincipalHandle& aPrincipalHandle) override
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
@@ -98,17 +98,18 @@ MediaEngineRemoteVideoSource::Shutdown()
 }
 
 nsresult
 MediaEngineRemoteVideoSource::Allocate(
     const dom::MediaTrackConstraints& aConstraints,
     const MediaEnginePrefs& aPrefs,
     const nsString& aDeviceId,
     const nsACString& aOrigin,
-    BaseAllocationHandle** aOutHandle)
+    BaseAllocationHandle** aOutHandle,
+    const char** aOutBadConstraint)
 {
   LOG((__PRETTY_FUNCTION__));
   AssertIsOnOwningThread();
 
   if (!mInitDone) {
     LOG(("Init not done"));
     return NS_ERROR_FAILURE;
   }
@@ -116,17 +117,18 @@ MediaEngineRemoteVideoSource::Allocate(
   AutoTArray<const NormalizedConstraints*, 10> allConstraints;
   for (auto& registered : mRegisteredHandles) {
     allConstraints.AppendElement(&registered->mConstraints);
   }
   RefPtr<AllocationHandle> handle = new AllocationHandle(aConstraints);
   allConstraints.AppendElement(&handle->mConstraints);
 
   NormalizedConstraints netConstraints(allConstraints);
-  if (netConstraints.mOverconstrained) {
+  if (netConstraints.mBadConstraint) {
+    *aOutBadConstraint = netConstraints.mBadConstraint;
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   if (!ChooseCapability(netConstraints, aPrefs, aDeviceId)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   if (mState == kReleased) {
@@ -278,17 +280,18 @@ MediaEngineRemoteVideoSource::Stop(mozil
 
   return NS_OK;
 }
 
 nsresult
 MediaEngineRemoteVideoSource::Restart(BaseAllocationHandle* aHandle,
                                       const dom::MediaTrackConstraints& aConstraints,
                                       const MediaEnginePrefs& aPrefs,
-                                      const nsString& aDeviceId)
+                                      const nsString& aDeviceId,
+                                      const char** aOutBadConstraint)
 {
   AssertIsOnOwningThread();
   if (!mInitDone) {
     LOG(("Init not done"));
     return NS_ERROR_FAILURE;
   }
   MOZ_ASSERT(aHandle);
   auto handle = static_cast<AllocationHandle*>(aHandle);
@@ -300,17 +303,18 @@ MediaEngineRemoteVideoSource::Restart(Ba
     if (registered.get() == handle) {
       continue; // Don't count old constraints
     }
     allConstraints.AppendElement(&registered->mConstraints);
   }
   allConstraints.AppendElement(&temp->mConstraints);
 
   NormalizedConstraints netConstraints(allConstraints);
-  if (netConstraints.mOverconstrained) {
+  if (netConstraints.mBadConstraint) {
+    *aOutBadConstraint = netConstraints.mBadConstraint;
     return NS_ERROR_FAILURE;
   }
 
   if (!ChooseCapability(netConstraints, aPrefs, aDeviceId)) {
     return NS_ERROR_FAILURE;
   }
   if (mState != kStarted) {
     return NS_OK;
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.h
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.h
@@ -81,24 +81,26 @@ public:
   public:
     NormalizedConstraints mConstraints;
   };
 
   nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                     const MediaEnginePrefs& aPrefs,
                     const nsString& aDeviceId,
                     const nsACString& aOrigin,
-                    BaseAllocationHandle** aOutHandle) override;
+                    BaseAllocationHandle** aOutHandle,
+                    const char** aOutBadConstraint) override;
   nsresult Deallocate(BaseAllocationHandle* aHandle) override;
   nsresult Start(SourceMediaStream*, TrackID, const PrincipalHandle&) override;
   nsresult Stop(SourceMediaStream*, TrackID) override;
   nsresult Restart(BaseAllocationHandle* aHandle,
                    const dom::MediaTrackConstraints& aConstraints,
                    const MediaEnginePrefs &aPrefs,
-                   const nsString& aDeviceId) override;
+                   const nsString& aDeviceId,
+                   const char** aOutBadConstraint) override;
   void NotifyPull(MediaStreamGraph* aGraph,
                   SourceMediaStream* aSource,
                   TrackID aId,
                   StreamTime aDesiredTime,
                   const PrincipalHandle& aPrincipalHandle) override;
   dom::MediaSourceEnum GetMediaSource() const override {
     return mMediaSource;
   }
--- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -135,32 +135,34 @@ MediaEngineTabVideoSource::GetUUID(nsACS
 #define DEFAULT_TABSHARE_VIDEO_MAX_HEIGHT 4096
 #define DEFAULT_TABSHARE_VIDEO_FRAMERATE 30
 
 nsresult
 MediaEngineTabVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
                                     const MediaEnginePrefs& aPrefs,
                                     const nsString& aDeviceId,
                                     const nsACString& aOrigin,
-                                    BaseAllocationHandle** aOutHandle)
+                                    BaseAllocationHandle** aOutHandle,
+                                    const char** aOutBadConstraint)
 {
   // windowId is not a proper constraint, so just read it.
   // It has no well-defined behavior in advanced, so ignore it there.
 
   mWindowId = aConstraints.mBrowserWindow.WasPassed() ?
               aConstraints.mBrowserWindow.Value() : -1;
   aOutHandle = nullptr;
-  return Restart(nullptr, aConstraints, aPrefs, aDeviceId);
+  return Restart(nullptr, aConstraints, aPrefs, aDeviceId, aOutBadConstraint);
 }
 
 nsresult
 MediaEngineTabVideoSource::Restart(BaseAllocationHandle* aHandle,
                                    const dom::MediaTrackConstraints& aConstraints,
                                    const mozilla::MediaEnginePrefs& aPrefs,
-                                   const nsString& aDeviceId)
+                                   const nsString& aDeviceId,
+                                   const char** aOutBadConstraint)
 {
   MOZ_ASSERT(!aHandle);
 
   // scrollWithPage is not proper a constraint, so just read it.
   // It has no well-defined behavior in advanced, so ignore it there.
 
   mScrollWithPage = aConstraints.mScrollWithPage.WasPassed() ?
                     aConstraints.mScrollWithPage.Value() : false;
--- a/dom/media/webrtc/MediaEngineTabVideoSource.h
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.h
@@ -21,26 +21,28 @@ class MediaEngineTabVideoSource : public
 
     void Shutdown() override {};
     void GetName(nsAString_internal&) override;
     void GetUUID(nsACString_internal&) override;
     nsresult Allocate(const dom::MediaTrackConstraints &,
                       const mozilla::MediaEnginePrefs&,
                       const nsString& aDeviceId,
                       const nsACString& aOrigin,
-                      BaseAllocationHandle** aOutHandle) override;
+                      BaseAllocationHandle** aOutHandle,
+                      const char** aOutBadConstraint) override;
     nsresult Deallocate(BaseAllocationHandle* aHandle) override;
     nsresult Start(mozilla::SourceMediaStream*, mozilla::TrackID, const mozilla::PrincipalHandle&) override;
     void SetDirectListeners(bool aHasDirectListeners) override {};
     void NotifyPull(mozilla::MediaStreamGraph*, mozilla::SourceMediaStream*, mozilla::TrackID, mozilla::StreamTime, const mozilla::PrincipalHandle& aPrincipalHandle) override;
     nsresult Stop(mozilla::SourceMediaStream*, mozilla::TrackID) override;
     nsresult Restart(BaseAllocationHandle* aHandle,
                      const dom::MediaTrackConstraints& aConstraints,
                      const mozilla::MediaEnginePrefs& aPrefs,
-                     const nsString& aDeviceId) override;
+                     const nsString& aDeviceId,
+                     const char** aOutBadConstraint) override;
     bool IsFake() override;
     dom::MediaSourceEnum GetMediaSource() const override {
       return dom::MediaSourceEnum::Browser;
     }
     uint32_t GetBestFitnessDistance(
       const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
       const nsString& aDeviceId) override
     {
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -73,17 +73,18 @@ public:
   {
   }
   void GetName(nsAString& aName) override;
   void GetUUID(nsACString& aUUID) override;
   nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                     const MediaEnginePrefs& aPrefs,
                     const nsString& aDeviceId,
                     const nsACString& aOrigin,
-                    BaseAllocationHandle** aOutHandle) override
+                    BaseAllocationHandle** aOutHandle,
+                    const char** aOutBadConstraint) override
   {
     // Nothing to do here, everything is managed in MediaManager.cpp
     aOutHandle = nullptr;
     return NS_OK;
   }
   nsresult Deallocate(BaseAllocationHandle* aHandle) override
   {
     // Nothing to do here, everything is managed in MediaManager.cpp
@@ -96,17 +97,18 @@ public:
   }
   nsresult Start(SourceMediaStream* aMediaStream,
                  TrackID aId,
                  const PrincipalHandle& aPrincipalHandle) override;
   nsresult Stop(SourceMediaStream* aMediaStream, TrackID aId) override;
   nsresult Restart(BaseAllocationHandle* aHandle,
                    const dom::MediaTrackConstraints& aConstraints,
                    const MediaEnginePrefs &aPrefs,
-                   const nsString& aDeviceId) override;
+                   const nsString& aDeviceId,
+                   const char** aOutBadConstraint) override;
   void SetDirectListeners(bool aDirect) override
   {}
   void NotifyOutputData(MediaStreamGraph* aGraph,
                         AudioDataValue* aBuffer, size_t aFrames,
                         TrackRate aRate, uint32_t aChannels) override
   {}
   void DeviceChanged() override
   {}
@@ -451,26 +453,28 @@ public:
 
   void GetName(nsAString& aName) override;
   void GetUUID(nsACString& aUUID) override;
 
   nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                     const MediaEnginePrefs& aPrefs,
                     const nsString& aDeviceId,
                     const nsACString& aOrigin,
-                    BaseAllocationHandle** aOutHandle) override;
+                    BaseAllocationHandle** aOutHandle,
+                    const char** aOutBadConstraint) override;
   nsresult Deallocate(BaseAllocationHandle* aHandle) override;
   nsresult Start(SourceMediaStream* aStream,
                  TrackID aID,
                  const PrincipalHandle& aPrincipalHandle) override;
   nsresult Stop(SourceMediaStream* aSource, TrackID aID) override;
   nsresult Restart(BaseAllocationHandle* aHandle,
                    const dom::MediaTrackConstraints& aConstraints,
                    const MediaEnginePrefs &aPrefs,
-                   const nsString& aDeviceId) override;
+                   const nsString& aDeviceId,
+                   const char** aOutBadConstraint) override;
   void SetDirectListeners(bool aHasDirectListeners) override {};
 
   void NotifyPull(MediaStreamGraph* aGraph,
                   SourceMediaStream* aSource,
                   TrackID aId,
                   StreamTime aDesiredTime,
                   const PrincipalHandle& aPrincipalHandle) override;
 
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -218,17 +218,18 @@ uint32_t MediaEngineWebRTCMicrophoneSour
   return distance;
 }
 
 nsresult
 MediaEngineWebRTCMicrophoneSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
                                             const MediaEnginePrefs &aPrefs,
                                             const nsString& aDeviceId,
                                             const nsACString& aOrigin,
-                                            BaseAllocationHandle** aOutHandle)
+                                            BaseAllocationHandle** aOutHandle,
+                                            const char** aOutBadConstraint)
 {
   AssertIsOnOwningThread();
   if (mState == kReleased) {
     if (sChannelsOpen == 0) {
       if (!InitEngine()) {
         LOG(("Audio engine is not initalized"));
         return NS_ERROR_FAILURE;
       }
@@ -255,24 +256,25 @@ MediaEngineWebRTCMicrophoneSource::Alloc
     if (mSources.IsEmpty()) {
       LOG(("Audio device %d reallocated", mCapIndex));
     } else {
       LOG(("Audio device %d allocated shared", mCapIndex));
     }
   }
   ++mNrAllocations;
   aOutHandle = nullptr;
-  return Restart(nullptr, aConstraints, aPrefs, aDeviceId);
+  return Restart(nullptr, aConstraints, aPrefs, aDeviceId, aOutBadConstraint);
 }
 
 nsresult
 MediaEngineWebRTCMicrophoneSource::Restart(BaseAllocationHandle* aHandle,
                                            const dom::MediaTrackConstraints& aConstraints,
                                            const MediaEnginePrefs &aPrefs,
-                                           const nsString& aDeviceId)
+                                           const nsString& aDeviceId,
+                                           const char** aOutBadConstraint)
 {
   MOZ_ASSERT(!aHandle);
   FlattenedConstraints c(aConstraints);
 
   bool aec_on = c.mEchoCancellation.Get(aPrefs.mAecOn);
   bool agc_on = c.mMozAutoGainControl.Get(aPrefs.mAgcOn);
   bool noise_on = c.mMozNoiseSuppression.Get(aPrefs.mNoiseOn);
 
@@ -843,17 +845,18 @@ MediaEngineWebRTCAudioCaptureSource::Sto
   return NS_OK;
 }
 
 nsresult
 MediaEngineWebRTCAudioCaptureSource::Restart(
     BaseAllocationHandle* aHandle,
     const dom::MediaTrackConstraints& aConstraints,
     const MediaEnginePrefs &aPrefs,
-    const nsString& aDeviceId)
+    const nsString& aDeviceId,
+    const char** aOutBadConstraint)
 {
   MOZ_ASSERT(!aHandle);
   return NS_OK;
 }
 
 uint32_t
 MediaEngineWebRTCAudioCaptureSource::GetBestFitnessDistance(
     const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
--- a/dom/media/webrtc/MediaTrackConstraints.cpp
+++ b/dom/media/webrtc/MediaTrackConstraints.cpp
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaTrackConstraints.h"
 
 #include <limits>
+#include <algorithm>
+#include <iterator>
 
 namespace mozilla {
 
 template<class ValueType>
 template<class ConstrainRange>
 void
 NormalizedConstraintSet::Range<ValueType>::SetFrom(const ConstrainRange& aOther)
 {
@@ -195,73 +197,126 @@ NormalizedConstraintSet::StringRange::Cl
 }
 
 bool
 NormalizedConstraintSet::StringRange::Intersects(const StringRange& aOther) const
 {
   if (!mExact.size() || !aOther.mExact.size()) {
     return true;
   }
-  for (auto& entry : aOther.mExact) {
-    if (mExact.find(entry) != mExact.end()) {
-      return true;
-    }
-  }
-  return false;
+
+  ValueType intersection;
+  set_intersection(mExact.begin(), mExact.end(),
+                   aOther.mExact.begin(), aOther.mExact.end(),
+                   std::inserter(intersection, intersection.begin()));
+  return !!intersection.size();
 }
 
 void
 NormalizedConstraintSet::StringRange::Intersect(const StringRange& aOther)
 {
   if (!aOther.mExact.size()) {
     return;
   }
-  for (auto& entry : mExact) {
-    if (aOther.mExact.find(entry) == aOther.mExact.end()) {
-      mExact.erase(entry);
-    }
+
+  ValueType intersection;
+  set_intersection(mExact.begin(), mExact.end(),
+                   aOther.mExact.begin(), aOther.mExact.end(),
+                   std::inserter(intersection, intersection.begin()));
+  mExact = intersection;
+}
+
+bool
+NormalizedConstraintSet::StringRange::Merge(const StringRange& aOther)
+{
+  if (!Intersects(aOther)) {
+    return false;
   }
+  Intersect(aOther);
+
+  ValueType unioned;
+  set_union(mIdeal.begin(), mIdeal.end(),
+            aOther.mIdeal.begin(), aOther.mIdeal.end(),
+            std::inserter(unioned, unioned.begin()));
+  mIdeal = unioned;
+  return true;
 }
 
 NormalizedConstraints::NormalizedConstraints(const dom::MediaTrackConstraints& aOther)
-: NormalizedConstraintSet(aOther, false), mOverconstrained(false)
+: NormalizedConstraintSet(aOther, false), mBadConstraint(nullptr)
 {
   if (aOther.mAdvanced.WasPassed()) {
     for (auto& entry : aOther.mAdvanced.Value()) {
       mAdvanced.AppendElement(NormalizedConstraintSet(entry, true));
     }
   }
 }
 
 // Merge constructor. Create net constraints out of merging a set of others.
 
 NormalizedConstraints::NormalizedConstraints(
     const nsTArray<const NormalizedConstraints*>& aOthers)
   : NormalizedConstraintSet(*aOthers[0])
-  , mOverconstrained(false)
+  , mBadConstraint(nullptr)
 {
   // Do intersection of all required constraints, and average of ideals.
 
   for (uint32_t i = 1; i < aOthers.Length(); i++) {
     auto& set = *aOthers[i];
 
-    if (!mWidth.Merge(set.mWidth) ||
-        !mHeight.Merge(set.mHeight) ||
-        !mFrameRate.Merge(set.mFrameRate) ||
-        !mFacingMode.Merge(set.mFacingMode) ||
-        mMediaSource != set.mMediaSource ||
-        mBrowserWindow != set.mBrowserWindow ||
-        !mViewportOffsetX.Merge(set.mViewportOffsetX) ||
-        !mViewportOffsetY.Merge(set.mViewportOffsetY) ||
-        !mViewportWidth.Merge(set.mViewportWidth) ||
-        !mViewportHeight.Merge(set.mViewportHeight) ||
-        !mEchoCancellation.Merge(set.mEchoCancellation) ||
-        !mMozNoiseSuppression.Merge(set.mMozNoiseSuppression) ||
-        !mMozAutoGainControl.Merge(set.mMozAutoGainControl)) {
-      mOverconstrained = true;
+    if (!mWidth.Merge(set.mWidth)) {
+      mBadConstraint = "width";
+      return;
+    }
+    if (!mHeight.Merge(set.mHeight)) {
+      mBadConstraint = "height";
+      return;
+    }
+    if (!mFrameRate.Merge(set.mFrameRate)) {
+      mBadConstraint = "frameRate";
+      return;
+    }
+    if (!mFacingMode.Merge(set.mFacingMode)) {
+      mBadConstraint = "facingMode";
+      return;
+    }
+    if (mMediaSource != set.mMediaSource) {
+      mBadConstraint = "mediaSource";
+      return;
+    }
+    if (mBrowserWindow != set.mBrowserWindow) {
+      mBadConstraint = "browserWindow";
+      return;
+    }
+    if (!mViewportOffsetX.Merge(set.mViewportOffsetX)) {
+      mBadConstraint = "viewportOffsetX";
+      return;
+    }
+    if (!mViewportOffsetY.Merge(set.mViewportOffsetY)) {
+      mBadConstraint = "viewportOffsetY";
+      return;
+    }
+    if (!mViewportWidth.Merge(set.mViewportWidth)) {
+      mBadConstraint = "viewportWidth";
+      return;
+    }
+    if (!mViewportHeight.Merge(set.mViewportHeight)) {
+      mBadConstraint = "viewportHeight";
+      return;
+    }
+    if (!mEchoCancellation.Merge(set.mEchoCancellation)) {
+      mBadConstraint = "echoCancellation";
+      return;
+    }
+    if (!mMozNoiseSuppression.Merge(set.mMozNoiseSuppression)) {
+      mBadConstraint = "mozNoiseSuppression";
+      return;
+    }
+    if (!mMozAutoGainControl.Merge(set.mMozAutoGainControl)) {
+      mBadConstraint = "mozAutoGainControl";
       return;
     }
 
     for (auto& entry : set.mAdvanced) {
       mAdvanced.AppendElement(entry);
     }
   }
   mWidth.FinalizeMerge();
--- a/dom/media/webrtc/MediaTrackConstraints.h
+++ b/dom/media/webrtc/MediaTrackConstraints.h
@@ -118,30 +118,17 @@ struct NormalizedConstraintSet
 
     void SetFrom(const dom::ConstrainDOMStringParameters& aOther);
     ValueType Clamp(const ValueType& n) const;
     ValueType Get(const ValueType& defaultValue) const {
       return Clamp(mIdeal.size() ? mIdeal : defaultValue);
     }
     bool Intersects(const StringRange& aOther) const;
     void Intersect(const StringRange& aOther);
-    bool Merge(const StringRange& aOther)
-    {
-      if (!Intersects(aOther)) {
-        return false;
-      }
-      Intersect(aOther);
-
-      for (auto& entry : aOther.mIdeal) {
-        if (mIdeal.find(entry) == mIdeal.end()) {
-          mIdeal.insert(entry);
-        }
-      }
-      return true;
-    }
+    bool Merge(const StringRange& aOther);
     void FinalizeMerge() {}
   };
 
   // All new constraints should be added here whether they use flattening or not
   LongRange mWidth, mHeight;
   DoubleRange mFrameRate;
   StringRange mFacingMode;
   nsString mMediaSource;
@@ -178,17 +165,17 @@ template<> void NormalizedConstraintSet:
 // Used instead of MediaTrackConstraints in lower-level code.
 struct NormalizedConstraints : public NormalizedConstraintSet
 {
   explicit NormalizedConstraints(const dom::MediaTrackConstraints& aOther);
   explicit NormalizedConstraints(
       const nsTArray<const NormalizedConstraints*>& aOthers);
 
   nsTArray<NormalizedConstraintSet> mAdvanced;
-  bool mOverconstrained;
+  const char* mBadConstraint;
 };
 
 // Flattened version is used in low-level code with orthogonal constraints only.
 struct FlattenedConstraints : public NormalizedConstraintSet
 {
   explicit FlattenedConstraints(const NormalizedConstraints& aOther);
 
   explicit FlattenedConstraints(const dom::MediaTrackConstraints& aOther)