Bug 1213517 - Normalize even more of the constraints code. r=padenot
☠☠ backed out by 91beab01e785 ☠ ☠
authorJan-Ivar Bruaroey <jib@mozilla.com>
Fri, 17 Jun 2016 15:20:10 -0400
changeset 330315 85832c026282b6047faa6d36330aba7ba3d74115
parent 330314 97cfe28779d9f2b78e3bfe9708c5ffeb6af609a5
child 330316 abf811706099ed9338a22d296323bcece643bb68
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs1213517
milestone50.0a1
Bug 1213517 - Normalize even more of the constraints code. r=padenot MozReview-Commit-ID: 1XjdHXKYOmP
dom/media/MediaManager.cpp
dom/media/MediaManager.h
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/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
@@ -781,31 +781,30 @@ MediaDevice::FitnessDistance(nsString aN
     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
 MediaDevice::GetBestFitnessDistance(
-    const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets)
+    const nsTArray<const NormalizedConstraintSet*>& aConstraintSets)
 {
   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) {
+      if (constraint->mMediaSource.mIdeal.find(mediaSource) ==
+          constraint->mMediaSource.mIdeal.end()) {
         return UINT32_MAX;
       }
     }
   }
   // Forward request to underlying object to interrogate per-mode capabilities.
   // Pass in device's origin-specific id for deviceId constraint comparison.
   nsString id;
   GetId(id);
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -65,17 +65,17 @@ class MediaDevice : public nsIMediaDevic
 public:
   typedef MediaEngineSource Source;
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIMEDIADEVICE
 
   void SetId(const nsAString& aID);
   virtual uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets);
+      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets);
   virtual Source* GetSource() = 0;
   nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
                     const MediaEnginePrefs &aPrefs,
                     const nsACString& aOrigin,
                     const char** aOutBadConstraint);
   nsresult Restart(const dom::MediaTrackConstraints &aConstraints,
                    const MediaEnginePrefs &aPrefs,
                    const char** aOutBadConstraint);
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -189,17 +189,17 @@ public:
   virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
                             const MediaEnginePrefs &aPrefs,
                             const nsString& aDeviceId,
                             const nsACString& aOrigin,
                             BaseAllocationHandle** aOutHandle,
                             const char** aOutBadConstraint) = 0;
 
   virtual uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
       const nsString& aDeviceId) = 0;
 
 protected:
   // Only class' own members can be initialized in constructor initializer list.
   explicit MediaEngineSource(MediaEngineState aState)
     : mState(aState)
 #ifdef DEBUG
     , mOwningThread(PR_GetCurrentThread())
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
@@ -98,34 +98,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 NormalizedConstraintSet*>& 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) {
-    NormalizedConstraintSet ns(*cs, !first);
+  for (const NormalizedConstraintSet* ns : 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, ns, aDeviceId);
+      uint32_t distance = GetFitnessDistance(cap, *ns, aDeviceId);
       if (distance == UINT32_MAX) {
         candidateSet.RemoveElementAt(i);
       } else {
         ++i;
         if (first) {
           candidate.mDistance = distance;
         }
       }
@@ -240,16 +239,21 @@ MediaEngineCameraVideoSource::ChooseCapa
     LogCapability("Capability", cap, candidate.mDistance);
     if (candidate.mDistance == UINT32_MAX) {
       candidateSet.RemoveElementAt(i);
     } else {
       ++i;
     }
   }
 
+  if (!candidateSet.Length()) {
+    LOG(("failed to find capability match from %d choices",num));
+    return false;
+  }
+
   // Filter further with all advanced constraints (that don't overconstrain).
 
   for (const auto &cs : aConstraints.mAdvanced) {
     CapabilitySet rejects;
     for (size_t i = 0; i < candidateSet.Length();) {
       auto& candidate = candidateSet[i];
       webrtc::CaptureCapability cap;
       GetCapability(candidate.mIndex, cap);
@@ -259,20 +263,18 @@ MediaEngineCameraVideoSource::ChooseCapa
       } else {
         ++i;
       }
     }
     if (!candidateSet.Length()) {
       candidateSet.AppendElements(Move(rejects));
     }
   }
