Backed out changesets d8d01c95be03 and 5fa0c8f5ccb2 (bug 907352) for Linux64 mochitest-3 crashes.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 18 Apr 2014 11:22:21 -0400
changeset 198863 6632c970ef7c683008a2098d1b94f940358d4422
parent 198862 d69cced6e24223c83aabd152b3f2208556da88dd
child 198864 167291c2fd24fbcc6aff72fffcfb1c87f3cf7f2e
push id486
push userasasaki@mozilla.com
push dateMon, 14 Jul 2014 18:39:42 +0000
treeherdermozilla-release@d33428174ff1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs907352
milestone31.0a1
backs outd8d01c95be038fb3989cc88e8e9bf371c4545f04
5fa0c8f5ccb2bd124a49f058ebdad002eff9695e
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
Backed out changesets d8d01c95be03 and 5fa0c8f5ccb2 (bug 907352) for Linux64 mochitest-3 crashes.
content/media/webspeech/recognition/SpeechRecognition.cpp
dom/base/Navigator.cpp
dom/base/Navigator.h
dom/media/GetUserMediaRequest.cpp
dom/media/GetUserMediaRequest.h
dom/media/MediaManager.cpp
dom/media/MediaManager.h
dom/media/MediaPermissionGonk.cpp
dom/media/tests/mochitest/constraints.js
dom/media/tests/mochitest/mochitest.ini
dom/media/tests/mochitest/test_getUserMedia_constraints.html
dom/media/tests/mochitest/test_getUserMedia_constraints_mobile.html
dom/media/tests/mochitest/test_getUserMedia_exceptions.html
dom/webidl/GetUserMediaRequest.webidl
dom/webidl/MediaStream.webidl
dom/webidl/MediaStreamTrack.webidl
dom/webidl/Navigator.webidl
--- a/content/media/webspeech/recognition/SpeechRecognition.cpp
+++ b/content/media/webspeech/recognition/SpeechRecognition.cpp
@@ -706,22 +706,24 @@ SpeechRecognition::Start(ErrorResult& aR
 
   nsresult rv;
   mRecognitionService = do_GetService(speechRecognitionServiceCID.get(), &rv);
   NS_ENSURE_SUCCESS_VOID(rv);
 
   rv = mRecognitionService->Initialize(this->asWeakPtr());
   NS_ENSURE_SUCCESS_VOID(rv);
 
+  AutoSafeJSContext cx;
   MediaStreamConstraints constraints;
   constraints.mAudio.SetAsBoolean() = true;
 
   if (!mTestConfig.mFakeFSMEvents) {
     MediaManager* manager = MediaManager::Get();
-    manager->GetUserMedia(false,
+    manager->GetUserMedia(cx,
+                          false,
                           GetOwner(),
                           constraints,
                           new GetUserMediaSuccessCallback(this),
                           new GetUserMediaErrorCallback(this));
   }
 
   nsRefPtr<SpeechEvent> event = new SpeechEvent(this, EVENT_START);
   NS_DispatchToMainThread(event);
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1289,17 +1289,18 @@ Navigator::SendBeacon(const nsAString& a
     aRv.Throw(rv);
     return false;
   }
   return true;
 }
 
 #ifdef MOZ_MEDIA_NAVIGATOR
 void
-Navigator::MozGetUserMedia(const MediaStreamConstraints& aConstraints,
+Navigator::MozGetUserMedia(JSContext* aCx,
+                           const MediaStreamConstraints& aConstraints,
                            NavigatorUserMediaSuccessCallback& aOnSuccess,
                            NavigatorUserMediaErrorCallback& aOnError,
                            ErrorResult& aRv)
 {
   CallbackObjectHolder<NavigatorUserMediaSuccessCallback,
                        nsIDOMGetUserMediaSuccessCallback> holder1(&aOnSuccess);
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onsuccess =
     holder1.ToXPCOMCallback();
@@ -1312,22 +1313,22 @@ Navigator::MozGetUserMedia(const MediaSt
       mWindow->GetOuterWindow()->GetCurrentInnerWindow() != mWindow) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return;
   }
 
   bool privileged = nsContentUtils::IsChromeDoc(mWindow->GetExtantDoc());
 
   MediaManager* manager = MediaManager::Get();
-  aRv = manager->GetUserMedia(privileged, mWindow, aConstraints,
+  aRv = manager->GetUserMedia(aCx, privileged, mWindow, aConstraints,
                               onsuccess, onerror);
 }
 
 void
-Navigator::MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
+Navigator::MozGetUserMediaDevices(const MediaStreamConstraintsInternal& aConstraints,
                                   MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
                                   NavigatorUserMediaErrorCallback& aOnError,
                                   uint64_t aInnerWindowID,
                                   ErrorResult& aRv)
 {
   CallbackObjectHolder<MozGetUserMediaDevicesSuccessCallback,
                        nsIGetUserMediaDevicesSuccessCallback> holder1(&aOnSuccess);
   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onsuccess =
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -28,16 +28,17 @@ class nsDOMCameraManager;
 class nsDOMDeviceStorage;
 class nsIDOMBlob;
 
 namespace mozilla {
 namespace dom {
 class Geolocation;
 class systemMessageCallback;
 class MediaStreamConstraints;
+class MediaStreamConstraintsInternal;
 class WakeLock;
 class ArrayBufferViewOrBlobOrStringOrFormData;
 }
 }
 
 #ifdef MOZ_B2G_RIL
 class nsIDOMMozIccManager;
 #endif // MOZ_B2G_RIL
@@ -225,21 +226,22 @@ public:
   system::AudioChannelManager* GetMozAudioChannelManager(ErrorResult& aRv);
 #endif // MOZ_AUDIO_CHANNEL_MANAGER
 
   bool SendBeacon(const nsAString& aUrl,
                   const Nullable<ArrayBufferViewOrBlobOrStringOrFormData>& aData,
                   ErrorResult& aRv);
 
 #ifdef MOZ_MEDIA_NAVIGATOR
-  void MozGetUserMedia(const MediaStreamConstraints& aConstraints,
+  void MozGetUserMedia(JSContext* aCx,
+                       const MediaStreamConstraints& aConstraints,
                        NavigatorUserMediaSuccessCallback& aOnSuccess,
                        NavigatorUserMediaErrorCallback& aOnError,
                        ErrorResult& aRv);
-  void MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
+  void MozGetUserMediaDevices(const MediaStreamConstraintsInternal& aConstraints,
                               MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
                               NavigatorUserMediaErrorCallback& aOnError,
                               uint64_t aInnerWindowID,
                               ErrorResult& aRv);
 #endif // MOZ_MEDIA_NAVIGATOR
   bool DoNewResolve(JSContext* aCx, JS::Handle<JSObject*> aObject,
                     JS::Handle<jsid> aId,
                     JS::MutableHandle<JSPropertyDescriptor> aDesc);
--- a/dom/media/GetUserMediaRequest.cpp
+++ b/dom/media/GetUserMediaRequest.cpp
@@ -11,22 +11,22 @@
 #include "nsCxPusher.h"
 
 namespace mozilla {
 namespace dom {
 
 GetUserMediaRequest::GetUserMediaRequest(
     nsPIDOMWindow* aInnerWindow,
     const nsAString& aCallID,
-    const MediaStreamConstraints& aConstraints,
+    const MediaStreamConstraintsInternal& aConstraints,
     bool aIsSecure)
   : mInnerWindowID(aInnerWindow->WindowID())
   , mOuterWindowID(aInnerWindow->GetOuterWindow()->WindowID())
   , mCallID(aCallID)
-  , mConstraints(new MediaStreamConstraints(aConstraints))
+  , mConstraints(new MediaStreamConstraintsInternal(aConstraints))
   , mIsSecure(aIsSecure)
 {
   SetIsDOMBinding();
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(GetUserMediaRequest)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(GetUserMediaRequest)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(GetUserMediaRequest)
@@ -62,15 +62,15 @@ uint64_t GetUserMediaRequest::InnerWindo
 }
 
 bool GetUserMediaRequest::IsSecure()
 {
   return mIsSecure;
 }
 
 void
-GetUserMediaRequest::GetConstraints(MediaStreamConstraints &result)
+GetUserMediaRequest::GetConstraints(MediaStreamConstraintsInternal &result)
 {
   result = *mConstraints;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/GetUserMediaRequest.h
+++ b/dom/media/GetUserMediaRequest.h
@@ -9,43 +9,43 @@
 #include "nsISupportsImpl.h"
 #include "nsAutoPtr.h"
 #include "nsWrapperCache.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "nsPIDOMWindow.h"
 
 namespace mozilla {
 namespace dom {
-class MediaStreamConstraints;
+class MediaStreamConstraintsInternal;
 
 class GetUserMediaRequest : public nsISupports, public nsWrapperCache
 {
 public:
   GetUserMediaRequest(nsPIDOMWindow* aInnerWindow,
                       const nsAString& aCallID,
-                      const MediaStreamConstraints& aConstraints,
+                      const MediaStreamConstraintsInternal& aConstraints,
                       bool aIsSecure);
   virtual ~GetUserMediaRequest() {};
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(GetUserMediaRequest)
 
   virtual JSObject* WrapObject(JSContext* cx)
     MOZ_OVERRIDE;
   nsISupports* GetParentObject();
 
   uint64_t WindowID();
   uint64_t InnerWindowID();
   bool IsSecure();
   void GetCallID(nsString& retval);
-  void GetConstraints(MediaStreamConstraints &result);
+  void GetConstraints(MediaStreamConstraintsInternal &result);
 
 private:
   uint64_t mInnerWindowID, mOuterWindowID;
   const nsString mCallID;
-  nsAutoPtr<MediaStreamConstraints> mConstraints;
+  nsAutoPtr<MediaStreamConstraintsInternal> mConstraints;
   bool mIsSecure;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // GetUserMediaRequest_h__
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -18,17 +18,16 @@
 #include "nsIScriptGlobalObject.h"
 #include "nsIPermissionManager.h"
 #include "nsIPopupWindowManager.h"
 #include "nsISupportsArray.h"
 #include "nsIDocShell.h"
 #include "nsIDocument.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIInterfaceRequestorUtils.h"
-#include "mozilla/Types.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/MediaStreamTrackBinding.h"
 #include "mozilla/dom/GetUserMediaRequestBinding.h"
 
 #include "Latency.h"
 
 // For PR_snprintf
@@ -70,21 +69,63 @@ GetMediaManagerLog()
   return sLog;
 }
 #define LOG(msg) PR_LOG(GetMediaManagerLog(), PR_LOG_DEBUG, msg)
 #else
 #define LOG(msg)
 #endif
 
 using dom::MediaStreamConstraints;         // Outside API (contains JSObject)
+using dom::MediaStreamConstraintsInternal; // Storable supported constraints
+using dom::MediaTrackConstraintsInternal;  // Video or audio constraints
 using dom::MediaTrackConstraintSet;        // Mandatory or optional constraints
 using dom::MediaTrackConstraints;          // Raw mMandatory (as JSObject)
 using dom::GetUserMediaRequest;
 using dom::Sequence;
-using dom::OwningBooleanOrMediaTrackConstraints;
+using dom::OwningBooleanOrMediaTrackConstraintsInternal;
+
+// Used to compare raw MediaTrackConstraintSet against normalized dictionary
+// version to detect member differences, e.g. unsupported constraints.
+
+static nsresult CompareDictionaries(JSContext* aCx, JSObject *aA,
+                                    const MediaTrackConstraintSet &aB,
+                                    nsString *aDifference)
+{
+  JS::Rooted<JSObject*> a(aCx, aA);
+  JSAutoCompartment ac(aCx, aA);
+  JS::Rooted<JS::Value> bval(aCx);
+  aB.ToObject(aCx, &bval);
+  JS::Rooted<JSObject*> b(aCx, &bval.toObject());
+
+  // Iterate over each property in A, and check if it is in B
+
+  JS::AutoIdArray props(aCx, JS_Enumerate(aCx, a));
+
+  for (size_t i = 0; i < props.length(); i++) {
+    JS::Rooted<JS::Value> bprop(aCx);
+    JS::Rooted<jsid> id(aCx, props[i]);
+    if (!JS_GetPropertyById(aCx, b, id, &bprop)) {
+      LOG(("Error parsing dictionary!\n"));
+      return NS_ERROR_UNEXPECTED;
+    }
+    if (bprop.isUndefined()) {
+      // Unknown property found in A. Bail with name
+      JS::Rooted<JS::Value> nameval(aCx);
+      bool success = JS_IdToValue(aCx, props[i], &nameval);
+      NS_ENSURE_TRUE(success, NS_ERROR_UNEXPECTED);
+
+      JS::Rooted<JSString*> namestr(aCx, JS::ToString(aCx, nameval));
+      NS_ENSURE_TRUE(namestr, NS_ERROR_UNEXPECTED);
+      aDifference->Assign(JS_GetStringCharsZ(aCx, namestr));
+      return NS_OK;
+    }
+  }
+  aDifference->Truncate();
+  return NS_OK;
+}
 
 ErrorCallbackRunnable::ErrorCallbackRunnable(
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
   const nsAString& aErrorMsg, uint64_t aWindowID)
   : mErrorMsg(aErrorMsg)
   , mWindowID(aWindowID)
   , mManager(MediaManager::GetInstance())
@@ -626,101 +667,64 @@ private:
   nsRefPtr<MediaEngineSource> mAudioSource;
   nsRefPtr<MediaEngineSource> mVideoSource;
   uint64_t mWindowID;
   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
 };
 
 static bool
-IsOn(const dom::OwningBooleanOrMediaTrackConstraints &aUnion) {
+IsOn(const dom::OwningBooleanOrMediaTrackConstraintsInternal &aUnion) {
   return !aUnion.IsBoolean() || aUnion.GetAsBoolean();
 }
 
+static JSObject *
+GetMandatoryJSObj(const dom::OwningBooleanOrMediaTrackConstraints &aUnion) {
+  return (aUnion.IsMediaTrackConstraints() &&
+          aUnion.GetAsMediaTrackConstraints().mMandatory.WasPassed()) ?
+          aUnion.GetAsMediaTrackConstraints().mMandatory.Value() : nullptr;
+}
+
 /**
  * Helper functions that implement the constraints algorithm from
  * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
  */
 
-#define lengthof(a) (sizeof(a) / sizeof(*a))
-
-static auto
-GetSupportedConstraintNames(const MediaEngineVideoSource *) ->
-    decltype((dom::SupportedVideoConstraintsValues::strings)) {
-  return dom::SupportedVideoConstraintsValues::strings;
-}
-
-static auto
-GetSupportedConstraintNames(const MediaEngineAudioSource *) ->
-    decltype((dom::SupportedAudioConstraintsValues::strings)) {
-  return dom::SupportedAudioConstraintsValues::strings;
-}
-
-// Reminder: add handling for new constraints both here and in GetSources below!
-
-static bool SatisfyConstraintSet(const MediaEngineVideoSource *,
-                                 const MediaTrackConstraintSet &aConstraints,
-                                 nsIMediaDevice &aCandidate)
+static bool SatisfyConstraint(const MediaEngineVideoSource *,
+                              const MediaTrackConstraintSet &aConstraints,
+                              nsIMediaDevice &aCandidate)
 {
   if (aConstraints.mFacingMode.WasPassed()) {
     nsString s;
     aCandidate.GetFacingMode(s);
     if (!s.EqualsASCII(dom::VideoFacingModeEnumValues::strings[
         uint32_t(aConstraints.mFacingMode.Value())].value)) {
       return false;
     }
   }
   // TODO: Add more video-specific constraints
   return true;
 }
 
-static bool SatisfyConstraintSet(const MediaEngineAudioSource *,
-                                 const MediaTrackConstraintSet &aConstraints,
-                                 nsIMediaDevice &aCandidate)
+static bool SatisfyConstraint(const MediaEngineAudioSource *,
+                              const MediaTrackConstraintSet &aConstraints,
+                              nsIMediaDevice &aCandidate)
 {
   // TODO: Add audio-specific constraints
   return true;
 }
 
-// Triage constraints into required and nonrequired + detect missing requireds
-
-class TriageHelper
-{
-public:
-  TriageHelper(const nsTArray<nsString>& aRequire)
-  : mRequire(aRequire)
-  , mNumRequirementsMet(0) {}
-
-  MediaTrackConstraintSet& Triage(const nsAString &name) {
-    if (mRequire.IndexOf(name) != mRequire.NoIndex) {
-      mNumRequirementsMet++;
-      return mRequired;
-    } else {
-      return mNonrequired;
-    }
-  }
-  bool RequirementsAreMet() {
-    MOZ_ASSERT(mNumRequirementsMet <= mRequire.Length());
-    return mNumRequirementsMet == mRequire.Length();
-  }
-  MediaTrackConstraintSet mRequired;
-  MediaTrackConstraintSet mNonrequired;
-private:
-  const nsTArray<nsString> mRequire;
-  uint32_t mNumRequirementsMet;
-};
-
 typedef nsTArray<nsCOMPtr<nsIMediaDevice> > SourceSet;
 
 // Source getter that constrains list returned
 
 template<class SourceType>
 static SourceSet *
   GetSources(MediaEngine *engine,
-             const OwningBooleanOrMediaTrackConstraints &aConstraints,
+             const OwningBooleanOrMediaTrackConstraintsInternal &aConstraints,
              void (MediaEngine::* aEnumerate)(nsTArray<nsRefPtr<SourceType> >*),
              char* media_device_name = nullptr)
 {
   ScopedDeletePtr<SourceSet> result(new SourceSet);
 
   if (!IsOn(aConstraints)) {
     return result.forget();
   }
@@ -750,114 +754,64 @@ static SourceSet *
 #endif
         candidateSet.AppendElement(new MediaDevice(sources[i]));
 #ifdef DEBUG
       }
 #endif
     }
   }
 
-  // If unconstrained then return the full list.
-
   if (aConstraints.IsBoolean()) {
     MOZ_ASSERT(aConstraints.GetAsBoolean());
     result->MoveElementsFrom(candidateSet);
     return result.forget();
   }
+  auto& constraints = aConstraints.GetAsMediaTrackConstraintsInternal();
 
-  // Otherwise apply constraints to the list of sources.
+  // Then apply mandatory constraints
 
-  auto& constraints = aConstraints.GetAsMediaTrackConstraints();
-  const nsTArray<nsString> empty;
-  const auto &require = constraints.mRequire.WasPassed()?
-      constraints.mRequire.Value() : empty;
-  {
-    // Check upfront the names of required constraints that are unsupported for
-    // this media-type. The spec requires these to fail, so getting them out of
-    // the way early provides a necessary invariant for the remaining algorithm
-    // which maximizes code-reuse by ignoring constraints of the other type
-    // (specifically, SatisfyConstraintSet is reused for the advanced algorithm
-    // where the spec requires it to ignore constraints of the other type)
-
-    const auto& supported = GetSupportedConstraintNames(type);
-    for (uint32_t i = 0; i < require.Length(); i++) {
-      bool found = false;
-      // EnumType arrays have a zero-terminator entry at the end. Skip.
-      for (size_t j = 0; j < sizeof(supported)/sizeof(*supported) - 1; j++) {
-        if (require[i].EqualsASCII(supported[j].value)) {
-          found = true;
-          break;
-        }
-      }
-      if (!found) {
-        return result.forget();
-      }
+  // Note: Iterator must be signed as it can dip below zero
+  for (int i = 0; i < int(candidateSet.Length()); i++) {
+    // Overloading instead of template specialization keeps things local
+    if (!SatisfyConstraint(type, constraints.mMandatory, *candidateSet[i])) {
+      candidateSet.RemoveElementAt(i--);
     }
   }
 
-  // Before we start, triage constraints into required and nonrequired.
-  // Reminder: add handling for new constraints both here & SatisfyConstraintSet
-
-  TriageHelper helper(require);
-
-  if (constraints.mFacingMode.WasPassed()) {
-    helper.Triage(NS_LITERAL_STRING("facingMode")).mFacingMode.Construct(
-        constraints.mFacingMode.Value());
-  }
-  if (!helper.RequirementsAreMet()) {
-    return result.forget();
-  }
-
-  // Now on to the actual algorithm: First apply required constraints.
-
-  for (uint32_t i = 0; i < candidateSet.Length();) {
-    // Overloading instead of template specialization keeps things local
-    if (!SatisfyConstraintSet(type, helper.mRequired, *candidateSet[i])) {
-      candidateSet.RemoveElementAt(i);
-    } else {
-      ++i;
-    }
-  }
-
-  // Then apply advanced (formerly known as optional) constraints.
+  // Then apply optional constraints.
   //
   // These are only effective when there are multiple sources to pick from.
   // Spec as-of-this-writing says to run algorithm on "all possible tracks
   // of media type T that the browser COULD RETURN" (emphasis added).
   //
   // We think users ultimately control which devices we could return, so after
   // determining the webpage's preferred list, we add the remaining choices
   // to the tail, reasoning that they would all have passed individually,
   // i.e. if the user had any one of them as their sole device (enabled).
   //
   // This avoids users having to unplug/disable devices should a webpage pick
   // the wrong one (UX-fail). Webpage-preferred devices will be listed first.
 
   SourceSet tailSet;
 
-  if (constraints.mAdvanced.WasPassed()) {
-    const auto &array = constraints.mAdvanced.Value();
+  if (constraints.mOptional.WasPassed()) {
+    const auto &array = constraints.mOptional.Value();
     for (int i = 0; i < int(array.Length()); i++) {
       SourceSet rejects;
-      for (uint32_t j = 0; j < candidateSet.Length();) {
-        if (!SatisfyConstraintSet(type, array[i], *candidateSet[j])) {
+      // Note: Iterator must be signed as it can dip below zero
+      for (int j = 0; j < int(candidateSet.Length()); j++) {
+        if (!SatisfyConstraint(type, array[i], *candidateSet[j])) {
           rejects.AppendElement(candidateSet[j]);
-          candidateSet.RemoveElementAt(j);
-        } else {
-          ++j;
+          candidateSet.RemoveElementAt(j--);
         }
       }
       (candidateSet.Length()? tailSet : candidateSet).MoveElementsFrom(rejects);
     }
   }
 
-  // Finally, order any remaining sources by how many nonrequired constraints
-  // they satisfy. TODO(jib): TBD once we implement >1 constraint (Bug 907352)
-
-
   result->MoveElementsFrom(candidateSet);
   result->MoveElementsFrom(tailSet);
   return result.forget();
 }
 
 /**
  * Runs on a seperate thread and is responsible for enumerating devices.
  * Depending on whether a picture or stream was asked for, either
@@ -866,17 +820,17 @@ static SourceSet *
  *
  * Do not run this on the main thread. The success and error callbacks *MUST*
  * be dispatched on the main thread!
  */
 class GetUserMediaRunnable : public nsRunnable
 {
 public:
   GetUserMediaRunnable(
-    const MediaStreamConstraints& aConstraints,
+    const MediaStreamConstraintsInternal& aConstraints,
     already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
     uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
     MediaEnginePrefs &aPrefs)
     : mConstraints(aConstraints)
     , mSuccess(aSuccess)
     , mError(aError)
     , mWindowID(aWindowID)
@@ -887,17 +841,17 @@ public:
     , mManager(MediaManager::GetInstance())
   {}
 
   /**
    * The caller can also choose to provide their own backend instead of
    * using the one provided by MediaManager::GetBackend.
    */
   GetUserMediaRunnable(
-    const MediaStreamConstraints& aConstraints,
+    const MediaStreamConstraintsInternal& aConstraints,
     already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
     uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
     MediaEnginePrefs &aPrefs,
     MediaEngine* aBackend)
     : mConstraints(aConstraints)
     , mSuccess(aSuccess)
     , mError(aError)
@@ -993,17 +947,17 @@ public:
 
     MOZ_ASSERT(!mSuccess);
     MOZ_ASSERT(!mError);
 
     return NS_OK;
   }
 
   nsresult
-  SetContraints(const MediaStreamConstraints& aConstraints)
+  SetContraints(const MediaStreamConstraintsInternal& aConstraints)
   {
     mConstraints = aConstraints;
     return NS_OK;
   }
 
   nsresult
   SetAudioDevice(MediaDevice* aAudioDevice)
   {
@@ -1122,17 +1076,17 @@ public:
 
     MOZ_ASSERT(!mSuccess);
     MOZ_ASSERT(!mError);
 
     return;
   }
 
 private:
-  MediaStreamConstraints mConstraints;
+  MediaStreamConstraintsInternal mConstraints;
 
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
   uint64_t mWindowID;
   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   nsRefPtr<MediaDevice> mAudioDevice;
   nsRefPtr<MediaDevice> mVideoDevice;
   MediaEnginePrefs mPrefs;
@@ -1148,17 +1102,17 @@ private:
  * GetUserMediaDevices function. Enumerates a list of audio & video devices,
  * wraps them up in nsIMediaDevice objects and returns it to the success
  * callback.
  */
 class GetUserMediaDevicesRunnable : public nsRunnable
 {
 public:
   GetUserMediaDevicesRunnable(
-    const MediaStreamConstraints& aConstraints,
+    const MediaStreamConstraintsInternal& aConstraints,
     already_AddRefed<nsIGetUserMediaDevicesSuccessCallback> aSuccess,
     already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
     uint64_t aWindowId, char* aAudioLoopbackDev, char* aVideoLoopbackDev)
     : mConstraints(aConstraints)
     , mSuccess(aSuccess)
     , mError(aError)
     , mManager(MediaManager::GetInstance())
     , mWindowId(aWindowId)
@@ -1189,17 +1143,17 @@ public:
                                                               mSuccess, mError,
                                                               final.forget()));
     // DeviceSuccessCallbackRunnable should have taken these.
     MOZ_ASSERT(!mSuccess && !mError);
     return NS_OK;
   }
 
 private:
-  MediaStreamConstraints mConstraints;
+  MediaStreamConstraintsInternal mConstraints;
   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
   nsRefPtr<MediaManager> mManager;
   uint64_t mWindowId;
   const nsString mCallId;
   // Audio & Video loopback devices to be used based on
   // the preference settings. This is currently used for
   // automated media tests only.
@@ -1335,30 +1289,66 @@ MediaManager::NotifyRecordingStatusChang
 }
 
 /**
  * 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(bool aPrivileged,
-  nsPIDOMWindow* aWindow, const MediaStreamConstraints& aConstraints,
+MediaManager::GetUserMedia(JSContext* aCx, bool aPrivileged,
+  nsPIDOMWindow* aWindow, const MediaStreamConstraints& aRawConstraints,
   nsIDOMGetUserMediaSuccessCallback* aOnSuccess,
   nsIDOMGetUserMediaErrorCallback* aOnError)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER);
   NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER);
   NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER);
 
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess(aOnSuccess);
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError);
 
+  Maybe<JSAutoCompartment> ac;
+  JS::Rooted<JSObject*> audioObj (aCx, GetMandatoryJSObj(aRawConstraints.mAudio));
+  JS::Rooted<JSObject*> videoObj (aCx, GetMandatoryJSObj(aRawConstraints.mVideo));
+  if (audioObj || videoObj) {
+    ac.construct(aCx, audioObj? audioObj : videoObj);
+  }
+
+  // aRawConstraints may have JSObject in mMandatory, so copy everything into
+  // MediaStreamConstraintsInternal which does not.
+
+  dom::RootedDictionary<MediaStreamConstraintsInternal> c(aCx);
+  JS::Rooted<JS::Value> temp(aCx);
+  // This isn't the fastest way to copy a MediaStreamConstraints into a
+  // MediaStreamConstraintsInternal, but requires less code maintenance than an
+  // explicit member-by-member copy, and should be safe given the circumstances.
+  aRawConstraints.ToObject(aCx, &temp);
+  bool success = c.Init(aCx, temp);
+  NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+
+  // Validate mandatory constraints by detecting any unknown constraints.
+  // Done by comparing the raw MediaTrackConstraints against the normalized copy
+
+  nsString unknownConstraintFound;
+  if (audioObj) {
+    nsresult rv = CompareDictionaries(aCx, audioObj,
+        c.mAudio.GetAsMediaTrackConstraintsInternal().mMandatory,
+        &unknownConstraintFound);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  if (videoObj) {
+    nsresult rv = CompareDictionaries(aCx, videoObj,
+        c.mVideo.GetAsMediaTrackConstraintsInternal().mMandatory,
+        &unknownConstraintFound);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   /**
    * If we were asked to get a picture, before getting a snapshot, we check if
    * the calling page is allowed to open a popup. We do this because
    * {picture:true} will open a new "window" to let the user preview or select
    * an image, on Android. The desktop UI for {picture:true} is TBD, at which
    * may point we can decide whether to extend this test there as well.
    */
 #if !defined(MOZ_WEBRTC)
@@ -1399,60 +1389,51 @@ MediaManager::GetUserMedia(bool aPrivile
   // This is safe since we're on main-thread, and the windowlist can only
   // be invalidated from the main-thread (see OnNavigation)
   StreamListeners* listeners = GetActiveWindows()->Get(windowID);
   if (!listeners) {
     listeners = new StreamListeners;
     GetActiveWindows()->Put(windowID, listeners);
   }
 
+  if (!unknownConstraintFound.IsEmpty()) {
+    // An unsupported mandatory constraint was found.
+    //
+    // We continue to ignore these for now, because we implement just
+    // facingMode, which means all existing uses of mandatory width/height would
+    // fail on Firefox only otherwise, which is undesirable.
+    //
+    // There's also basis for always ignoring them in a new proposal.
+    // TODO(jib): This is a super-low-risk fix for backport. Clean up later.
+
+    LOG(("Unsupported mandatory constraint: %s\n",
+          NS_ConvertUTF16toUTF8(unknownConstraintFound).get()));
+
+    // unknown constraints existed in aRawConstraints only, which is unused
+    // from here, so continuing here effectively ignores them, as is desired.
+  }
+
   // Ensure there's a thread for gum to proxy to off main thread
   nsIThread *mediaThread = MediaManager::GetThread();
 
   // Create a disabled listener to act as a placeholder
   GetUserMediaCallbackMediaStreamListener* listener =
     new GetUserMediaCallbackMediaStreamListener(mediaThread, windowID);
 
   // No need for locking because we always do this in the main thread.
   listeners->AppendElement(listener);
 
-  MediaStreamConstraints c(aConstraints); // copy
-
   // Developer preference for turning off permission check.
   if (Preferences::GetBool("media.navigator.permission.disabled", false)) {
     aPrivileged = true;
   }
   if (!Preferences::GetBool("media.navigator.video.enabled", true)) {
     c.mVideo.SetAsBoolean() = false;
   }
 
-#if defined(ANDROID) || defined(MOZ_WIDGET_GONK)
-  // Be backwards compatible only on mobile and only for facingMode.
-  if (c.mVideo.IsMediaTrackConstraints()) {
-    auto& tc = c.mVideo.GetAsMediaTrackConstraints();
-
-    if (!tc.mRequire.WasPassed()) {
-      if (tc.mMandatory.mFacingMode.WasPassed() && !tc.mFacingMode.WasPassed()) {
-        tc.mFacingMode.Construct(tc.mMandatory.mFacingMode.Value());
-        tc.mRequire.Construct().AppendElement(NS_LITERAL_STRING("facingMode"));
-      }
-    }
-    if (tc.mOptional.WasPassed() && !tc.mAdvanced.WasPassed()) {
-      tc.mAdvanced.Construct();
-      for (uint32_t i = 0; i < tc.mOptional.Value().Length(); i++) {
-        if (tc.mOptional.Value()[i].mFacingMode.WasPassed()) {
-          MediaTrackConstraintSet n;
-          n.mFacingMode.Construct(tc.mOptional.Value()[i].mFacingMode.Value());
-          tc.mAdvanced.Value().AppendElement(n);
-        }
-      }
-    }
-  }
-#endif
-
   // Pass callbacks and MediaStreamListener along to GetUserMediaRunnable.
   nsRefPtr<GetUserMediaRunnable> runnable;
   if (c.mFake) {
     // Fake stream from default backend.
     runnable = new GetUserMediaRunnable(c, onSuccess.forget(),
       onError.forget(), windowID, listener, mPrefs, new MediaEngineDefault());
   } else {
     // Stream from default device from WebRTC backend.
@@ -1573,17 +1554,17 @@ MediaManager::GetUserMedia(bool aPrivile
     obs->NotifyObservers(req, "getUserMedia:request", nullptr);
   }
 
   return NS_OK;
 }
 
 nsresult
 MediaManager::GetUserMediaDevices(nsPIDOMWindow* aWindow,
-  const MediaStreamConstraints& aConstraints,
+  const MediaStreamConstraintsInternal& aConstraints,
   nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
   nsIDOMGetUserMediaErrorCallback* aOnError,
   uint64_t aInnerWindowID)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER);
   NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER);
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -490,24 +490,24 @@ public:
   }
   bool IsWindowStillActive(uint64_t aWindowId) {
     return !!GetWindowListeners(aWindowId);
   }
   // Note: also calls aListener->Remove(), even if inactive
   void RemoveFromWindowList(uint64_t aWindowID,
     GetUserMediaCallbackMediaStreamListener *aListener);
 
-  nsresult GetUserMedia(bool aPrivileged,
+  nsresult GetUserMedia(JSContext* aCx, bool aPrivileged,
     nsPIDOMWindow* aWindow,
     const dom::MediaStreamConstraints& aRawConstraints,
     nsIDOMGetUserMediaSuccessCallback* onSuccess,
     nsIDOMGetUserMediaErrorCallback* onError);
 
   nsresult GetUserMediaDevices(nsPIDOMWindow* aWindow,
-    const dom::MediaStreamConstraints& aConstraints,
+    const dom::MediaStreamConstraintsInternal& aConstraints,
     nsIGetUserMediaDevicesSuccessCallback* onSuccess,
     nsIDOMGetUserMediaErrorCallback* onError,
     uint64_t aInnerWindowID = 0);
   void OnNavigation(uint64_t aWindowID);
 
   MediaEnginePrefs mPrefs;
 
 private:
--- a/dom/media/MediaPermissionGonk.cpp
+++ b/dom/media/MediaPermissionGonk.cpp
@@ -171,17 +171,17 @@ private:
 
 // MediaPermissionRequest
 NS_IMPL_ISUPPORTS1(MediaPermissionRequest, nsIContentPermissionRequest)
 
 MediaPermissionRequest::MediaPermissionRequest(nsRefPtr<dom::GetUserMediaRequest> &aRequest,
                                                nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices)
   : mRequest(aRequest)
 {
-  dom::MediaStreamConstraints constraints;
+  dom::MediaStreamConstraintsInternal constraints;
   mRequest->GetConstraints(constraints);
 
   mAudio = !constraints.mAudio.IsBoolean() || constraints.mAudio.GetAsBoolean();
   mVideo = !constraints.mVideo.IsBoolean() || constraints.mVideo.GetAsBoolean();
 
   for (uint32_t i = 0; i < aDevices.Length(); ++i) {
     nsCOMPtr<nsIMediaDevice> device(aDevices[i]);
     nsAutoString deviceType;
@@ -573,17 +573,17 @@ MediaPermissionManager::HandleRequest(ns
     return NS_ERROR_FAILURE;
   }
 
   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess =
       new MediaDeviceSuccessCallback(req);
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError =
       new MediaDeviceErrorCallback(callID);
 
-  dom::MediaStreamConstraints constraints;
+  dom::MediaStreamConstraintsInternal constraints;
   req->GetConstraints(constraints);
 
   nsRefPtr<MediaManager> MediaMgr = MediaManager::GetInstance();
   nsresult rv = MediaMgr->GetUserMediaDevices(innerWindow, constraints, onSuccess, onError);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
deleted file mode 100644
--- a/dom/media/tests/mochitest/constraints.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/* 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/. */
-
-/**
-  Tests covering gUM constraints API for audio, video and fake video. Exercise
-  successful parsing code and ensure that unknown required constraints and
-  overconstraining cases produce appropriate errors.
-
-  TODO(jib): Merge desktop and mobile version of these tests again.
-*/
-var common_tests = [
-  // Each test here tests a different constraint or codepath.
-  { message: "unknown required constraint on video fails",
-    constraints: { video: { somethingUnknown:0, require:["somethingUnknown"] } },
-    error: "NO_DEVICES_FOUND" },
-  { message: "unknown required constraint on audio fails",
-    constraints: { audio: { somethingUnknown:0, require:["somethingUnknown"] } },
-    error: "NO_DEVICES_FOUND" },
-  { message: "missing required constraint on video fails",
-    constraints: { video: { require:["facingMode"] } },
-    error: "NO_DEVICES_FOUND" },
-  { message: "missing required constraint on audio fails",
-    constraints: { audio: { require:["facingMode"] } },
-    error: "NO_DEVICES_FOUND" },
-  { message: "video overconstrained by facingMode fails",
-    constraints: { video: { facingMode:'left', require:["facingMode"] } },
-    error: "NO_DEVICES_FOUND" },
-  { message: "audio overconstrained by facingMode fails",
-    constraints: { audio: { facingMode:'left', require:["facingMode"] } },
-    error: "NO_DEVICES_FOUND" },
-  { message: "Success-path: optional video facingMode + audio ignoring facingMode",
-    constraints: { fake: true,
-                   audio: { facingMode:'left',
-                            foo:0,
-                            advanced: [{ facingMode:'environment' },
-                                       { facingMode:'user' },
-                                       { bar:0 }] },
-                   video: { // TODO: Bug 767924 sequences in unions
-                            //facingMode:['left', 'right', 'user', 'environment'],
-                            //require:["facingMode"],
-                            facingMode:'left',
-                            foo:0,
-                            advanced: [{ facingMode:'environment' },
-                                       { facingMode:'user' },
-                                       { bar:0 }] } },
-    error: null }
-];
-
-
-/**
- * Starts the test run by running through each constraint
- * test by verifying that the right callback and error message is fired.
- */
-
-function testConstraints(tests) {
-  var i = 0;
-  next();
-
-  function Success() {
-    ok(!tests[i].error, tests[i].message);
-    i++;
-    next();
-  }
-  function Failure(err) {
-    ok(tests[i].error? (err === tests[i].error) : false,
-       tests[i].message + " (err=" + err + ")");
-    i++;
-    next();
-  }
-  function next() {
-    if (i < tests.length) {
-      navigator.mozGetUserMedia(tests[i].constraints, Success, Failure);
-    } else {
-      SimpleTest.finish();
-    }
-  }
-};
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -1,12 +1,11 @@
 [DEFAULT]
 support-files =
   head.js
-  constraints.js
   mediaStreamPlayback.js
   pc.js
   templates.js
   NetworkPreparationChromeScript.js
 
 [test_dataChannel_basicAudio.html]
 skip-if = (toolkit == 'gonk' && debug) #Bug 962984, test fail on b2g debug build
 [test_dataChannel_basicAudioVideo.html]
@@ -21,19 +20,16 @@ skip-if = toolkit=='gonk' # b2g(Bug 9604
 [test_dataChannel_noOffer.html]
 [test_getUserMedia_basicAudio.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_getUserMedia_basicVideo.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_getUserMedia_basicVideoAudio.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure, turned an intermittent (bug 962579) into a permanant orange
 [test_getUserMedia_constraints.html]
-skip-if = (toolkit=='gonk' || toolkit=='android') # Bug 907352, backwards-compatible behavior on mobile only
-[test_getUserMedia_constraints_mobile.html]
-skip-if = (toolkit!='gonk' && toolkit!='android') # Bug 907352, backwards-compatible behavior on mobile only
 [test_getUserMedia_exceptions.html]
 [test_getUserMedia_gumWithinGum.html]
 [test_getUserMedia_playAudioTwice.html]
 [test_getUserMedia_playVideoAudioTwice.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure; bug 926558
 [test_getUserMedia_playVideoTwice.html]
 [test_getUserMedia_stopAudioStream.html]
 [test_getUserMedia_stopAudioStreamWithFollowupAudio.html]
--- a/dom/media/tests/mochitest/test_getUserMedia_constraints.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_constraints.html
@@ -4,37 +4,86 @@
 https://bugzilla.mozilla.org/show_bug.cgi?id=882145
 -->
 <head>
   <meta charset="utf-8">
   <title>Test mozGetUserMedia Constraints</title>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
-  <script type="application/javascript" src="constraints.js"></script>
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=882145">Test mozGetUserMedia Constraints (desktop)</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=882145">Test mozGetUserMedia Constraints</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script type="application/javascript">
 /**
-  See constraints.js for testConstraints() and common_tests.
-  TODO(jib): Merge desktop and mobile version of these tests again (Bug 997365)
+  Tests covering gUM constraints API for audio, video and fake video. Exercise
+  successful parsing code and ensure that unknown mandatory constraints and
+  overconstraining cases produce appropriate errors.
 */
-var desktop_tests = [
-  { message: "legacy facingMode ignored (desktop)",
-    constraints: { fake: true, video: { mandatory: { facingMode:'left' } } },
-    error: null },
+var tests = [
+  // Each test here tests a different constraint or codepath.
+  { message: "video overconstrained by facingMode fails",
+    constraints: { video: { mandatory: { facingMode:'left' } } },
+    error: "NO_DEVICES_FOUND",
+    pass: false },
+  { message: "Success-path: optional video facingMode + audio ignoring facingMode",
+    constraints: { fake: true,
+                   audio: { mandatory: { facingMode:'left' } },
+                   video: { mandatory: { somethingUnknown:0 },
+                            optional: [{ facingMode:'left' },
+                                       { facingMode:'right' },
+                                       { facingMode:'environment' },
+                                       { facingMode:'user' },
+                                       { foo:0 }] } },
+    error: null,
+    pass: false },
+  { message: null },
 ];
 
+/**
+ * Starts the test run by running through each constraint
+ * test by verifying that the right callback and error message is fired.
+ */
+
 runTest(function () {
-  testConstraints(common_tests.concat(desktop_tests));
+  var i = 0;
+  next();
+
+  function Success() {
+    info("successcallback");
+    tests[i].pass = !tests[i].error;
+    i++;
+    next();
+  }
+  function Failure(err) {
+    info("errcallback: " + err);
+    tests[i].pass = tests[i].error? (err === tests[i].error) : false;
+    i++;
+    next();
+  }
+  function next() {
+    if (tests[i].message) {
+      navigator.mozGetUserMedia(tests[i].constraints, Success, Failure);
+    } else {
+      finish();
+    }
+  }
+  function finish() {
+    tests.forEach(function (test) {
+      if (test.message) {
+        ok(test.pass, test.message);
+      } else {
+        SimpleTest.finish();
+      }
+    });
+  }
 });
 
 
 </script>
 </pre>
 </body>
 </html>
deleted file mode 100644
--- a/dom/media/tests/mochitest/test_getUserMedia_constraints_mobile.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=882145
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test mozGetUserMedia Constraints</title>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript" src="head.js"></script>
-  <script type="application/javascript" src="constraints.js"></script>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=882145">Test mozGetUserMedia Constraints (mobile)</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-<script type="application/javascript">
-/**
-  See constraints.js for testConstraints() and common_tests.
-  TODO(jib): Merge desktop and mobile version of these tests again (Bug 997365)
-*/
-var mobile_tests = [
-  { message: "legacy facingMode overconstrains video (mobile)",
-    constraints: { video: { mandatory: { facingMode:'left' } } },
-    error: "NO_DEVICES_FOUND" },
-];
-
-runTest(function () {
-  testConstraints(common_tests.concat(mobile_tests));
-});
-
-
-</script>
-</pre>
-</body>
-</html>
--- a/dom/media/tests/mochitest/test_getUserMedia_exceptions.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_exceptions.html
@@ -44,17 +44,17 @@ var exceptionTests = [
   { params: [{video: true, fake: true}, 1, unexpectedCall],
     error: "Argument 2 of Navigator.mozGetUserMedia is not an object.",
     message: "wrong object type as second parameter" },
   { params: [{video: true, fake: true}, unexpectedCall, 1],
     error: "Argument 3 of Navigator.mozGetUserMedia is not an object.",
     message: "wrong object type as third parameter" },
 
   // Each test here verifies constraint syntax as defined in webidl
-  { params: [{ fake: true, video: { advanced: [{ facingMode:'foo' }] } },
+  { params: [{ fake: true, video: { optional: [{ facingMode:'foo' }] } },
              unexpectedCall, unexpectedCall],
     error: "'facingMode' member of MediaTrackConstraintSet 'foo' is not a valid value for enumeration VideoFacingModeEnum.",
     message: "invalid facingMode enum value" }
 ];
 
 /**
  * A callback function that is only called if a particular
  * exception was not thrown, resulting in the test failing.
--- a/dom/webidl/GetUserMediaRequest.webidl
+++ b/dom/webidl/GetUserMediaRequest.webidl
@@ -6,11 +6,11 @@
  * This is an internal IDL file
  */
 
 [NoInterfaceObject]
 interface GetUserMediaRequest {
   readonly attribute unsigned long long windowID;
   readonly attribute unsigned long long innerWindowID;
   readonly attribute DOMString callID;
-  MediaStreamConstraints getConstraints();
+  MediaStreamConstraintsInternal getConstraints();
   readonly attribute boolean isSecure;
 };
--- a/dom/webidl/MediaStream.webidl
+++ b/dom/webidl/MediaStream.webidl
@@ -11,18 +11,25 @@
  */
 
 // 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
+    boolean picture = false;
+    boolean fake = false;
+};
+
+dictionary MediaStreamConstraintsInternal {
+    (boolean or MediaTrackConstraintsInternal) audio;
+    (boolean or MediaTrackConstraintsInternal) video;
+    boolean picture = false;
+    boolean fake = false;
 };
 
 interface MediaStream {
     // readonly attribute DOMString    id;
     sequence<AudioStreamTrack> getAudioTracks ();
     sequence<VideoStreamTrack> getVideoTracks ();
     // MediaStreamTrack           getTrackById (DOMString trackId);
     // void                       addTrack (MediaStreamTrack track);
--- a/dom/webidl/MediaStreamTrack.webidl
+++ b/dom/webidl/MediaStreamTrack.webidl
@@ -6,53 +6,45 @@
  * The origin of this IDL file is
  * http://dev.w3.org/2011/webrtc/editor/getusermedia.html
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 // Important! Do not ever add members that might need tracing (e.g. object)
-// to MediaTrackConstraintSet
+// to MediaTrackConstraintSet or any dictionary marked XxxInternal here
 
 enum VideoFacingModeEnum {
     "user",
     "environment",
     "left",
     "right"
 };
 
-enum SupportedVideoConstraints {
-    "facingMode"
-};
-
-enum SupportedAudioConstraints {
-    "dummy"
-};
-
 dictionary MediaTrackConstraintSet {
-    ConstrainVideoFacingMode facingMode;
-};
-
-dictionary MobileLegacyMediaTrackConstraintSet {
     VideoFacingModeEnum facingMode;
 };
 
-dictionary MediaTrackConstraints : MediaTrackConstraintSet {
-    sequence<DOMString> require;
-    sequence<MediaTrackConstraintSet> advanced;
+// MediaTrackConstraint = single-property-subset of MediaTrackConstraintSet
+// Implemented as full set. Test Object.keys(pair).length == 1
 
-    // mobile-only backwards-compatibility for facingMode
-    MobileLegacyMediaTrackConstraintSet mandatory;
-    sequence<MobileLegacyMediaTrackConstraintSet> _optional;
+// typedef MediaTrackConstraintSet MediaTrackConstraint; // TODO: Bug 913053
+
+dictionary MediaTrackConstraints {
+    object mandatory; // so we can see unknown + unsupported constraints
+    sequence<MediaTrackConstraintSet> _optional; // a.k.a. MediaTrackConstraint
 };
 
-typedef VideoFacingModeEnum ConstrainVideoFacingMode;
-// TODO: Bug 767924 sequences in unions
-//typedef (VideoFacingModeEnum or sequence<VideoFacingModeEnum>) ConstrainVideoFacingMode;
+// Internal dictionary holds result of processing raw MediaTrackConstraints above
+
+dictionary MediaTrackConstraintsInternal {
+    MediaTrackConstraintSet mandatory; // holds only supported constraints
+    sequence<MediaTrackConstraintSet> _optional; // a.k.a. MediaTrackConstraint
+};
 
 interface MediaStreamTrack {
     readonly    attribute DOMString             kind;
     readonly    attribute DOMString             id;
     readonly    attribute DOMString             label;
                 attribute boolean               enabled;
 //    readonly    attribute MediaStreamTrackState readyState;
 //    readonly    attribute SourceTypeEnum        sourceType;
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -332,17 +332,17 @@ partial interface Navigator {
                        NavigatorUserMediaSuccessCallback successCallback,
                        NavigatorUserMediaErrorCallback errorCallback);
 };
 
 // nsINavigatorUserMedia
 callback MozGetUserMediaDevicesSuccessCallback = void (nsIVariant? devices);
 partial interface Navigator {
   [Throws, ChromeOnly]
-  void mozGetUserMediaDevices(MediaStreamConstraints constraints,
+  void mozGetUserMediaDevices(MediaStreamConstraintsInternal constraints,
                               MozGetUserMediaDevicesSuccessCallback onsuccess,
                               NavigatorUserMediaErrorCallback onerror,
                               // The originating innerWindowID is needed to
                               // avoid calling the callbacks if the window has
                               // navigated away. It is optional only as legacy.
                               optional unsigned long long innerWindowID = 0);
 };
 #endif // MOZ_MEDIA_NAVIGATOR