-  if (!candidateSet.Length()) {
-    LOG(("failed to find capability match from %d choices",num));
-    return false;
-  }
+  MOZ_ASSERT(candidateSet.Length(),
+             "advanced constraints filtering step can't reduce candidates to zero");
 
   // Remaining algorithm is up to the UA.
 
   TrimLessFitCandidates(candidateSet);
 
   // Any remaining multiples all have the same distance. A common case of this
   // occurs when no ideal is specified. Lean toward defaults.
   uint32_t sameDistance = candidateSet[0].mDistance;
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.h
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.h
@@ -44,17 +44,17 @@ public:
   }
 
   nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
       const nsString& aDeviceId) override;
 
   void Shutdown() override {};
 
 protected:
   struct CapabilityCandidate {
     explicit CapabilityCandidate(uint8_t index, uint32_t distance = 0)
     : mIndex(index), mDistance(distance) {}
--- a/dom/media/webrtc/MediaEngineDefault.cpp
+++ b/dom/media/webrtc/MediaEngineDefault.cpp
@@ -67,23 +67,23 @@ void
 MediaEngineDefaultVideoSource::GetUUID(nsACString& aUUID)
 {
   aUUID.AssignLiteral("1041FCBD-3F12-4F7B-9E9B-1EC556DD5676");
   return;
 }
 
 uint32_t
 MediaEngineDefaultVideoSource::GetBestFitnessDistance(
-    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+    const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
     const nsString& aDeviceId)
 {
   uint32_t distance = 0;
 #ifdef MOZ_WEBRTC
-  for (const dom::MediaTrackConstraintSet* cs : aConstraintSets) {
-    distance = GetMinimumFitnessDistance(*cs, false, aDeviceId);
+  for (const auto* cs : aConstraintSets) {
+    distance = GetMinimumFitnessDistance(*cs, aDeviceId);
     break; // distance is read from first entry only
   }
 #endif
   return distance;
 }
 
 nsresult
 MediaEngineDefaultVideoSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
@@ -395,23 +395,23 @@ void
 MediaEngineDefaultAudioSource::GetUUID(nsACString& aUUID)
 {
   aUUID.AssignLiteral("B7CBD7C1-53EF-42F9-8353-73F61C70C092");
   return;
 }
 
 uint32_t
 MediaEngineDefaultAudioSource::GetBestFitnessDistance(
-    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+    const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
     const nsString& aDeviceId)
 {
   uint32_t distance = 0;
 #ifdef MOZ_WEBRTC
-  for (const dom::MediaTrackConstraintSet* cs : aConstraintSets) {
-    distance = GetMinimumFitnessDistance(*cs, false, aDeviceId);
+  for (const auto* cs : aConstraintSets) {
+    distance = GetMinimumFitnessDistance(*cs, aDeviceId);
     break; // distance is read from first entry only
   }
 #endif
   return distance;
 }
 
 nsresult
 MediaEngineDefaultAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
--- a/dom/media/webrtc/MediaEngineDefault.h
+++ b/dom/media/webrtc/MediaEngineDefault.h
@@ -60,17 +60,17 @@ public:
                    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,
+      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
       const nsString& aDeviceId) override;
 
   bool IsFake() override {
     return true;
   }
 
   dom::MediaSourceEnum GetMediaSource() const override {
     return dom::MediaSourceEnum::Camera;
@@ -169,17 +169,17 @@ public:
   }
 
   nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
       const nsString& aDeviceId) override;
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSITIMERCALLBACK
 
 protected:
   ~MediaEngineDefaultAudioSource();
 
--- a/dom/media/webrtc/MediaEngineTabVideoSource.h
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.h
@@ -38,17 +38,17 @@ class MediaEngineTabVideoSource : public
                      const mozilla::MediaEnginePrefs& aPrefs,
                      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 nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
       const nsString& aDeviceId) override
     {
       return 0;
     }
 
     nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override
     {
       return NS_ERROR_NOT_IMPLEMENTED;
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -130,17 +130,17 @@ public:
   {
     return false;
   }
   nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
   uint32_t GetBestFitnessDistance(
-    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+    const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
     const nsString& aDeviceId) override;
 
 protected:
   virtual ~MediaEngineWebRTCAudioCaptureSource() { Shutdown(); }
   nsCString mUUID;
 };
 
 // Small subset of VoEHardware
@@ -497,17 +497,17 @@ public:
   }
 
   nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
       const nsString& aDeviceId) override;
 
   // VoEMediaProcess.
   void Process(int channel, webrtc::ProcessingTypes type,
                int16_t audio10ms[], int length,
                int samplingFreq, bool isStereo) override;
 
   void Shutdown() override;
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -201,23 +201,23 @@ MediaEngineWebRTCMicrophoneSource::GetUU
 // 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 MediaEngineWebRTCMicrophoneSource::GetBestFitnessDistance(
-    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+    const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
     const nsString& aDeviceId)
 {
   uint32_t distance = 0;
 
-  for (const MediaTrackConstraintSet* cs : aConstraintSets) {
-    distance = GetMinimumFitnessDistance(*cs, false, aDeviceId);
+  for (const auto* cs : aConstraintSets) {
+    distance = GetMinimumFitnessDistance(*cs, aDeviceId);
     break; // distance is read from first entry only
   }
   return distance;
 }
 
 nsresult
 MediaEngineWebRTCMicrophoneSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
                                             const MediaEnginePrefs &aPrefs,
@@ -854,16 +854,16 @@ MediaEngineWebRTCAudioCaptureSource::Res
     const char** aOutBadConstraint)
 {
   MOZ_ASSERT(!aHandle);
   return NS_OK;
 }
 
 uint32_t
 MediaEngineWebRTCAudioCaptureSource::GetBestFitnessDistance(
-    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+    const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
     const nsString& aDeviceId)
 {
   // There is only one way of capturing audio for now, and it's always adequate.
   return 0;
 }
 
 }
--- a/dom/media/webrtc/MediaTrackConstraints.cpp
+++ b/dom/media/webrtc/MediaTrackConstraints.cpp
@@ -350,23 +350,20 @@ FlattenedConstraints::FlattenedConstrain
 //
 // 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 NormalizedConstraintSet &aConstraints,
     const nsString& aDeviceId)
 {
-  NormalizedConstraintSet ns(aConstraints, aAdvanced);
-
-  return FitnessDistance(aDeviceId, ns.mDeviceId);
+  return FitnessDistance(aDeviceId, aConstraints.mDeviceId);
 }
 
 template<class ValueType, class NormalizedRange>
 /* static */ uint32_t
 MediaConstraintsHelper::FitnessDistance(ValueType aN,
                                         const NormalizedRange& aRange)
 {
   if (aRange.mMin > aN || aRange.mMax < aN) {
--- a/dom/media/webrtc/MediaTrackConstraints.h
+++ b/dom/media/webrtc/MediaTrackConstraints.h
@@ -246,18 +246,18 @@ public:
 };
 
 template<> bool NormalizedConstraintSet::Range<bool>::Merge(const Range& aOther);
 template<> void NormalizedConstraintSet::Range<bool>::FinalizeMerge();
 
 // Used instead of MediaTrackConstraints in lower-level code.
 struct NormalizedConstraints : public NormalizedConstraintSet
 {
-  explicit NormalizedConstraints(const dom::MediaTrackConstraints& aOther,
-                                 nsTArray<MemberPtrType>* aList = nullptr);
+  NormalizedConstraints(const dom::MediaTrackConstraints& aOther,
+                        nsTArray<MemberPtrType>* aList = nullptr);
 
   // Merge constructor
   explicit NormalizedConstraints(
       const nsTArray<const NormalizedConstraints*>& aOthers);
 
   nsTArray<NormalizedConstraintSet> mAdvanced;
   const char* mBadConstraint;
 };
@@ -277,143 +277,148 @@ class MediaConstraintsHelper
 {
 protected:
   template<class ValueType, class NormalizedRange>
   static uint32_t FitnessDistance(ValueType aN, const NormalizedRange& aRange);
   static uint32_t FitnessDistance(nsString aN,
       const NormalizedConstraintSet::StringRange& aConstraint);
 
   static uint32_t
-  GetMinimumFitnessDistance(const dom::MediaTrackConstraintSet &aConstraints,
-                            bool aAdvanced,
+  GetMinimumFitnessDistance(const NormalizedConstraintSet &aConstraints,
                             const nsString& aDeviceId);
 
   template<class DeviceType>
   static bool
-  SomeSettingsFit(const dom::MediaTrackConstraints &aConstraints,
+  SomeSettingsFit(const NormalizedConstraints &aConstraints,
                   nsTArray<RefPtr<DeviceType>>& aSources)
   {
-    nsTArray<const dom::MediaTrackConstraintSet*> aggregateConstraints;
-    aggregateConstraints.AppendElement(&aConstraints);
+    nsTArray<const NormalizedConstraintSet*> sets;
+    sets.AppendElement(&aConstraints);
 
     MOZ_ASSERT(aSources.Length());
     for (auto& source : aSources) {
-      if (source->GetBestFitnessDistance(aggregateConstraints) != UINT32_MAX) {
+      if (source->GetBestFitnessDistance(sets) != UINT32_MAX) {
         return true;
       }
     }
     return false;
   }
 
 public:
   // Apply constrains to a supplied list of sources (removes items from the list)
 
   template<class DeviceType>
   static const char*
-  SelectSettings(const dom::MediaTrackConstraints &aConstraints,
+  SelectSettings(const NormalizedConstraints &aConstraints,
                  nsTArray<RefPtr<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<RefPtr<DeviceType>> unsatisfactory;
-    nsTArray<const dom::MediaTrackConstraintSet*> aggregateConstraints;
+    nsTArray<const NormalizedConstraintSet*> aggregateConstraints;
     aggregateConstraints.AppendElement(&c);
 
     std::multimap<uint32_t, RefPtr<DeviceType>> ordered;
 
     for (uint32_t i = 0; i < aSources.Length();) {
       uint32_t distance = aSources[i]->GetBestFitnessDistance(aggregateConstraints);
       if (distance == UINT32_MAX) {
         unsatisfactory.AppendElement(aSources[i]);
         aSources.RemoveElementAt(i);
       } else {
         ordered.insert(std::pair<uint32_t, RefPtr<DeviceType>>(distance,
                                                                  aSources[i]));
         ++i;
       }
     }
     if (!aSources.Length()) {
-      // None selected. The spec says to report a constraint that satisfies NONE
-      // of the sources. Unfortunately, this is a bit laborious to find out, and
-      // requires updating as new constraints are added!
-
-      if (!unsatisfactory.Length() ||
-          !SomeSettingsFit(dom::MediaTrackConstraints(), unsatisfactory)) {
-        return "";
-      }
-      if (c.mDeviceId.IsConstrainDOMStringParameters()) {
-        dom::MediaTrackConstraints fresh;
-        fresh.mDeviceId = c.mDeviceId;
-        if (!SomeSettingsFit(fresh, unsatisfactory)) {
-          return "deviceId";
-        }
-      }
-      if (c.mWidth.IsConstrainLongRange()) {
-        dom::MediaTrackConstraints fresh;
-        fresh.mWidth = c.mWidth;
-        if (!SomeSettingsFit(fresh, unsatisfactory)) {
-          return "width";
-        }
-      }
-      if (c.mHeight.IsConstrainLongRange()) {
-        dom::MediaTrackConstraints fresh;
-        fresh.mHeight = c.mHeight;
-        if (!SomeSettingsFit(fresh, unsatisfactory)) {
-          return "height";
-        }
-      }
-      if (c.mFrameRate.IsConstrainDoubleRange()) {
-        dom::MediaTrackConstraints fresh;
-        fresh.mFrameRate = c.mFrameRate;
-        if (!SomeSettingsFit(fresh, unsatisfactory)) {
-          return "frameRate";
-        }
-      }
-      if (c.mFacingMode.IsConstrainDOMStringParameters()) {
-        dom::MediaTrackConstraints fresh;
-        fresh.mFacingMode = c.mFacingMode;
-        if (!SomeSettingsFit(fresh, unsatisfactory)) {
-          return "facingMode";
-        }
-      }
-      return "";
+      return FindBadConstraint(c, unsatisfactory);
     }
 
     // Order devices by shortest distance
     for (auto& ordinal : ordered) {
       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]);
-        nsTArray<RefPtr<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;
-          }
+    for (int i = 0; i < int(c.mAdvanced.Length()); i++) {
+      aggregateConstraints.AppendElement(&c.mAdvanced[i]);
+      nsTArray<RefPtr<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 (!aSources.Length()) {
-          aSources.AppendElements(Move(rejects));
-          aggregateConstraints.RemoveElementAt(aggregateConstraints.Length() - 1);
-        }
+      }
+      if (!aSources.Length()) {
+        aSources.AppendElements(Move(rejects));
+        aggregateConstraints.RemoveElementAt(aggregateConstraints.Length() - 1);
       }
     }
     return nullptr;
   }
+
+  template<class DeviceType>
+  static const char*
+  FindBadConstraint(const NormalizedConstraints& aConstraints,
+                    nsTArray<RefPtr<DeviceType>>& aSources)
+  {
+    // The spec says to report a constraint that satisfies NONE
+    // of the sources. Unfortunately, this is a bit laborious to find out, and
+    // requires updating as new constraints are added!
+    auto& c = aConstraints;
+    dom::MediaTrackConstraints empty;
+
+    if (!aSources.Length() ||
+        !SomeSettingsFit(NormalizedConstraints(empty), aSources)) {
+      return "";
+    }
+    {
+      NormalizedConstraints fresh(empty);
+      fresh.mDeviceId = c.mDeviceId;
+      if (!SomeSettingsFit(fresh, aSources)) {
+        return "deviceId";
+      }
+    }
+    {
+      NormalizedConstraints fresh(empty);
+      fresh.mWidth = c.mWidth;
+      if (!SomeSettingsFit(fresh, aSources)) {
+        return "width";
+      }
+    }
+    {
+      NormalizedConstraints fresh(empty);
+      fresh.mHeight = c.mHeight;
+      if (!SomeSettingsFit(fresh, aSources)) {
+        return "height";
+      }
+    }
+    {
+      NormalizedConstraints fresh(empty);
+      fresh.mFrameRate = c.mFrameRate;
+      if (!SomeSettingsFit(fresh, aSources)) {
+        return "frameRate";
+      }
+    }
+    {
+      NormalizedConstraints fresh(empty);
+      fresh.mFacingMode = c.mFacingMode;
+      if (!SomeSettingsFit(fresh, aSources)) {
+        return "facingMode";
+      }
+    }
+    return "";
+  }
 };
 
 } // namespace mozilla
 
 #endif /* MEDIATRACKCONSTRAINTS_H_ */