Merge m-c to inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 31 Oct 2014 19:13:48 -0400
changeset 213464 a20982886f52fa60f3c879b87d92a5ed9c848448
parent 213461 89282ba7eec983259684c65eebf3a9d4b7d2f525 (current diff)
parent 213463 b695d957565426e638bcbdcb0452eb6339daaa9c (diff)
child 213465 dc0e165853187916e1f1e7c5cf16c9f0191186b2
push id51235
push userryanvm@gmail.com
push dateFri, 31 Oct 2014 23:13:52 +0000
treeherdermozilla-inbound@a20982886f52 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound. a=merge CLOSED TREE
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -203,32 +203,16 @@ DOMInterfaces = {
     'headerFile': 'DOMCameraDetectedFace.h'
 },
 
 'CameraManager': {
     'nativeType': 'nsDOMCameraManager',
     'headerFile': 'DOMCameraManager.h'
 },
 
-'CameraRecorderAudioProfile': {
-    'headerFile': 'DOMCameraCapabilities.h'
-},
-
-'CameraRecorderProfile': {
-    'headerFile': 'DOMCameraCapabilities.h'
-},
-
-'CameraRecorderProfiles': {
-    'headerFile': 'DOMCameraCapabilities.h'
-},
-
-'CameraRecorderVideoProfile': {
-    'headerFile': 'DOMCameraCapabilities.h'
-},
-
 'CanvasRenderingContext2D': {
     'implicitJSContext': [
         'createImageData', 'getImageData'
     ],
     'binaryNames': {
         'mozImageSmoothingEnabled': 'imageSmoothingEnabled',
         'mozFillRule': 'fillRule'
     }
--- a/dom/camera/CameraControlImpl.cpp
+++ b/dom/camera/CameraControlImpl.cpp
@@ -3,16 +3,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CameraControlImpl.h"
 #include "base/basictypes.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/unused.h"
 #include "nsPrintfCString.h"
 #include "nsIWeakReferenceUtils.h"
+#include "CameraRecorderProfiles.h"
 #include "CameraCommon.h"
 #include "nsGlobalWindow.h"
 #include "DeviceStorageFileDescriptor.h"
 #include "CameraControlListener.h"
 
 using namespace mozilla;
 
 nsWeakPtr CameraControlImpl::sCameraThread;
@@ -59,16 +60,22 @@ CameraControlImpl::~CameraControlImpl()
 {
   MOZ_ASSERT(mListenerLock, "mListenerLock missing in ~CameraControlImpl()");
   if (mListenerLock) {
     PR_DestroyRWLock(mListenerLock);
     mListenerLock = nullptr;
   }
 }
 
+already_AddRefed<RecorderProfileManager>
+CameraControlImpl::GetRecorderProfileManager()
+{
+  return GetRecorderProfileManagerImpl();
+}
+
 void
 CameraControlImpl::Shutdown()
 {
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 }
 
 void
 CameraControlImpl::OnHardwareStateChange(CameraControlListener::HardwareState aNewState)
@@ -102,17 +109,17 @@ CameraControlImpl::OnHardwareStateChange
 }
 
 void
 CameraControlImpl::OnConfigurationChange()
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
   RwLockAutoEnterRead lock(mListenerLock);
 
-  DOM_CAMERA_LOGI("OnConfigurationChange : %zu listeners\n", mListeners.Length());
+  DOM_CAMERA_LOGI("OnConfigurationChange : %d listeners\n", mListeners.Length());
 
   for (uint32_t i = 0; i < mListeners.Length(); ++i) {
     CameraControlListener* l = mListeners[i];
     l->OnConfigurationChange(mCurrentConfiguration);
   }
 }
 
 void
@@ -257,17 +264,17 @@ CameraControlImpl::OnRateLimitPreview(bo
 
 bool
 CameraControlImpl::OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight)
 {
   // This function runs on neither the Main Thread nor the Camera Thread.
   //  On Gonk, it is called from the camera driver's preview thread.
   RwLockAutoEnterRead lock(mListenerLock);
 
-  DOM_CAMERA_LOGI("OnNewPreviewFrame: we have %zu preview frame listener(s)\n",
+  DOM_CAMERA_LOGI("OnNewPreviewFrame: we have %d preview frame listener(s)\n",
     mListeners.Length());
 
   bool consumed = false;
 
   for (uint32_t i = 0; i < mListeners.Length(); ++i) {
     CameraControlListener* l = mListeners[i];
     consumed = l->OnNewPreviewFrame(aImage, aWidth, aHeight) || consumed;
   }
--- a/dom/camera/CameraControlImpl.h
+++ b/dom/camera/CameraControlImpl.h
@@ -20,16 +20,18 @@
 #include "CameraControlListener.h"
 
 namespace mozilla {
 
 namespace layers {
   class Image;
 }
 
+class RecorderProfileManager;
+
 class CameraControlImpl : public ICameraControl
 {
 public:
   explicit CameraControlImpl(uint32_t aCameraId);
   virtual void AddListener(CameraControlListener* aListener) MOZ_OVERRIDE;
   virtual void RemoveListener(CameraControlListener* aListener) MOZ_OVERRIDE;
 
   // See ICameraControl.h for these methods' return values.
@@ -42,16 +44,17 @@ public:
   virtual nsresult StartFaceDetection() MOZ_OVERRIDE;
   virtual nsresult StopFaceDetection() MOZ_OVERRIDE;
   virtual nsresult TakePicture() MOZ_OVERRIDE;
   virtual nsresult StartRecording(DeviceStorageFileDescriptor* aFileDescriptor,
                                   const StartRecordingOptions* aOptions) MOZ_OVERRIDE;
   virtual nsresult StopRecording() MOZ_OVERRIDE;
   virtual nsresult ResumeContinuousFocus() MOZ_OVERRIDE;
 
+  already_AddRefed<RecorderProfileManager> GetRecorderProfileManager();
   uint32_t GetCameraId() { return mCameraId; }
 
   virtual void Shutdown() MOZ_OVERRIDE;
 
   // Event handlers called directly from outside this class.
   void OnShutter();
   void OnClosed();
   void OnUserError(CameraControlListener::UserContext aContext, nsresult aError);
@@ -120,16 +123,18 @@ protected:
   virtual nsresult TakePictureImpl() = 0;
   virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
                                       const StartRecordingOptions* aOptions) = 0;
   virtual nsresult StopRecordingImpl() = 0;
   virtual nsresult ResumeContinuousFocusImpl() = 0;
   virtual nsresult PushParametersImpl() = 0;
   virtual nsresult PullParametersImpl() = 0;
 
+  virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl() = 0;
+
   void OnShutterInternal();
   void OnClosedInternal();
 
   uint32_t mCameraId;
 
   CameraControlListener::CameraListenerConfiguration mCurrentConfiguration;
 
   CameraControlListener::PreviewState   mPreviewState;
--- a/dom/camera/CameraControlListener.h
+++ b/dom/camera/CameraControlListener.h
@@ -29,18 +29,18 @@ protected:
     MOZ_COUNT_DTOR(CameraControlListener);
   }
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CameraControlListener);
 
   enum HardwareState
   {
-    kHardwareClosed,
-    kHardwareOpen
+    kHardwareOpen,
+    kHardwareClosed
   };
   virtual void OnHardwareStateChange(HardwareState aState) { }
 
   enum PreviewState
   {
     kPreviewStopped,
     kPreviewPaused,
     kPreviewStarted
new file mode 100644
--- /dev/null
+++ b/dom/camera/CameraRecorderProfiles.cpp
@@ -0,0 +1,195 @@
+/* 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 "CameraRecorderProfiles.h"
+#include "jsapi.h"
+#include "CameraCommon.h"
+
+using namespace mozilla;
+
+/**
+ * Video profile implementation.
+ */
+RecorderVideoProfile::RecorderVideoProfile(uint32_t aCameraId, uint32_t aQualityIndex)
+  : mCameraId(aCameraId)
+  , mQualityIndex(aQualityIndex)
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
+
+RecorderVideoProfile::~RecorderVideoProfile()
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
+
+nsresult
+RecorderVideoProfile::GetJsObject(JSContext* aCx, JSObject** aObject)
+{
+  NS_ENSURE_TRUE(aObject, NS_ERROR_INVALID_ARG);
+
+  JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
+  NS_ENSURE_TRUE(o, NS_ERROR_OUT_OF_MEMORY);
+
+  const char* codec = GetCodecName();
+  NS_ENSURE_TRUE(codec, NS_ERROR_FAILURE);
+
+  JS::Rooted<JSString*> s(aCx, JS_NewStringCopyZ(aCx, codec));
+  JS::Rooted<JS::Value> v(aCx, STRING_TO_JSVAL(s));
+  if (!JS_SetProperty(aCx, o, "codec", v)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (mBitrate != -1) {
+    v = INT_TO_JSVAL(mBitrate);
+    if (!JS_SetProperty(aCx, o, "bitrate", v)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+  if (mFramerate != -1) {
+    v = INT_TO_JSVAL(mFramerate);
+    if (!JS_SetProperty(aCx, o, "framerate", v)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+  if (mWidth != -1) {
+    v = INT_TO_JSVAL(mWidth);
+    if (!JS_SetProperty(aCx, o, "width", v)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+  if (mHeight != -1) {
+    v = INT_TO_JSVAL(mHeight);
+    if (!JS_SetProperty(aCx, o, "height", v)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  *aObject = o;
+  return NS_OK;
+}
+
+/**
+ * Audio profile implementation.
+ */
+RecorderAudioProfile::RecorderAudioProfile(uint32_t aCameraId, uint32_t aQualityIndex)
+  : mCameraId(aCameraId)
+  , mQualityIndex(aQualityIndex)
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
+
+RecorderAudioProfile::~RecorderAudioProfile()
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
+
+nsresult
+RecorderAudioProfile::GetJsObject(JSContext* aCx, JSObject** aObject)
+{
+  NS_ENSURE_TRUE(aObject, NS_ERROR_INVALID_ARG);
+
+  JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
+  NS_ENSURE_TRUE(o, NS_ERROR_OUT_OF_MEMORY);
+
+  const char* codec = GetCodecName();
+  NS_ENSURE_TRUE(codec, NS_ERROR_FAILURE);
+
+  JS::Rooted<JSString*> s(aCx, JS_NewStringCopyZ(aCx, codec));
+  JS::Rooted<JS::Value> v(aCx, STRING_TO_JSVAL(s));
+  if (!JS_SetProperty(aCx, o, "codec", v)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (mBitrate != -1) {
+    v = INT_TO_JSVAL(mBitrate);
+    if (!JS_SetProperty(aCx, o, "bitrate", v)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+  if (mSamplerate != -1) {
+    v = INT_TO_JSVAL(mSamplerate);
+    if (!JS_SetProperty(aCx, o, "samplerate", v)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+  if (mChannels != -1) {
+    v = INT_TO_JSVAL(mChannels);
+    if (!JS_SetProperty(aCx, o, "channels", v)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  *aObject = o;
+  return NS_OK;
+}
+
+/**
+ * Recorder Profile
+ */
+RecorderProfile::RecorderProfile(uint32_t aCameraId, uint32_t aQualityIndex)
+  : mCameraId(aCameraId)
+  , mQualityIndex(aQualityIndex)
+  , mName(nullptr)
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
+
+RecorderProfile::~RecorderProfile()
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
+
+/**
+ * Recorder profile manager implementation.
+ */
+RecorderProfileManager::RecorderProfileManager(uint32_t aCameraId)
+  : mCameraId(aCameraId)
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
+
+RecorderProfileManager::~RecorderProfileManager()
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
+
+nsresult
+RecorderProfileManager::GetJsObject(JSContext* aCx, JSObject** aObject) const
+{
+  NS_ENSURE_TRUE(aObject, NS_ERROR_INVALID_ARG);
+
+  JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
+  if (!o) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  for (uint32_t q = 0; q < GetMaxQualityIndex(); ++q) {
+    if (!IsSupported(q)) {
+      continue;
+    }
+
+    nsRefPtr<RecorderProfile> profile = Get(q);
+    if (!profile) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    const char* profileName = profile->GetName();
+    if (!profileName) {
+      // don't allow anonymous recorder profiles
+      continue;
+    }
+
+    JS::Rooted<JSObject*> p(aCx);
+    nsresult rv = profile->GetJsObject(aCx, p.address());
+    NS_ENSURE_SUCCESS(rv, rv);
+    JS::Rooted<JS::Value> v(aCx, OBJECT_TO_JSVAL(p));
+
+    if (!JS_SetProperty(aCx, o, profileName, v)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  *aObject = o;
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/CameraRecorderProfiles.h
@@ -0,0 +1,274 @@
+/* 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/. */
+
+#ifndef DOM_CAMERA_CAMERA_RECORDER_PROFILES_H
+#define DOM_CAMERA_CAMERA_RECORDER_PROFILES_H
+
+#include "nsISupportsImpl.h"
+#include "nsMimeTypes.h"
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+#include "jsapi.h"
+#include "CameraCommon.h"
+
+namespace mozilla {
+
+class CameraControlImpl;
+
+class RecorderVideoProfile
+{
+public:
+  RecorderVideoProfile(uint32_t aCameraId, uint32_t aQualityIndex);
+  virtual ~RecorderVideoProfile();
+
+  int GetBitrate() const    { return mBitrate; }
+  int GetFramerate() const  { return mFramerate; }
+  int GetWidth() const      { return mWidth; }
+  int GetHeight() const     { return mHeight; }
+
+  enum Codec {
+    H263,
+    H264,
+    MPEG4SP,
+    UNKNOWN
+  };
+  Codec GetCodec() const    { return mCodec; }
+  const char* GetCodecName() const
+  {
+    switch (mCodec) {
+      case H263:    return "h263";
+      case H264:    return "h264";
+      case MPEG4SP: return "mpeg4sp";
+      default:      return nullptr;
+    }
+  }
+
+  // Get a representation of this video profile that can be returned
+  // to JS, possibly as a child member of another object.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aObject' is null;
+  //  - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
+  //  - NS_ERROR_FAILURE if construction of the JS object fails.
+  nsresult GetJsObject(JSContext* aCx, JSObject** aObject);
+
+protected:
+  uint32_t mCameraId;
+  uint32_t mQualityIndex;
+  Codec mCodec;
+  int mBitrate;
+  int mFramerate;
+  int mWidth;
+  int mHeight;
+};
+
+class RecorderAudioProfile
+{
+public:
+  RecorderAudioProfile(uint32_t aCameraId, uint32_t aQualityIndex);
+  virtual ~RecorderAudioProfile();
+
+  int GetBitrate() const    { return mBitrate; }
+  int GetSamplerate() const { return mSamplerate; }
+  int GetChannels() const   { return mChannels; }
+
+  enum Codec {
+    AMRNB,
+    AMRWB,
+    AAC,
+    UNKNOWN
+  };
+
+  Codec GetCodec() const    { return mCodec; }
+  const char* GetCodecName() const
+  {
+    switch (mCodec) {
+      case AMRNB: return "amrnb";
+      case AMRWB: return "amrwb";
+      case AAC:   return "aac";
+      default:    return nullptr;
+    }
+  }
+
+  // Get a representation of this audio profile that can be returned
+  // to JS, possibly as a child member of another object.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aObject' is null;
+  //  - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
+  //  - NS_ERROR_FAILURE if construction of the JS object fails.
+  nsresult GetJsObject(JSContext* aCx, JSObject** aObject);
+
+protected:
+  uint32_t mCameraId;
+  uint32_t mQualityIndex;
+  Codec mCodec;
+  int mBitrate;
+  int mSamplerate;
+  int mChannels;
+};
+
+class RecorderProfile
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RecorderProfile)
+
+  RecorderProfile(uint32_t aCameraId, uint32_t aQualityIndex);
+
+  virtual const RecorderVideoProfile* GetVideoProfile() const = 0;
+  virtual const RecorderAudioProfile* GetAudioProfile() const = 0;
+  const char* GetName() const { return mName; }
+
+  enum FileFormat {
+    THREE_GPP,
+    MPEG4,
+    UNKNOWN
+  };
+  FileFormat GetFileFormat() const { return mFileFormat; }
+  const char* GetFileFormatName() const
+  {
+    switch (mFileFormat) {
+      case THREE_GPP: return "3gp";
+      case MPEG4:     return "mp4";
+      default:        return nullptr;
+    }
+  }
+  const char* GetFileMimeType() const
+  {
+    switch (mFileFormat) {
+      case THREE_GPP: return VIDEO_3GPP;
+      case MPEG4:     return VIDEO_MP4;
+      default:        return nullptr;
+    }
+  }
+
+  // Get a representation of this recorder profile that can be returned
+  // to JS, possibly as a child member of another object.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aObject' is null;
+  //  - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
+  //  - NS_ERROR_FAILURE if construction of the JS object fails.
+  virtual nsresult GetJsObject(JSContext* aCx, JSObject** aObject) = 0;
+
+protected:
+  virtual ~RecorderProfile();
+
+  uint32_t mCameraId;
+  uint32_t mQualityIndex;
+  const char* mName;
+  FileFormat mFileFormat;
+};
+
+template <class Audio, class Video>
+class RecorderProfileBase : public RecorderProfile
+{
+public:
+  RecorderProfileBase(uint32_t aCameraId, uint32_t aQualityIndex)
+    : RecorderProfile(aCameraId, aQualityIndex)
+    , mVideo(aCameraId, aQualityIndex)
+    , mAudio(aCameraId, aQualityIndex)
+  {
+    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  }
+
+  virtual ~RecorderProfileBase()
+  {
+    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  }
+
+  const RecorderVideoProfile* GetVideoProfile() const { return &mVideo; }
+  const RecorderAudioProfile* GetAudioProfile() const { return &mAudio; }
+
+  // Get a representation of this recorder profile that can be returned
+  // to JS, possibly as a child member of another object.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aObject' is null;
+  //  - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
+  //  - NS_ERROR_NOT_AVAILABLE if the profile has no file format name;
+  //  - NS_ERROR_FAILURE if construction of the JS object fails.
+  nsresult
+  GetJsObject(JSContext* aCx, JSObject** aObject)
+  {
+    NS_ENSURE_TRUE(aObject, NS_ERROR_INVALID_ARG);
+
+    const char* format = GetFileFormatName();
+    if (!format) {
+      // the profile must have a file format
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+
+    JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
+    if (!o) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    JS::Rooted<JSString*> s(aCx, JS_NewStringCopyZ(aCx, format));
+    JS::Rooted<JS::Value> v(aCx, STRING_TO_JSVAL(s));
+    if (!JS_SetProperty(aCx, o, "format", v)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    JS::Rooted<JSObject*> video(aCx);
+    nsresult rv = mVideo.GetJsObject(aCx, video.address());
+    NS_ENSURE_SUCCESS(rv, rv);
+    v = OBJECT_TO_JSVAL(video);
+    if (!JS_SetProperty(aCx, o, "video", v)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    JS::Rooted<JSObject*> audio(aCx);
+    rv = mAudio.GetJsObject(aCx, audio.address());
+    NS_ENSURE_SUCCESS(rv, rv);
+    v = OBJECT_TO_JSVAL(audio);
+    if (!JS_SetProperty(aCx, o, "audio", v)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    *aObject = o;
+    return NS_OK;
+  }
+
+protected:
+  Video mVideo;
+  Audio mAudio;
+};
+
+class RecorderProfileManager
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RecorderProfileManager)
+
+  virtual bool IsSupported(uint32_t aQualityIndex) const { return true; }
+  virtual already_AddRefed<RecorderProfile> Get(uint32_t aQualityIndex) const = 0;
+
+  uint32_t GetMaxQualityIndex() const { return mMaxQualityIndex; }
+
+  // Get a representation of all supported recorder profiles that can be
+  // returned to JS.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aObject' is null;
+  //  - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
+  //  - NS_ERROR_NOT_AVAILABLE if the profile has no file format name;
+  //  - NS_ERROR_FAILURE if construction of the JS object fails.
+  nsresult GetJsObject(JSContext* aCx, JSObject** aObject) const;
+
+protected:
+  explicit RecorderProfileManager(uint32_t aCameraId);
+  virtual ~RecorderProfileManager();
+
+  uint32_t mCameraId;
+  uint32_t mMaxQualityIndex;
+};
+
+} // namespace mozilla
+
+#endif // DOM_CAMERA_CAMERA_RECORDER_PROFILES_H
--- a/dom/camera/DOMCameraCapabilities.cpp
+++ b/dom/camera/DOMCameraCapabilities.cpp
@@ -7,246 +7,68 @@
 #include "DOMCameraCapabilities.h"
 #include "nsPIDOMWindow.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/CameraManagerBinding.h"
 #include "mozilla/dom/CameraCapabilitiesBinding.h"
 #include "Navigator.h"
 #include "CameraCommon.h"
 #include "ICameraControl.h"
+#include "CameraRecorderProfiles.h"
 
 namespace mozilla {
 namespace dom {
 
-/**
- * CameraRecorderVideoProfile
- */
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CameraRecorderVideoProfile, mParent)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(CameraRecorderVideoProfile)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(CameraRecorderVideoProfile)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CameraRecorderVideoProfile)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-JSObject*
-CameraRecorderVideoProfile::WrapObject(JSContext* aCx)
-{
-  return CameraRecorderVideoProfileBinding::Wrap(aCx, this);
-}
-
-CameraRecorderVideoProfile::CameraRecorderVideoProfile(nsISupports* aParent,
-    const ICameraControl::RecorderProfile::Video& aProfile)
-  : mParent(aParent)
-  , mCodec(aProfile.GetCodec())
-  , mBitrate(aProfile.GetBitsPerSecond())
-  , mFramerate(aProfile.GetFramesPerSecond())
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-
-  mSize.mWidth = aProfile.GetSize().width;
-  mSize.mHeight = aProfile.GetSize().height;
-
-  DOM_CAMERA_LOGI("  video: '%s' %ux%u bps=%u fps=%u\n",
-    NS_ConvertUTF16toUTF8(mCodec).get(), mSize.mWidth, mSize.mHeight, mBitrate, mFramerate);
-}
-
-CameraRecorderVideoProfile::~CameraRecorderVideoProfile()
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-}
-
-/**
- * CameraRecorderAudioProfile
- */
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CameraRecorderAudioProfile, mParent)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(CameraRecorderAudioProfile)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(CameraRecorderAudioProfile)
+NS_IMPL_CYCLE_COLLECTION_CLASS(CameraCapabilities)
 
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CameraRecorderAudioProfile)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-JSObject*
-CameraRecorderAudioProfile::WrapObject(JSContext* aCx)
-{
-  return CameraRecorderAudioProfileBinding::Wrap(aCx, this);
-}
-
-CameraRecorderAudioProfile::CameraRecorderAudioProfile(nsISupports* aParent,
-    const ICameraControl::RecorderProfile::Audio& aProfile)
-  : mParent(aParent)
-  , mCodec(aProfile.GetCodec())
-  , mBitrate(aProfile.GetBitsPerSecond())
-  , mSamplerate(aProfile.GetSamplesPerSecond())
-  , mChannels(aProfile.GetChannels())
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  DOM_CAMERA_LOGI("  audio: '%s' bps=%u samples/s=%u channels=%u\n",
-    NS_ConvertUTF16toUTF8(mCodec).get(), mBitrate, mSamplerate, mChannels);
-}
-
-CameraRecorderAudioProfile::~CameraRecorderAudioProfile()
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-}
-
-/**
- * CameraRecorderProfile
- */
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CameraRecorderProfile, mParent)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(CameraRecorderProfile)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(CameraRecorderProfile)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CameraRecorderProfile)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-JSObject*
-CameraRecorderProfile::WrapObject(JSContext* aCx)
-{
-  return CameraRecorderProfileBinding::Wrap(aCx, this);
-}
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CameraCapabilities)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  tmp->mRecorderProfiles = JS::UndefinedValue();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
-CameraRecorderProfile::CameraRecorderProfile(nsISupports* aParent,
-                                             const ICameraControl::RecorderProfile& aProfile)
-  : mParent(aParent)
-  , mName(aProfile.GetName())
-  , mContainerFormat(aProfile.GetContainer())
-  , mMimeType(aProfile.GetMimeType())
-  , mVideo(new CameraRecorderVideoProfile(this, aProfile.GetVideo()))
-  , mAudio(new CameraRecorderAudioProfile(this, aProfile.GetAudio()))
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  DOM_CAMERA_LOGI("profile: '%s' container=%s mime-type=%s\n",
-    NS_ConvertUTF16toUTF8(mName).get(),
-    NS_ConvertUTF16toUTF8(mContainerFormat).get(),
-    NS_ConvertUTF16toUTF8(mMimeType).get());
-}
-
-CameraRecorderProfile::~CameraRecorderProfile()
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-}
-
-/**
- * CameraRecorderProfiles
- */
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CameraRecorderProfiles, mParent)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(CameraRecorderProfiles)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(CameraRecorderProfiles)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CameraRecorderProfiles)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-JSObject*
-CameraRecorderProfiles::WrapObject(JSContext* aCx)
-{
-  return CameraRecorderProfilesBinding::Wrap(aCx, this);
-}
-
-CameraRecorderProfiles::CameraRecorderProfiles(nsISupports* aParent,
-                                               ICameraControl* aCameraControl)
-  : mParent(aParent)
-  , mCameraControl(aCameraControl)
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-}
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CameraCapabilities)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
-CameraRecorderProfiles::~CameraRecorderProfiles()
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-}
-
-void
-CameraRecorderProfiles::GetSupportedNames(unsigned aFlags, nsTArray<nsString>& aNames)
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p, flags=0x%x\n",
-    __func__, __LINE__, this, aFlags);
-
-  nsresult rv = mCameraControl->GetRecorderProfiles(aNames);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aNames.Clear();
-  }
-}
-
-CameraRecorderProfile*
-CameraRecorderProfiles::NamedGetter(const nsAString& aName, bool& aFound)
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p, name='%s'\n", __func__, __LINE__, this,
-    NS_ConvertUTF16toUTF8(aName).get());
-
-  CameraRecorderProfile* profile = mProfiles.GetWeak(aName, &aFound);
-  if (!aFound || !profile) {
-    nsRefPtr<ICameraControl::RecorderProfile> p = mCameraControl->GetProfileInfo(aName);
-    if (p) {
-      profile = new CameraRecorderProfile(this, *p);
-      mProfiles.Put(aName, profile);
-      aFound = true;
-    }
-  }
-  return profile;
-}
-
-bool
-CameraRecorderProfiles::NameIsEnumerable(const nsAString& aName)
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p, name='%s' (always returns true)\n",
-    __func__, __LINE__, this, NS_ConvertUTF16toUTF8(aName).get());
-
-  return true;
-}
-
-/**
- * CameraCapabilities
- */
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CameraCapabilities, mWindow)
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CameraCapabilities)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mRecorderProfiles)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(CameraCapabilities)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(CameraCapabilities)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CameraCapabilities)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 /* static */
 bool
 CameraCapabilities::HasSupport(JSContext* aCx, JSObject* aGlobal)
 {
   return Navigator::HasCameraSupport(aCx, aGlobal);
 }
 
-CameraCapabilities::CameraCapabilities(nsPIDOMWindow* aWindow,
-                                       ICameraControl* aCameraControl)
-  : mMaxFocusAreas(0)
-  , mMaxMeteringAreas(0)
-  , mMaxDetectedFaces(0)
-  , mMinExposureCompensation(0.0)
-  , mMaxExposureCompensation(0.0)
-  , mExposureCompensationStep(0.0)
+CameraCapabilities::CameraCapabilities(nsPIDOMWindow* aWindow)
+  : mRecorderProfiles(JS::UndefinedValue())
   , mWindow(aWindow)
-  , mCameraControl(aCameraControl)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   MOZ_COUNT_CTOR(CameraCapabilities);
+  mozilla::HoldJSObjects(this);
 }
 
 CameraCapabilities::~CameraCapabilities()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  mRecorderProfiles = JS::UndefinedValue();
+  mozilla::DropJSObjects(this);
   MOZ_COUNT_DTOR(CameraCapabilities);
 }
 
 JSObject*
 CameraCapabilities::WrapObject(JSContext* aCx)
 {
   return CameraCapabilitiesBinding::Wrap(aCx, this);
 }
@@ -255,246 +77,234 @@ CameraCapabilities::WrapObject(JSContext
   do {                                                        \
     if (NS_FAILED(rv)) {                                      \
       DOM_CAMERA_LOGW("Error %x trying to get " #param "\n",  \
         (rv));                                                \
     }                                                         \
   } while(0)
 
 nsresult
-CameraCapabilities::TranslateToDictionary(uint32_t aKey, nsTArray<CameraSize>& aSizes)
+CameraCapabilities::TranslateToDictionary(ICameraControl* aCameraControl,
+                                          uint32_t aKey, nsTArray<CameraSize>& aSizes)
 {
   nsresult rv;
   nsTArray<ICameraControl::Size> sizes;
 
-  rv = mCameraControl->Get(aKey, sizes);
+  rv = aCameraControl->Get(aKey, sizes);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   aSizes.Clear();
   aSizes.SetCapacity(sizes.Length());
   for (uint32_t i = 0; i < sizes.Length(); ++i) {
     CameraSize* s = aSizes.AppendElement();
     s->mWidth = sizes[i].width;
     s->mHeight = sizes[i].height;
   }
 
   return NS_OK;
 }
 
-void
-CameraCapabilities::GetPreviewSizes(nsTArray<dom::CameraSize>& retval)
+nsresult
+CameraCapabilities::Populate(ICameraControl* aCameraControl)
 {
-  if (mPreviewSizes.Length() == 0) {
-    nsresult rv = TranslateToDictionary(CAMERA_PARAM_SUPPORTED_PREVIEWSIZES,
-                                        mPreviewSizes);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PREVIEWSIZES);
+  NS_ENSURE_TRUE(aCameraControl, NS_ERROR_INVALID_ARG);
+
+  nsresult rv;
+
+  rv = TranslateToDictionary(aCameraControl, CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, mPreviewSizes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PREVIEWSIZES);
+
+  rv = TranslateToDictionary(aCameraControl, CAMERA_PARAM_SUPPORTED_PICTURESIZES, mPictureSizes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PICTURESIZES);
+
+  rv = TranslateToDictionary(aCameraControl, CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES, mThumbnailSizes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES);
+
+  rv = TranslateToDictionary(aCameraControl, CAMERA_PARAM_SUPPORTED_VIDEOSIZES, mVideoSizes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_VIDEOSIZES);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_PICTUREFORMATS, mFileFormats);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PICTUREFORMATS);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_WHITEBALANCES, mWhiteBalanceModes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_WHITEBALANCES);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_SCENEMODES, mSceneModes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_SCENEMODES);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_EFFECTS, mEffects);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_EFFECTS);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_FLASHMODES, mFlashModes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_FLASHMODES);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_FOCUSMODES, mFocusModes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_FOCUSMODES);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_ISOMODES, mIsoModes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_ISOMODES);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_ZOOMRATIOS, mZoomRatios);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_ZOOMRATIOS);
+
+  int32_t areas;
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS, areas);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS);
+  mMaxFocusAreas = areas < 0 ? 0 : areas;
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS, areas);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS);
+  mMaxMeteringAreas = areas < 0 ? 0 : areas;
+
+  int32_t faces;
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXDETECTEDFACES, faces);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXDETECTEDFACES);
+  mMaxDetectedFaces = faces < 0 ? 0 : faces;
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION, mMinExposureCompensation);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION, mMaxExposureCompensation);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP, mExposureCompensationStep);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP);
+
+  mRecorderProfileManager = aCameraControl->GetRecorderProfileManager();
+  if (!mRecorderProfileManager) {
+    DOM_CAMERA_LOGW("Unable to get recorder profile manager\n");
+  } else {
+    AutoJSContext js;
+
+    JS::Rooted<JSObject*> o(js);
+    nsresult rv = mRecorderProfileManager->GetJsObject(js, o.address());
+    if (NS_FAILED(rv)) {
+      DOM_CAMERA_LOGE("Failed to JS-objectify profile manager (0x%x)\n", rv);
+    } else {
+      mRecorderProfiles = JS::ObjectValue(*o);
+    }
   }
+
+  // For now, always return success, since the presence or absence of capabilities
+  // indicates whether or not they are supported.
+  return NS_OK;
+}
+
+void
+CameraCapabilities::GetPreviewSizes(nsTArray<dom::CameraSize>& retval) const
+{
   retval = mPreviewSizes;
 }
 
 void
-CameraCapabilities::GetPictureSizes(nsTArray<dom::CameraSize>& retval)
+CameraCapabilities::GetPictureSizes(nsTArray<dom::CameraSize>& retval) const
 {
-  if (mPictureSizes.Length() == 0) {
-    nsresult rv = TranslateToDictionary(CAMERA_PARAM_SUPPORTED_PICTURESIZES,
-                                        mPictureSizes);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PICTURESIZES);
-  }
   retval = mPictureSizes;
 }
 
 void
-CameraCapabilities::GetThumbnailSizes(nsTArray<dom::CameraSize>& retval)
+CameraCapabilities::GetThumbnailSizes(nsTArray<dom::CameraSize>& retval) const
 {
-  if (mThumbnailSizes.Length() == 0) {
-    nsresult rv = TranslateToDictionary(CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES,
-                                        mThumbnailSizes);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES);
-  }
   retval = mThumbnailSizes;
 }
 
 void
-CameraCapabilities::GetVideoSizes(nsTArray<dom::CameraSize>& retval)
+CameraCapabilities::GetVideoSizes(nsTArray<dom::CameraSize>& retval) const
 {
-  if (mVideoSizes.Length() == 0) {
-    nsresult rv = TranslateToDictionary(CAMERA_PARAM_SUPPORTED_VIDEOSIZES,
-                                        mVideoSizes);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_VIDEOSIZES);
-  }
   retval = mVideoSizes;
 }
 
 void
-CameraCapabilities::GetFileFormats(nsTArray<nsString>& retval)
+CameraCapabilities::GetFileFormats(nsTArray<nsString>& retval) const
 {
-  if (mFileFormats.Length() == 0) {
-    nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_PICTUREFORMATS,
-                                      mFileFormats);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PICTUREFORMATS);
-  }
   retval = mFileFormats;
 }
 
 void
-CameraCapabilities::GetWhiteBalanceModes(nsTArray<nsString>& retval)
+CameraCapabilities::GetWhiteBalanceModes(nsTArray<nsString>& retval) const
 {
-  if (mWhiteBalanceModes.Length() == 0) {
-    nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_WHITEBALANCES,
-                                      mWhiteBalanceModes);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_WHITEBALANCES);
-  }
   retval = mWhiteBalanceModes;
 }
 
 void
-CameraCapabilities::GetSceneModes(nsTArray<nsString>& retval)
+CameraCapabilities::GetSceneModes(nsTArray<nsString>& retval) const
 {
-  if (mSceneModes.Length() == 0) {
-    nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_SCENEMODES,
-                                      mSceneModes);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_SCENEMODES);
-  }
   retval = mSceneModes;
 }
 
 void
-CameraCapabilities::GetEffects(nsTArray<nsString>& retval)
+CameraCapabilities::GetEffects(nsTArray<nsString>& retval) const
 {
-  if (mEffects.Length() == 0) {
-    nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_EFFECTS,
-                                      mEffects);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_EFFECTS);
-  }
   retval = mEffects;
 }
 
 void
-CameraCapabilities::GetFlashModes(nsTArray<nsString>& retval)
+CameraCapabilities::GetFlashModes(nsTArray<nsString>& retval) const
 {
-  if (mFlashModes.Length() == 0) {
-    nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_FLASHMODES,
-                                      mFlashModes);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_FLASHMODES);
-  }
   retval = mFlashModes;
 }
 
 void
-CameraCapabilities::GetFocusModes(nsTArray<nsString>& retval)
+CameraCapabilities::GetFocusModes(nsTArray<nsString>& retval) const
 {
-  if (mFocusModes.Length() == 0) {
-    nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_FOCUSMODES,
-                                      mFocusModes);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_FOCUSMODES);
-  }
   retval = mFocusModes;
 }
 
 void
-CameraCapabilities::GetZoomRatios(nsTArray<double>& retval)
+CameraCapabilities::GetZoomRatios(nsTArray<double>& retval) const
 {
-  if (mZoomRatios.Length() == 0) {
-    nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_ZOOMRATIOS,
-                                      mZoomRatios);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_ZOOMRATIOS);
-  }
   retval = mZoomRatios;
 }
 
 uint32_t
-CameraCapabilities::MaxFocusAreas()
+CameraCapabilities::MaxFocusAreas() const
 {
-  if (mMaxFocusAreas == 0) {
-    int32_t areas;
-    nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS,
-                                      areas);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS);
-    mMaxFocusAreas = areas < 0 ? 0 : areas;
-  }
   return mMaxFocusAreas;
 }
 
 uint32_t
-CameraCapabilities::MaxMeteringAreas()
+CameraCapabilities::MaxMeteringAreas() const
 {
-  if (mMaxMeteringAreas == 0) {
-    int32_t areas;
-    nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS,
-                                      areas);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS);
-    mMaxMeteringAreas = areas < 0 ? 0 : areas;
-  }
   return mMaxMeteringAreas;
 }
 
 uint32_t
-CameraCapabilities::MaxDetectedFaces()
+CameraCapabilities::MaxDetectedFaces() const
 {
-  if (mMaxDetectedFaces == 0) {
-    int32_t faces;
-    nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXDETECTEDFACES,
-                                      faces);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXDETECTEDFACES);
-    mMaxDetectedFaces = faces < 0 ? 0 : faces;
-  }
   return mMaxDetectedFaces;
 }
 
 double
-CameraCapabilities::MinExposureCompensation()
+CameraCapabilities::MinExposureCompensation() const
 {
-  if (mMinExposureCompensation == 0.0) {
-    nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION,
-                                      mMinExposureCompensation);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION);
-  }
   return mMinExposureCompensation;
 }
 
 double
-CameraCapabilities::MaxExposureCompensation()
+CameraCapabilities::MaxExposureCompensation() const
 {
-  if (mMaxExposureCompensation == 0.0) {
-    nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION,
-                                      mMaxExposureCompensation);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION);
-  }
   return mMaxExposureCompensation;
 }
 
 double
-CameraCapabilities::ExposureCompensationStep()
+CameraCapabilities::ExposureCompensationStep() const
 {
-  if (mExposureCompensationStep == 0.0) {
-    nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP,
-                                      mExposureCompensationStep);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP);
-  }
   return mExposureCompensationStep;
 }
 
-CameraRecorderProfiles*
-CameraCapabilities::RecorderProfiles()
+void
+CameraCapabilities::GetRecorderProfiles(JSContext* aCx,
+                                        JS::MutableHandle<JS::Value> aRetval) const
 {
-  nsRefPtr<CameraRecorderProfiles> profiles = mRecorderProfiles;
-  if (!mRecorderProfiles) {
-    profiles = new CameraRecorderProfiles(this, mCameraControl);
-    mRecorderProfiles = profiles;
-  }
-  return profiles;
+  JS::ExposeValueToActiveJS(mRecorderProfiles);
+  aRetval.set(mRecorderProfiles);
 }
 
 void
-CameraCapabilities::GetIsoModes(nsTArray<nsString>& retval)
+CameraCapabilities::GetIsoModes(nsTArray<nsString>& retval) const
 {
-  if (mIsoModes.Length() == 0) {
-    nsresult rv = mCameraControl->Get(CAMERA_PARAM_SUPPORTED_ISOMODES,
-                                      mIsoModes);
-    LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_ISOMODES);
-  }
   retval = mIsoModes;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/camera/DOMCameraCapabilities.h
+++ b/dom/camera/DOMCameraCapabilities.h
@@ -4,229 +4,86 @@
  * 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/. */
 
 #ifndef mozilla_dom_CameraCapabilities_h__
 #define mozilla_dom_CameraCapabilities_h__
 
 #include "nsString.h"
 #include "nsAutoPtr.h"
-#include "base/basictypes.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/CameraManagerBinding.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 #include "nsPIDOMWindow.h"
-#include "nsHashKeys.h"
-#include "nsRefPtrHashtable.h"
-#include "nsDataHashtable.h"
-#include "ICameraControl.h"
 
 struct JSContext;
+class nsPIDOMWindow;
 
 namespace mozilla {
+
+class ICameraControl;
+class RecorderProfileManager;
+
 namespace dom {
 
-/**
- * CameraRecorderVideoProfile
- */
-class CameraRecorderVideoProfile MOZ_FINAL : public nsISupports
-                                           , public nsWrapperCache
-{
-public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CameraRecorderVideoProfile)
-
-  explicit CameraRecorderVideoProfile(nsISupports* aParent,
-    const ICameraControl::RecorderProfile::Video& aProfile);
-  nsISupports* GetParentObject() const        { return mParent; }
-  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
-
-  uint32_t BitsPerSecond() const              { return mBitrate; }
-  uint32_t FramesPerSecond() const            { return mFramerate; }
-  void GetCodec(nsAString& aCodec) const      { aCodec = mCodec; }
-
-  void GetSize(dom::CameraSize& aSize) const  { aSize = mSize; }
-
-  // XXXmikeh - legacy, remove these when the Camera app is updated
-  uint32_t Width() const                      { return mSize.mWidth; }
-  uint32_t Height() const                     { return mSize.mHeight; }
-
-protected:
-  virtual ~CameraRecorderVideoProfile();
-
-  nsCOMPtr<nsISupports> mParent;
-
-  const nsString mCodec;
-  uint32_t mBitrate;
-  uint32_t mFramerate;
-  dom::CameraSize mSize;
-
-private:
-  DISALLOW_EVIL_CONSTRUCTORS(CameraRecorderVideoProfile);
-};
-
-/**
- * CameraRecorderAudioProfile
- */
-class CameraRecorderAudioProfile MOZ_FINAL : public nsISupports
-                                           , public nsWrapperCache
-{
-public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CameraRecorderAudioProfile)
-
-  explicit CameraRecorderAudioProfile(nsISupports* aParent,
-    const ICameraControl::RecorderProfile::Audio& aProfile);
-  nsISupports* GetParentObject() const    { return mParent; }
-  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
-
-  uint32_t BitsPerSecond() const          { return mBitrate; }
-  uint32_t SamplesPerSecond() const       { return mSamplerate; }
-  uint32_t Channels() const               { return mChannels; }
-  void GetCodec(nsAString& aCodec) const  { aCodec = mCodec; }
-
-protected:
-  virtual ~CameraRecorderAudioProfile();
-
-  nsCOMPtr<nsISupports> mParent;
-
-  const nsString mCodec;
-  uint32_t mBitrate;
-  uint32_t mSamplerate;
-  uint32_t mChannels;
-
-private:
-  DISALLOW_EVIL_CONSTRUCTORS(CameraRecorderAudioProfile);
-};
-
-/**
- * CameraRecorderProfile
- */
-class CameraRecorderProfile MOZ_FINAL : public nsISupports
-                                      , public nsWrapperCache
-{
-public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CameraRecorderProfile)
-
-  explicit CameraRecorderProfile(nsISupports* aParent,
-                                 const ICameraControl::RecorderProfile& aProfile);
-  nsISupports* GetParentObject() const          { return mParent; }
-  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
-
-  void GetMimeType(nsAString& aMimeType) const  { aMimeType = mMimeType; }
-
-  CameraRecorderVideoProfile* Video()           { return mVideo; }
-  CameraRecorderAudioProfile* Audio()           { return mAudio; }
-
-  void GetName(nsAString& aName) const          { aName = mName; }
-
-  void
-  GetContainerFormat(nsAString& aContainerFormat) const
-  {
-    aContainerFormat = mContainerFormat;
-  }
-
-protected:
-  virtual ~CameraRecorderProfile();
-
-  nsCOMPtr<nsISupports> mParent;
-
-  const nsString mName;
-  const nsString mContainerFormat;
-  const nsString mMimeType;
-
-  nsRefPtr<CameraRecorderVideoProfile> mVideo;
-  nsRefPtr<CameraRecorderAudioProfile> mAudio;
-
-private:
-  DISALLOW_EVIL_CONSTRUCTORS(CameraRecorderProfile);
-};
-
-/**
- * CameraRecorderProfiles
- */
-class CameraRecorderProfiles MOZ_FINAL : public nsISupports
-                                       , public nsWrapperCache
-{
-public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CameraRecorderProfiles)
-
-  explicit CameraRecorderProfiles(nsISupports* aParent,
-                                  ICameraControl* aCameraControl);
-  nsISupports* GetParentObject() const { return mParent; }
-  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
-
-  CameraRecorderProfile* NamedGetter(const nsAString& aName, bool& aFound);
-  bool NameIsEnumerable(const nsAString& aName);
-  void GetSupportedNames(unsigned aFlags, nsTArray<nsString>& aNames);
-
-protected:
-  virtual ~CameraRecorderProfiles();
-
-  nsCOMPtr<nsISupports> mParent;
-  nsRefPtr<ICameraControl> mCameraControl;
-  nsRefPtrHashtable<nsStringHashKey, CameraRecorderProfile> mProfiles;
-
-private:
-  DISALLOW_EVIL_CONSTRUCTORS(CameraRecorderProfiles);
-};
-
-/**
- * CameraCapabilities
- */
 class CameraCapabilities MOZ_FINAL : public nsISupports
                                    , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CameraCapabilities)
 
   // Because this header's filename doesn't match its C++ or DOM-facing
   // classname, we can't rely on the [Func="..."] WebIDL tag to implicitly
   // include the right header for us; instead we must explicitly include a
   // HasSupport() method in each header. We can get rid of these with the
   // Great Renaming proposed in bug 983177.
   static bool HasSupport(JSContext* aCx, JSObject* aGlobal);
 
-  explicit CameraCapabilities(nsPIDOMWindow* aWindow,
-                              ICameraControl* aCameraControl);
+  explicit CameraCapabilities(nsPIDOMWindow* aWindow);
+
+  // Populate the camera capabilities interface from the specific
+  // camera control object.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aCameraControl' is null.
+  nsresult Populate(ICameraControl* aCameraControl);
 
   nsPIDOMWindow* GetParentObject() const { return mWindow; }
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
-  void GetPreviewSizes(nsTArray<CameraSize>& aRetVal);
-  void GetPictureSizes(nsTArray<CameraSize>& aRetVal);
-  void GetThumbnailSizes(nsTArray<CameraSize>& aRetVal);
-  void GetVideoSizes(nsTArray<CameraSize>& aRetVal);
-  void GetFileFormats(nsTArray<nsString>& aRetVal);
-  void GetWhiteBalanceModes(nsTArray<nsString>& aRetVal);
-  void GetSceneModes(nsTArray<nsString>& aRetVal);
-  void GetEffects(nsTArray<nsString>& aRetVal);
-  void GetFlashModes(nsTArray<nsString>& aRetVal);
-  void GetFocusModes(nsTArray<nsString>& aRetVal);
-  void GetZoomRatios(nsTArray<double>& aRetVal);
-  uint32_t MaxFocusAreas();
-  uint32_t MaxMeteringAreas();
-  uint32_t MaxDetectedFaces();
-  double MinExposureCompensation();
-  double MaxExposureCompensation();
-  double ExposureCompensationStep();
-  void GetIsoModes(nsTArray<nsString>& aRetVal);
-
-  CameraRecorderProfiles* RecorderProfiles();
+  void GetPreviewSizes(nsTArray<CameraSize>& aRetVal) const;
+  void GetPictureSizes(nsTArray<CameraSize>& aRetVal) const;
+  void GetThumbnailSizes(nsTArray<CameraSize>& aRetVal) const;
+  void GetVideoSizes(nsTArray<CameraSize>& aRetVal) const;
+  void GetFileFormats(nsTArray<nsString>& aRetVal) const;
+  void GetWhiteBalanceModes(nsTArray<nsString>& aRetVal) const;
+  void GetSceneModes(nsTArray<nsString>& aRetVal) const;
+  void GetEffects(nsTArray<nsString>& aRetVal) const;
+  void GetFlashModes(nsTArray<nsString>& aRetVal) const;
+  void GetFocusModes(nsTArray<nsString>& aRetVal) const;
+  void GetZoomRatios(nsTArray<double>& aRetVal) const;
+  uint32_t MaxFocusAreas() const;
+  uint32_t MaxMeteringAreas() const;
+  uint32_t MaxDetectedFaces() const;
+  double MinExposureCompensation() const;
+  double MaxExposureCompensation() const;
+  double ExposureCompensationStep() const;
+  void GetRecorderProfiles(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval) const;
+  void GetIsoModes(nsTArray<nsString>& aRetVal) const;
 
 protected:
   ~CameraCapabilities();
 
-  nsresult TranslateToDictionary(uint32_t aKey, nsTArray<CameraSize>& aSizes);
+  nsresult TranslateToDictionary(ICameraControl* aCameraControl,
+                                 uint32_t aKey, nsTArray<CameraSize>& aSizes);
 
   nsTArray<CameraSize> mPreviewSizes;
   nsTArray<CameraSize> mPictureSizes;
   nsTArray<CameraSize> mThumbnailSizes;
   nsTArray<CameraSize> mVideoSizes;
 
   nsTArray<nsString> mFileFormats;
   nsTArray<nsString> mWhiteBalanceModes;
@@ -241,20 +98,18 @@ protected:
   uint32_t mMaxFocusAreas;
   uint32_t mMaxMeteringAreas;
   uint32_t mMaxDetectedFaces;
 
   double mMinExposureCompensation;
   double mMaxExposureCompensation;
   double mExposureCompensationStep;
 
+  nsRefPtr<RecorderProfileManager> mRecorderProfileManager;
+  JS::Heap<JS::Value> mRecorderProfiles;
+
   nsRefPtr<nsPIDOMWindow> mWindow;
-  nsRefPtr<ICameraControl> mCameraControl;
-  nsRefPtr<CameraRecorderProfiles> mRecorderProfiles;
-
-private:
-  DISALLOW_EVIL_CONSTRUCTORS(CameraCapabilities);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_CameraCapabilities_h__
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -659,17 +659,22 @@ nsDOMCameraControl::SetOnFacesDetected(C
 }
 
 already_AddRefed<dom::CameraCapabilities>
 nsDOMCameraControl::Capabilities()
 {
   nsRefPtr<CameraCapabilities> caps = mCapabilities;
 
   if (!caps) {
-    caps = new CameraCapabilities(mWindow, mCameraControl);
+    caps = new CameraCapabilities(mWindow);
+    nsresult rv = caps->Populate(mCameraControl);
+    if (NS_FAILED(rv)) {
+      DOM_CAMERA_LOGW("Failed to populate camera capabilities (%d)\n", rv);
+      return nullptr;
+    }
     mCapabilities = caps;
   }
 
   return caps.forget();
 }
 
 class ImmediateErrorCallback : public nsRunnable
 {
@@ -1130,19 +1135,20 @@ nsDOMCameraControl::DispatchStateEvent(c
 
 // Camera Control event handlers--must only be called from the Main Thread!
 void
 nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState aState)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ErrorResult ignored;
 
+  DOM_CAMERA_LOGI("DOM OnHardwareStateChange(%d)\n", aState);
+
   switch (aState) {
     case CameraControlListener::kHardwareOpen:
-      DOM_CAMERA_LOGI("DOM OnHardwareStateChange: open\n");
       {
         // The hardware is open, so we can return a camera to JS, even if
         // the preview hasn't started yet.
         nsRefPtr<Promise> promise = mGetCameraPromise.forget();
         if (promise) {
           CameraGetPromiseData data;
           data.mCamera = this;
           data.mConfiguration = *mCurrentConfiguration;
@@ -1153,17 +1159,16 @@ nsDOMCameraControl::OnHardwareStateChang
         if (cb) {
           ErrorResult ignored;
           cb->Call(*this, *mCurrentConfiguration, ignored);
         }
       }
       break;
 
     case CameraControlListener::kHardwareClosed:
-      DOM_CAMERA_LOGI("DOM OnHardwareStateChange: closed\n");
       {
         nsRefPtr<Promise> promise = mReleasePromise.forget();
         if (promise || mReleaseOnSuccessCb) {
           // If we have this event handler, this was a solicited hardware close.
           if (promise) {
             promise->MaybeResolve(JS::UndefinedHandleValue);
           }
           nsRefPtr<CameraReleaseCallback> cb = mReleaseOnSuccessCb.forget();
@@ -1178,17 +1183,16 @@ nsDOMCameraControl::OnHardwareStateChang
             cb->Call(ignored);
           }
         }
         DispatchTrustedEvent(NS_LITERAL_STRING("close"));
       }
       break;
 
     default:
-      DOM_CAMERA_LOGE("DOM OnHardwareStateChange: UNKNOWN=%d\n", aState);
       MOZ_ASSERT_UNREACHABLE("Unanticipated camera hardware state");
   }
 }
 
 void
 nsDOMCameraControl::OnShutter()
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -1393,17 +1397,17 @@ nsDOMCameraControl::OnAutoFocusMoving(bo
   if (aIsMoving) {
     DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("focusing"));
   }
 }
 
 void
 nsDOMCameraControl::OnFacesDetected(const nsTArray<ICameraControl::Face>& aFaces)
 {
-  DOM_CAMERA_LOGI("DOM OnFacesDetected %zu face(s)\n", aFaces.Length());
+  DOM_CAMERA_LOGI("DOM OnFacesDetected %u face(s)\n", aFaces.Length());
   MOZ_ASSERT(NS_IsMainThread());
 
   Sequence<OwningNonNull<DOMCameraDetectedFace> > faces;
   uint32_t len = aFaces.Length();
 
   if (faces.SetCapacity(len)) {
     nsRefPtr<DOMCameraDetectedFace> f;
     for (uint32_t i = 0; i < len; ++i) {
--- a/dom/camera/DOMCameraManager.cpp
+++ b/dom/camera/DOMCameraManager.cpp
@@ -54,17 +54,17 @@ GetCameraLog()
 ::WindowTable* nsDOMCameraManager::sActiveWindows = nullptr;
 
 nsDOMCameraManager::nsDOMCameraManager(nsPIDOMWindow* aWindow)
   : mWindowId(aWindow->WindowID())
   , mPermission(nsIPermissionManager::DENY_ACTION)
   , mWindow(aWindow)
 {
   /* member initializers and constructor code */
-  DOM_CAMERA_LOGT("%s:%d : this=%p, windowId=%" PRIx64 "\n", __func__, __LINE__, this, mWindowId);
+  DOM_CAMERA_LOGT("%s:%d : this=%p, windowId=%llx\n", __func__, __LINE__, this, mWindowId);
   MOZ_COUNT_CTOR(nsDOMCameraManager);
 }
 
 nsDOMCameraManager::~nsDOMCameraManager()
 {
   /* destructor code */
   MOZ_COUNT_DTOR(nsDOMCameraManager);
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
@@ -359,32 +359,32 @@ nsDOMCameraManager::PermissionCancelled(
     ErrorResult ignored;
     aOnError->Call(NS_LITERAL_STRING("Permission denied."), ignored);
   }
 }
 
 void
 nsDOMCameraManager::Register(nsDOMCameraControl* aDOMCameraControl)
 {
-  DOM_CAMERA_LOGI(">>> Register( aDOMCameraControl = %p ) mWindowId = 0x%" PRIx64 "\n", aDOMCameraControl, mWindowId);
+  DOM_CAMERA_LOGI(">>> Register( aDOMCameraControl = %p ) mWindowId = 0x%llx\n", aDOMCameraControl, mWindowId);
   MOZ_ASSERT(NS_IsMainThread());
 
   // Put the camera control into the hash table
   CameraControls* controls = sActiveWindows->Get(mWindowId);
   if (!controls) {
     controls = new CameraControls;
     sActiveWindows->Put(mWindowId, controls);
   }
   controls->AppendElement(aDOMCameraControl);
 }
 
 void
 nsDOMCameraManager::Shutdown(uint64_t aWindowId)
 {
-  DOM_CAMERA_LOGI(">>> Shutdown( aWindowId = 0x%" PRIx64 " )\n", aWindowId);
+  DOM_CAMERA_LOGI(">>> Shutdown( aWindowId = 0x%llx )\n", aWindowId);
   MOZ_ASSERT(NS_IsMainThread());
 
   CameraControls* controls = sActiveWindows->Get(aWindowId);
   if (!controls) {
     return;
   }
 
   uint32_t length = controls->Length();
--- a/dom/camera/FallbackCameraControl.cpp
+++ b/dom/camera/FallbackCameraControl.cpp
@@ -40,19 +40,16 @@ public:
   virtual nsresult Get(uint32_t aKey, nsTArray<Region>& aRegions) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
 
   virtual nsresult SetLocation(const Position& aLocation) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
 
   virtual nsresult Get(uint32_t aKey, nsTArray<Size>& aSizes) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
   virtual nsresult Get(uint32_t aKey, nsTArray<nsString>& aValues) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
   virtual nsresult Get(uint32_t aKey, nsTArray<double>& aValues) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
 
-  virtual nsresult GetRecorderProfiles(nsTArray<nsString>& aProfiles) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
-  virtual RecorderProfile* GetProfileInfo(const nsAString& aProfile) MOZ_OVERRIDE { return nullptr; }
-
   nsresult PushParameters() { return NS_ERROR_NOT_INITIALIZED; }
   nsresult PullParameters() { return NS_ERROR_NOT_INITIALIZED; }
 
 protected:
   ~FallbackCameraControl();
 
   virtual nsresult StartPreviewImpl() { return NS_ERROR_NOT_INITIALIZED; }
   virtual nsresult StopPreviewImpl() { return NS_ERROR_NOT_INITIALIZED; }
@@ -61,13 +58,14 @@ protected:
   virtual nsresult StopFaceDetectionImpl() { return NS_ERROR_NOT_INITIALIZED; }
   virtual nsresult TakePictureImpl() { return NS_ERROR_NOT_INITIALIZED; }
   virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
                                       const StartRecordingOptions* aOptions = nullptr) MOZ_OVERRIDE
                                         { return NS_ERROR_NOT_INITIALIZED; }
   virtual nsresult StopRecordingImpl() { return NS_ERROR_NOT_INITIALIZED; }
   virtual nsresult PushParametersImpl() { return NS_ERROR_NOT_INITIALIZED; }
   virtual nsresult PullParametersImpl() { return NS_ERROR_NOT_INITIALIZED; }
+  virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl() MOZ_OVERRIDE { return nullptr; }
 
 private:
   FallbackCameraControl(const FallbackCameraControl&) MOZ_DELETE;
   FallbackCameraControl& operator=(const FallbackCameraControl&) MOZ_DELETE;
 };
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -66,18 +66,21 @@ nsGonkCameraControl::nsGonkCameraControl
   , mLastThumbnailSize({0, 0})
   , mPreviewFps(30)
   , mResumePreviewAfterTakingPicture(false) // XXXmikeh - see bug 950102
   , mFlashSupported(false)
   , mLuminanceSupported(false)
   , mAutoFlashModeOverridden(false)
   , mSeparateVideoAndPreviewSizesSupported(false)
   , mDeferConfigUpdate(0)
+  , mMediaProfiles(nullptr)
   , mRecorder(nullptr)
   , mRecorderMonitor("GonkCameraControl::mRecorder.Monitor")
+  , mProfileManager(nullptr)
+  , mRecorderProfile(nullptr)
   , mVideoFile(nullptr)
   , mReentrantMonitor("GonkCameraControl::OnTakePicture.Monitor")
 {
   // Constructor runs on the main thread...
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   mImageContainer = LayerManager::CreateImageContainer();
 }
 
@@ -137,17 +140,16 @@ nsGonkCameraControl::Initialize()
 
   mCameraHw = GonkCameraHardware::Connect(this, mCameraId);
   if (!mCameraHw.get()) {
     DOM_CAMERA_LOGE("Failed to connect to camera %d (this=%p)\n", mCameraId, this);
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   DOM_CAMERA_LOGI("Initializing camera %d (this=%p, mCameraHw=%p)\n", mCameraId, this, mCameraHw.get());
-  mCurrentConfiguration.mRecorderProfile.Truncate();
 
   // Initialize our camera configuration database.
   PullParametersImpl();
 
   // Set preferred preview frame format.
   mParams.Set(CAMERA_PARAM_PREVIEWFORMAT, NS_LITERAL_STRING("yuv420sp"));
   // Turn off any normal pictures returned by the HDR scene mode
   mParams.Set(CAMERA_PARAM_SCENEMODE_HDR_RETURNNORMALPICTURE, false);
@@ -298,16 +300,19 @@ nsGonkCameraControl::SetConfigurationImp
   return StartPreviewImpl();
 }
 
 nsresult
 nsGonkCameraControl::SetPictureConfiguration(const Configuration& aConfig)
 {
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 
+  // remove any existing recorder profile
+  mRecorderProfile = nullptr;
+
   nsresult rv = SetPreviewSize(aConfig.mPreviewSize);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   mParams.Get(CAMERA_PARAM_PREVIEWFRAMERATE, mPreviewFps);
 
   DOM_CAMERA_LOGI("picture mode preview: wanted %ux%u, got %ux%u (%u fps)\n",
@@ -950,17 +955,17 @@ nsGonkCameraControl::SetupRecordingFlash
 nsresult
 nsGonkCameraControl::StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
                                         const StartRecordingOptions* aOptions)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
 
   ReentrantMonitorAutoEnter mon(mRecorderMonitor);
 
-  NS_ENSURE_TRUE(!mCurrentConfiguration.mRecorderProfile.IsEmpty(), NS_ERROR_NOT_INITIALIZED);
+  NS_ENSURE_TRUE(mRecorderProfile, NS_ERROR_NOT_INITIALIZED);
   NS_ENSURE_FALSE(mRecorder, NS_ERROR_FAILURE);
 
   /**
    * Get the base path from device storage and append the app-specified
    * filename to it.  The filename may include a relative subpath
    * (e.g.) "DCIM/IMG_0001.jpg".
    *
    * The camera app needs to provide the file extension '.3gp' for now.
@@ -1358,35 +1363,42 @@ nsGonkCameraControl::GetSupportedSize(co
   return rv;
 }
 
 nsresult
 nsGonkCameraControl::SetVideoConfiguration(const Configuration& aConfig)
 {
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 
-  RecorderProfile* profile;
-  if (!mRecorderProfiles.Get(aConfig.mRecorderProfile, &profile)) {
-    DOM_CAMERA_LOGE("Recorder profile '%s' is not supported\n",
-      NS_ConvertUTF16toUTF8(aConfig.mRecorderProfile).get());
+  // read preferences for camcorder
+  mMediaProfiles = MediaProfiles::getInstance();
+
+  nsAutoCString profile = NS_ConvertUTF16toUTF8(aConfig.mRecorderProfile);
+  mRecorderProfile = GetGonkRecorderProfileManager().take()->Get(profile.get());
+  if (!mRecorderProfile) {
+    DOM_CAMERA_LOGE("Recorder profile '%s' is not supported\n", profile.get());
     return NS_ERROR_INVALID_ARG;
   }
 
-  mCurrentConfiguration.mRecorderProfile = aConfig.mRecorderProfile;
-  const RecorderProfile::Video& video(profile->GetVideo());
-  const Size& size = video.GetSize();
-  int fps = video.GetFramesPerSecond();
-  if (fps <= 0 || size.width <= 0 || size.height <= 0) {
-    DOM_CAMERA_LOGE("Can't configure video with fps=%d, width=%d, height=%d\n",
-      fps, size.width, size.height);
+  const GonkRecorderVideoProfile* video = mRecorderProfile->GetGonkVideoProfile();
+  int width = video->GetWidth();
+  int height = video->GetHeight();
+  int fps = video->GetFramerate();
+  if (fps == -1 || width < 0 || height < 0) {
+    DOM_CAMERA_LOGE("Can't configure preview with fps=%d, width=%d, height=%d\n",
+      fps, width, height);
     return NS_ERROR_FAILURE;
   }
 
   PullParametersImpl();
 
+  Size size;
+  size.width = static_cast<uint32_t>(width);
+  size.height = static_cast<uint32_t>(height);
+
   {
     ICameraControlParameterSetAutoEnter set(this);
     nsresult rv;
 
     if (mSeparateVideoAndPreviewSizesSupported) {
       // The camera supports two video streams: a low(er) resolution preview
       // stream and and a potentially high(er) resolution stream for encoding. 
       rv = SetVideoSize(size);
@@ -1582,22 +1594,18 @@ nsGonkCameraControl::SetupRecording(int 
   const size_t SIZE = 256;
   char buffer[SIZE];
 
   ReentrantMonitorAutoEnter mon(mRecorderMonitor);
 
   mRecorder = new GonkRecorder();
   CHECK_SETARG_RETURN(mRecorder->init(), NS_ERROR_FAILURE);
 
-  nsresult rv =
-    GonkRecorderProfile::ConfigureRecorder(*mRecorder, mCameraId,
-                                           mCurrentConfiguration.mRecorderProfile);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
+  nsresult rv = mRecorderProfile->ConfigureRecorder(mRecorder);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   CHECK_SETARG_RETURN(mRecorder->setCamera(mCameraHw), NS_ERROR_FAILURE);
 
   DOM_CAMERA_LOGI("maxVideoLengthMs=%llu\n", aMaxVideoLengthMs);
   const uint64_t kMaxVideoLengthMs = INT64_MAX / 1000;
   if (aMaxVideoLengthMs == 0) {
     aMaxVideoLengthMs = -1;
   } else if (aMaxVideoLengthMs > kMaxVideoLengthMs) {
@@ -1658,86 +1666,37 @@ nsGonkCameraControl::StopImpl()
      mCameraHw->Close();
      mCameraHw.clear();
   }
 
   OnHardwareStateChange(CameraControlListener::kHardwareClosed);
   return NS_OK;
 }
 
-nsresult
-nsGonkCameraControl::LoadRecorderProfiles()
+already_AddRefed<GonkRecorderProfileManager>
+nsGonkCameraControl::GetGonkRecorderProfileManager()
 {
-  if (mRecorderProfiles.Count() == 0) {
-    nsTArray<nsRefPtr<RecorderProfile>> profiles;
-    nsresult rv = GonkRecorderProfile::GetAll(mCameraId, profiles);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
+  if (!mProfileManager) {
     nsTArray<Size> sizes;
-    rv = Get(CAMERA_PARAM_SUPPORTED_VIDEOSIZES, sizes);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return NS_ERROR_NOT_AVAILABLE;
-    }
+    nsresult rv = Get(CAMERA_PARAM_SUPPORTED_VIDEOSIZES, sizes);
+    NS_ENSURE_SUCCESS(rv, nullptr);
 
-    // Limit profiles to those video sizes supported by the camera hardware...
-    for (nsTArray<RecorderProfile>::size_type i = 0; i < profiles.Length(); ++i) {
-      int width = profiles[i]->GetVideo().GetSize().width; 
-      int height = profiles[i]->GetVideo().GetSize().height;
-      if (width < 0 || height < 0) {
-        DOM_CAMERA_LOGW("Ignoring weird profile '%s' with width and/or height < 0\n",
-          NS_ConvertUTF16toUTF8(profiles[i]->GetName()).get());
-        continue;
-      }
-      for (nsTArray<Size>::size_type n = 0; n < sizes.Length(); ++n) {
-        if (static_cast<uint32_t>(width) == sizes[n].width &&
-            static_cast<uint32_t>(height) == sizes[n].height) {
-          mRecorderProfiles.Put(profiles[i]->GetName(), profiles[i]);
-          break;
-        }
-      }
-    }
+    mProfileManager = new GonkRecorderProfileManager(mCameraId);
+    mProfileManager->SetSupportedResolutions(sizes);
   }
 
-  return NS_OK;
-}
-
-/* static */ PLDHashOperator
-nsGonkCameraControl::Enumerate(const nsAString& aProfileName,
-                               RecorderProfile* aProfile,
-                               void* aUserArg)
-{
-  nsTArray<nsString>* profiles = static_cast<nsTArray<nsString>*>(aUserArg);
-  MOZ_ASSERT(profiles);
-  profiles->AppendElement(aProfileName);
-  return PL_DHASH_NEXT;
+  nsRefPtr<GonkRecorderProfileManager> profileMgr = mProfileManager;
+  return profileMgr.forget();
 }
 
-nsresult
-nsGonkCameraControl::GetRecorderProfiles(nsTArray<nsString>& aProfiles)
+already_AddRefed<RecorderProfileManager>
+nsGonkCameraControl::GetRecorderProfileManagerImpl()
 {
-  nsresult rv = LoadRecorderProfiles();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  aProfiles.Clear();
-  mRecorderProfiles.EnumerateRead(Enumerate, static_cast<void*>(&aProfiles));
-  return NS_OK;
-}
-
-ICameraControl::RecorderProfile*
-nsGonkCameraControl::GetProfileInfo(const nsAString& aProfile)
-{
-  RecorderProfile* profile;
-  if (!mRecorderProfiles.Get(aProfile, &profile)) {
-    return nullptr;
-  }
-  return profile;
+  nsRefPtr<RecorderProfileManager> profileMgr = GetGonkRecorderProfileManager();
+  return profileMgr.forget();
 }
 
 void
 nsGonkCameraControl::OnRateLimitPreview(bool aLimit)
 {
   CameraControlImpl::OnRateLimitPreview(aLimit);
 }
 
--- a/dom/camera/GonkCameraControl.h
+++ b/dom/camera/GonkCameraControl.h
@@ -13,17 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #ifndef DOM_CAMERA_GONKCAMERACONTROL_H
 #define DOM_CAMERA_GONKCAMERACONTROL_H
 
 #include "base/basictypes.h"
-#include "nsRefPtrHashtable.h"
 #include <media/MediaProfiles.h>
 #include "mozilla/ReentrantMonitor.h"
 #include "DeviceStorage.h"
 #include "CameraControlImpl.h"
 #include "CameraCommon.h"
 #include "GonkRecorder.h"
 #include "GonkCameraHwMgr.h"
 #include "GonkCameraParameters.h"
@@ -75,20 +74,16 @@ public:
   virtual nsresult Get(uint32_t aKey, nsTArray<Region>& aRegions) MOZ_OVERRIDE;
 
   virtual nsresult SetLocation(const Position& aLocation) MOZ_OVERRIDE;
 
   virtual nsresult Get(uint32_t aKey, nsTArray<Size>& aSizes) MOZ_OVERRIDE;
   virtual nsresult Get(uint32_t aKey, nsTArray<nsString>& aValues) MOZ_OVERRIDE;
   virtual nsresult Get(uint32_t aKey, nsTArray<double>& aValues) MOZ_OVERRIDE;
 
-  virtual nsresult GetRecorderProfiles(nsTArray<nsString>& aProfiles) MOZ_OVERRIDE;
-  virtual ICameraControl::RecorderProfile* 
-    GetProfileInfo(const nsAString& aProfile) MOZ_OVERRIDE;
-
   nsresult PushParameters();
   nsresult PullParameters();
 
 protected:
   ~nsGonkCameraControl();
 
   using CameraControlImpl::OnRateLimitPreview;
   using CameraControlImpl::OnNewPreviewFrame;
@@ -120,30 +115,27 @@ protected:
   virtual nsresult StopFaceDetectionImpl() MOZ_OVERRIDE;
   virtual nsresult TakePictureImpl() MOZ_OVERRIDE;
   virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
                                       const StartRecordingOptions* aOptions = nullptr) MOZ_OVERRIDE;
   virtual nsresult StopRecordingImpl() MOZ_OVERRIDE;
   virtual nsresult ResumeContinuousFocusImpl() MOZ_OVERRIDE;
   virtual nsresult PushParametersImpl() MOZ_OVERRIDE;
   virtual nsresult PullParametersImpl() MOZ_OVERRIDE;
+  virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl() MOZ_OVERRIDE;
+  already_AddRefed<GonkRecorderProfileManager> GetGonkRecorderProfileManager();
 
   nsresult SetupRecording(int aFd, int aRotation, uint64_t aMaxFileSizeBytes,
                           uint64_t aMaxVideoLengthMs);
   nsresult SetupRecordingFlash(bool aAutoEnableLowLightTorch);
   nsresult SetPreviewSize(const Size& aSize);
   nsresult SetVideoSize(const Size& aSize);
   nsresult PausePreview();
   nsresult GetSupportedSize(const Size& aSize, const nsTArray<Size>& supportedSizes, Size& best);
 
-  nsresult LoadRecorderProfiles();
-  static PLDHashOperator Enumerate(const nsAString& aProfileName,
-                                   RecorderProfile* aProfile,
-                                   void* aUserArg);
-
   friend class SetPictureSize;
   friend class SetThumbnailSize;
   nsresult SetPictureSize(const Size& aSize);
   nsresult SetPictureSizeImpl(const Size& aSize);
   nsresult SetThumbnailSize(const Size& aSize);
   nsresult UpdateThumbnailSize();
   nsresult SetThumbnailSizeImpl(const Size& aSize);
 
@@ -160,24 +152,26 @@ protected:
   bool                      mLuminanceSupported;
   bool                      mAutoFlashModeOverridden;
   bool                      mSeparateVideoAndPreviewSizesSupported;
   Atomic<uint32_t>          mDeferConfigUpdate;
   GonkCameraParameters      mParams;
 
   nsRefPtr<mozilla::layers::ImageContainer> mImageContainer;
 
+  android::MediaProfiles*   mMediaProfiles;
   nsRefPtr<android::GonkRecorder> mRecorder;
   // Touching mRecorder happens inside this monitor because the destructor
   // can run on any thread, and we need to be able to clean up properly if
   // GonkCameraControl goes away.
   ReentrantMonitor          mRecorderMonitor;
 
-  // Supported recorder profiles
-  nsRefPtrHashtable<nsStringHashKey, RecorderProfile> mRecorderProfiles;
+  // Camcorder profile settings for the desired quality level
+  nsRefPtr<GonkRecorderProfileManager> mProfileManager;
+  nsRefPtr<GonkRecorderProfile> mRecorderProfile;
 
   nsRefPtr<DeviceStorageFile> mVideoFile;
   nsString                  mFileFormat;
 
   // Guards against calling StartPreviewImpl() while in OnTakePictureComplete().
   ReentrantMonitor          mReentrantMonitor;
 
 private:
--- a/dom/camera/GonkRecorderProfiles.cpp
+++ b/dom/camera/GonkRecorderProfiles.cpp
@@ -1,27 +1,26 @@
 /*
- * Copyright (C) 2012-2014 Mozilla Foundation
+ * Copyright (C) 2012 Mozilla Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "GonkRecorderProfiles.h"
 #include <media/MediaProfiles.h>
-#include "nsMimeTypes.h"
 #include "GonkRecorder.h"
 #include "CameraControlImpl.h"
 #include "CameraCommon.h"
 
 using namespace mozilla;
 using namespace android;
 
 #define DEF_GONK_RECORDER_PROFILE(e, n) e##_INDEX,
@@ -34,323 +33,221 @@ enum {
 static struct {
   const char* name;
   int quality;
 } ProfileList[] = {
   #include "GonkRecorderProfiles.def"
   { nullptr, 0 }
 };
 
-/* static */ nsClassHashtable<nsUint32HashKey, ProfileHashtable> GonkRecorderProfile::sProfiles;
-/* static */ android::MediaProfiles* sMediaProfiles = nullptr;
+static MediaProfiles* sMediaProfiles = nullptr;
 
-static MediaProfiles*
-GetMediaProfiles()
+static bool
+IsQualitySupported(uint32_t aCameraId, uint32_t aQualityIndex)
+{
+  if (!sMediaProfiles) {
+    sMediaProfiles = MediaProfiles::getInstance();
+  }
+  camcorder_quality q = static_cast<camcorder_quality>(ProfileList[aQualityIndex].quality);
+  return sMediaProfiles->hasCamcorderProfile(static_cast<int>(aCameraId), q);
+}
+
+static int
+GetProfileParam(uint32_t aCameraId, uint32_t aQualityIndex, const char* aParam)
 {
   if (!sMediaProfiles) {
     sMediaProfiles = MediaProfiles::getInstance();
   }
-  MOZ_ASSERT(sMediaProfiles);
-  return sMediaProfiles;
-}
-
-static bool
-IsProfileSupported(uint32_t aCameraId, uint32_t aProfileIndex)
-{
-  MediaProfiles* profiles = GetMediaProfiles();
-  camcorder_quality q = static_cast<camcorder_quality>(ProfileList[aProfileIndex].quality);
-  return profiles->hasCamcorderProfile(static_cast<int>(aCameraId), q);
-}
-
-static int
-GetProfileParameter(uint32_t aCameraId, uint32_t aProfileIndex, const char* aParameter)
-{
-  MediaProfiles* profiles = GetMediaProfiles();
-  camcorder_quality q = static_cast<camcorder_quality>(ProfileList[aProfileIndex].quality);
-  return profiles->getCamcorderProfileParamByName(aParameter, static_cast<int>(aCameraId), q);
-}
-
-/* static */ bool
-GonkRecorderVideo::Translate(video_encoder aCodec, nsAString& aCodecName)
-{
-  switch (aCodec) {
-    case VIDEO_ENCODER_H263:
-      aCodecName.AssignASCII("h263");
-      break;
-
-    case VIDEO_ENCODER_H264:
-      aCodecName.AssignASCII("h264");
-      break;
-
-    case VIDEO_ENCODER_MPEG_4_SP:
-      aCodecName.AssignASCII("mpeg4sp");
-      break;
-
-    default:
-      return false;
-  }
-
-  return true;
-}
-
-int
-GonkRecorderVideo::GetProfileParameter(const char* aParameter)
-{
-  return ::GetProfileParameter(mCameraId, mProfileIndex, aParameter);
-}
-
-GonkRecorderVideo::GonkRecorderVideo(uint32_t aCameraId, uint32_t aProfileIndex)
-  : mCameraId(aCameraId)
-  , mProfileIndex(aProfileIndex)
-  , mIsValid(false)
-{
-  mPlatformEncoder = static_cast<video_encoder>(GetProfileParameter("vid.codec"));
-  bool isValid = Translate(mPlatformEncoder, mCodec);
-
-  int v = GetProfileParameter("vid.width");
-  if (v >= 0) {
-    mSize.width = v;
-  } else {
-    isValid = false;
-  }
-  v = GetProfileParameter("vid.height");
-  if (v >= 0) {
-    mSize.height = v;
-  } else {
-    isValid = false;
-  }
-  v = GetProfileParameter("vid.bps");
-  if (v >= 0) {
-    mBitsPerSecond = v;
-  } else {
-    isValid = false;
-  }
-  v = GetProfileParameter("vid.fps");
-  if (v >= 0) {
-    mFramesPerSecond = v;
-  } else {
-    isValid = false;
-  }
-
-  mIsValid = isValid;
-}
-
-/* static */ bool
-GonkRecorderAudio::Translate(audio_encoder aCodec, nsAString& aCodecName)
-{
-  switch (aCodec) {
-    case AUDIO_ENCODER_AMR_NB:
-      aCodecName.AssignASCII("amrnb");
-      break;
-
-    case AUDIO_ENCODER_AMR_WB:
-      aCodecName.AssignASCII("amrwb");
-      break;
-
-    case AUDIO_ENCODER_AAC:
-      aCodecName.AssignASCII("aac");
-      break;
-
-    default:
-      return false;
-  }
-
-  return true;
-}
-
-int
-GonkRecorderAudio::GetProfileParameter(const char* aParameter)
-{
-  return ::GetProfileParameter(mCameraId, mProfileIndex, aParameter);
+  camcorder_quality q = static_cast<camcorder_quality>(ProfileList[aQualityIndex].quality);
+  return sMediaProfiles->getCamcorderProfileParamByName(aParam, static_cast<int>(aCameraId), q);
 }
 
-GonkRecorderAudio::GonkRecorderAudio(uint32_t aCameraId, uint32_t aProfileIndex)
-  : mCameraId(aCameraId)
-  , mProfileIndex(aProfileIndex)
-  , mIsValid(false)
-{
-  mPlatformEncoder = static_cast<audio_encoder>(GetProfileParameter("aud.codec"));
-  bool isValid = Translate(mPlatformEncoder, mCodec);
-
-  int v = GetProfileParameter("aud.ch");
-  if (v >= 0) {
-    mChannels = v;
-  } else {
-    isValid = false;
-  }
-  v = GetProfileParameter("aud.bps");
-  if (v >= 0) {
-    mBitsPerSecond = v;
-  } else {
-    isValid = false;
-  }
-  v = GetProfileParameter("aud.hz");
-  if (v >= 0) {
-    mSamplesPerSecond = v;
-  } else {
-    isValid = false;
-  }
-
-  mIsValid = isValid;
-}
-
-/* static */ bool
-GonkRecorderProfile::Translate(output_format aContainer, nsAString& aContainerName)
+/**
+ * Recorder profile.
+ */
+static RecorderProfile::FileFormat
+TranslateFileFormat(output_format aFileFormat)
 {
-  switch (aContainer) {
-    case OUTPUT_FORMAT_THREE_GPP:
-      aContainerName.AssignASCII("3gp");
-      break;
-
-    case OUTPUT_FORMAT_MPEG_4:
-      aContainerName.AssignASCII("mp4");
-      break;
-
-    default:
-      return false;
+  switch (aFileFormat) {
+    case OUTPUT_FORMAT_THREE_GPP: return RecorderProfile::THREE_GPP;
+    case OUTPUT_FORMAT_MPEG_4:    return RecorderProfile::MPEG4;
+    default:                      return RecorderProfile::UNKNOWN;
   }
-
-  return true;
-}
-
-/* static */ bool
-GonkRecorderProfile::GetMimeType(output_format aContainer, nsAString& aMimeType)
-{
-  switch (aContainer) {
-    case OUTPUT_FORMAT_THREE_GPP:
-      aMimeType.AssignASCII(VIDEO_3GPP);
-      break;
-
-    case OUTPUT_FORMAT_MPEG_4:
-      aMimeType.AssignASCII(VIDEO_MP4);
-      break;
-
-    default:
-      return false;
-  }
-
-  return true;
 }
 
-int
-GonkRecorderProfile::GetProfileParameter(const char* aParameter)
-{
-  return ::GetProfileParameter(mCameraId, mProfileIndex, aParameter);
-}
-
-GonkRecorderProfile::GonkRecorderProfile(uint32_t aCameraId,
-                                         uint32_t aProfileIndex,
-                                         const nsAString& aName)
-  : GonkRecorderProfileBase<GonkRecorderAudio, GonkRecorderVideo>(aCameraId,
-                                                                  aProfileIndex, aName)
-  , mCameraId(aCameraId)
-  , mProfileIndex(aProfileIndex)
-  , mIsValid(false)
+GonkRecorderProfile::GonkRecorderProfile(uint32_t aCameraId, uint32_t aQualityIndex)
+  : RecorderProfileBase<GonkRecorderAudioProfile, GonkRecorderVideoProfile>(aCameraId, aQualityIndex)
 {
-  mOutputFormat = static_cast<output_format>(GetProfileParameter("file.format"));
-  bool isValid = Translate(mOutputFormat, mContainer);
-  isValid = GetMimeType(mOutputFormat, mMimeType) ? isValid : false;
-
-  mIsValid = isValid && mAudio.IsValid() && mVideo.IsValid();
-}
-
-/* static */ PLDHashOperator
-GonkRecorderProfile::Enumerate(const nsAString& aProfileName,
-                               GonkRecorderProfile* aProfile,
-                               void* aUserArg)
-{
-  nsTArray<nsRefPtr<ICameraControl::RecorderProfile>>* profiles =
-    static_cast<nsTArray<nsRefPtr<ICameraControl::RecorderProfile>>*>(aUserArg);
-  MOZ_ASSERT(profiles);
-  profiles->AppendElement(aProfile);
-  return PL_DHASH_NEXT;
+  DOM_CAMERA_LOGT("%s:%d : this=%p, mCameraId=%d, mQualityIndex=%d\n", __func__, __LINE__, this, mCameraId, mQualityIndex);
+  mPlatformOutputFormat = static_cast<output_format>(GetProfileParam(mCameraId, mQualityIndex, "file.format"));
+  mFileFormat = TranslateFileFormat(mPlatformOutputFormat);
+  if (aQualityIndex < PROFILE_COUNT) {
+    mName = ProfileList[aQualityIndex].name;
+    DOM_CAMERA_LOGI("Created camera %d profile index %d: '%s'\n", mCameraId, mQualityIndex, mName);
+  }
 }
 
-/* static */
-ProfileHashtable*
-GonkRecorderProfile::GetProfileHashtable(uint32_t aCameraId)
+GonkRecorderProfile::~GonkRecorderProfile()
 {
-  ProfileHashtable* profiles = sProfiles.Get(aCameraId);
-  if (!profiles) {
-    profiles = new ProfileHashtable;
-    sProfiles.Put(aCameraId, profiles);
-
-    for (uint32_t i = 0; ProfileList[i].name; ++i) {
-      if (IsProfileSupported(aCameraId, i)) {
-        DOM_CAMERA_LOGI("Profile %d '%s' supported by platform\n", i, ProfileList[i].name);
-        nsAutoString name;
-        name.AssignASCII(ProfileList[i].name);
-        nsRefPtr<GonkRecorderProfile> profile = new GonkRecorderProfile(aCameraId, i, name);
-        if (!profile->IsValid()) {
-          DOM_CAMERA_LOGE("Profile %d '%s' is not valid\n", i, ProfileList[i].name);
-          continue;
-        }
-        profiles->Put(name, profile);
-      } else {
-        DOM_CAMERA_LOGI("Profile %d '%s' not supported by platform\n", i, ProfileList[i].name);
-      }
-    }
-  }
-  return profiles;
-}
-
-/* static */ nsresult
-GonkRecorderProfile::GetAll(uint32_t aCameraId,
-                            nsTArray<nsRefPtr<ICameraControl::RecorderProfile>>& aProfiles)
-{
-  ProfileHashtable* profiles = GetProfileHashtable(aCameraId);
-  if (!profiles) {
-    return NS_ERROR_FAILURE;
-  }
-
-  aProfiles.Clear();
-  profiles->EnumerateRead(Enumerate, static_cast<void*>(&aProfiles));
-  
-  return NS_OK;
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
 }
 
 nsresult
-GonkRecorderProfile::ConfigureRecorder(GonkRecorder& aRecorder)
+GonkRecorderProfile::ConfigureRecorder(GonkRecorder* aRecorder)
 {
+  if (!aRecorder) {
+    DOM_CAMERA_LOGW("ConfigureRecorder() called with null aRecorder\n");
+    return NS_ERROR_INVALID_ARG;
+  }
+
   static const size_t SIZE = 256;
   char buffer[SIZE];
 
   // set all the params
-  CHECK_SETARG(aRecorder.setAudioSource(AUDIO_SOURCE_CAMCORDER));
-  CHECK_SETARG(aRecorder.setVideoSource(VIDEO_SOURCE_CAMERA));
-  CHECK_SETARG(aRecorder.setOutputFormat(mOutputFormat));
-  CHECK_SETARG(aRecorder.setVideoFrameRate(mVideo.GetFramesPerSecond()));
-  CHECK_SETARG(aRecorder.setVideoSize(mVideo.GetSize().width, mVideo.GetSize().height));
-  CHECK_SETARG(aRecorder.setVideoEncoder(mVideo.GetPlatformEncoder()));
-  CHECK_SETARG(aRecorder.setAudioEncoder(mAudio.GetPlatformEncoder()));
+  CHECK_SETARG(aRecorder->setAudioSource(AUDIO_SOURCE_CAMCORDER));
+  CHECK_SETARG(aRecorder->setVideoSource(VIDEO_SOURCE_CAMERA));
+  CHECK_SETARG(aRecorder->setOutputFormat(GetOutputFormat()));
+  CHECK_SETARG(aRecorder->setVideoFrameRate(mVideo.GetFramerate()));
+  CHECK_SETARG(aRecorder->setVideoSize(mVideo.GetWidth(), mVideo.GetHeight()));
+  CHECK_SETARG(aRecorder->setVideoEncoder(mVideo.GetPlatformCodec()));
+  CHECK_SETARG(aRecorder->setAudioEncoder(mAudio.GetPlatformCodec()));
 
-  snprintf(buffer, SIZE, "video-param-encoding-bitrate=%d", mVideo.GetBitsPerSecond());
-  CHECK_SETARG(aRecorder.setParameters(String8(buffer)));
+  snprintf(buffer, SIZE, "video-param-encoding-bitrate=%d", mVideo.GetBitrate());
+  CHECK_SETARG(aRecorder->setParameters(String8(buffer)));
 
-  snprintf(buffer, SIZE, "audio-param-encoding-bitrate=%d", mAudio.GetBitsPerSecond());
-  CHECK_SETARG(aRecorder.setParameters(String8(buffer)));
+  snprintf(buffer, SIZE, "audio-param-encoding-bitrate=%d", mAudio.GetBitrate());
+  CHECK_SETARG(aRecorder->setParameters(String8(buffer)));
 
   snprintf(buffer, SIZE, "audio-param-number-of-channels=%d", mAudio.GetChannels());
-  CHECK_SETARG(aRecorder.setParameters(String8(buffer)));
+  CHECK_SETARG(aRecorder->setParameters(String8(buffer)));
 
-  snprintf(buffer, SIZE, "audio-param-sampling-rate=%d", mAudio.GetSamplesPerSecond());
-  CHECK_SETARG(aRecorder.setParameters(String8(buffer)));
+  snprintf(buffer, SIZE, "audio-param-sampling-rate=%d", mAudio.GetSamplerate());
+  CHECK_SETARG(aRecorder->setParameters(String8(buffer)));
 
   return NS_OK;
 }
 
-/* static */ nsresult
-GonkRecorderProfile::ConfigureRecorder(android::GonkRecorder& aRecorder,
-                                       uint32_t aCameraId,
-                                       const nsAString& aProfileName)
+/**
+ * Recorder audio profile.
+ */
+static RecorderAudioProfile::Codec
+TranslateAudioCodec(audio_encoder aCodec)
+{
+  switch (aCodec) {
+    case AUDIO_ENCODER_AMR_NB:  return RecorderAudioProfile::AMRNB;
+    case AUDIO_ENCODER_AMR_WB:  return RecorderAudioProfile::AMRWB;
+    case AUDIO_ENCODER_AAC:     return RecorderAudioProfile::AAC;
+    default:                    return RecorderAudioProfile::UNKNOWN;
+  }
+}
+
+GonkRecorderAudioProfile::GonkRecorderAudioProfile(uint32_t aCameraId, uint32_t aQualityIndex)
+  : RecorderAudioProfile(aCameraId, aQualityIndex)
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p, mCameraId=%d, mQualityIndex=%d\n", __func__, __LINE__, this, mCameraId, mQualityIndex);
+  mPlatformCodec = static_cast<audio_encoder>(GetProfileParam(mCameraId, mQualityIndex, "aud.codec"));
+  mCodec = TranslateAudioCodec(mPlatformCodec);
+  mBitrate = GetProfileParam(mCameraId, mQualityIndex, "aud.bps");
+  mSamplerate = GetProfileParam(mCameraId, mQualityIndex, "aud.hz");
+  mChannels = GetProfileParam(mCameraId, mQualityIndex, "aud.ch");
+}
+
+GonkRecorderAudioProfile::~GonkRecorderAudioProfile()
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
+
+/**
+ * Recorder video profile.
+ */
+static RecorderVideoProfile::Codec
+TranslateVideoCodec(video_encoder aCodec)
 {
-  ProfileHashtable* profiles = GetProfileHashtable(aCameraId);
-  if (!profiles) {
-    return NS_ERROR_FAILURE;
+  switch (aCodec) {
+    case VIDEO_ENCODER_H263:      return RecorderVideoProfile::H263;
+    case VIDEO_ENCODER_H264:      return RecorderVideoProfile::H264;
+    case VIDEO_ENCODER_MPEG_4_SP: return RecorderVideoProfile::MPEG4SP;
+    default:                      return RecorderVideoProfile::UNKNOWN;
+  }
+}
+
+GonkRecorderVideoProfile::GonkRecorderVideoProfile(uint32_t aCameraId, uint32_t aQualityIndex)
+  : RecorderVideoProfile(aCameraId, aQualityIndex)
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p, mCameraId=%d, mQualityIndex=%d\n", __func__, __LINE__, this, mCameraId, mQualityIndex);
+  mPlatformCodec = static_cast<video_encoder>(GetProfileParam(mCameraId, mQualityIndex, "vid.codec"));
+  mCodec = TranslateVideoCodec(mPlatformCodec);
+  mBitrate = GetProfileParam(mCameraId, mQualityIndex, "vid.bps");
+  mFramerate = GetProfileParam(mCameraId, mQualityIndex, "vid.fps");
+  mWidth = GetProfileParam(mCameraId, mQualityIndex, "vid.width");
+  mHeight = GetProfileParam(mCameraId, mQualityIndex, "vid.height");
+}
+
+GonkRecorderVideoProfile::~GonkRecorderVideoProfile()
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
+
+GonkRecorderProfileManager::GonkRecorderProfileManager(uint32_t aCameraId)
+  : RecorderProfileManager(aCameraId)
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  mMaxQualityIndex = sizeof(ProfileList) / sizeof(ProfileList[0]) - 1;
+}
+
+GonkRecorderProfileManager::~GonkRecorderProfileManager()
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
+
+bool
+GonkRecorderProfileManager::IsSupported(uint32_t aQualityIndex) const
+{
+  if (!IsQualitySupported(mCameraId, aQualityIndex)) {
+    // This profile is not supported
+    return false;
   }
 
-  GonkRecorderProfile* profile;
-  if (!profiles->Get(aProfileName, &profile)) {
-    return NS_ERROR_INVALID_ARG;
+  int width = GetProfileParam(mCameraId, aQualityIndex, "vid.width");
+  int height = GetProfileParam(mCameraId, aQualityIndex, "vid.height");
+  if (width == -1 || height == -1) {
+    // This would be unexpected, but we handle it just in case
+    DOM_CAMERA_LOGE("Camera %d recorder profile %d has width=%d, height=%d\n", mCameraId, aQualityIndex, width, height);
+    return false;
   }
 
-  return profile->ConfigureRecorder(aRecorder);
+  for (uint32_t i = 0; i < mSupportedSizes.Length(); ++i) {
+    if (static_cast<uint32_t>(width) == mSupportedSizes[i].width &&
+      static_cast<uint32_t>(height) == mSupportedSizes[i].height)
+    {
+      return true;
+    }
+  }
+  return false;
+}
+
+already_AddRefed<RecorderProfile>
+GonkRecorderProfileManager::Get(uint32_t aQualityIndex) const
+{
+  // This overrides virtual RecorderProfileManager::Get(...)
+  DOM_CAMERA_LOGT("%s:%d : aQualityIndex=%d\n", __func__, __LINE__, aQualityIndex);
+  nsRefPtr<RecorderProfile> profile = new GonkRecorderProfile(mCameraId, aQualityIndex);
+  return profile.forget();
 }
+
+already_AddRefed<GonkRecorderProfile>
+GonkRecorderProfileManager::Get(const char* aProfileName) const
+{
+  DOM_CAMERA_LOGT("%s:%d : aProfileName='%s'\n", __func__, __LINE__, aProfileName);
+  for (uint32_t i = 0; i < mMaxQualityIndex; ++i) {
+    if (strcmp(ProfileList[i].name, aProfileName) == 0) {
+      nsRefPtr<GonkRecorderProfile> profile = nullptr;
+      if (IsSupported(i)) {
+        profile = new GonkRecorderProfile(mCameraId, i);
+        return profile.forget();
+      }
+      return nullptr;
+    }
+  }
+
+  DOM_CAMERA_LOGW("Couldn't file recorder profile named '%s'\n", aProfileName);
+  return nullptr;
+}
--- a/dom/camera/GonkRecorderProfiles.h
+++ b/dom/camera/GonkRecorderProfiles.h
@@ -1,16 +1,17 @@
 /* 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/. */
 
 #ifndef DOM_CAMERA_GONK_RECORDER_PROFILES_H
 #define DOM_CAMERA_GONK_RECORDER_PROFILES_H
 
 #include <media/MediaProfiles.h>
+#include "CameraRecorderProfiles.h"
 #include "ICameraControl.h"
 
 #ifndef CHECK_SETARG_RETURN
 #define CHECK_SETARG_RETURN(x, rv)      \
   do {                                  \
     if (x) {                            \
       DOM_CAMERA_LOGE(#x " failed\n");  \
       return rv;                        \
@@ -18,132 +19,104 @@
   } while(0)
 #endif
 
 #ifndef CHECK_SETARG
 #define CHECK_SETARG(x) CHECK_SETARG_RETURN(x, NS_ERROR_NOT_AVAILABLE)
 #endif
 
 namespace android {
-  class GonkRecorder;
+class GonkRecorder;
 };
 
 namespace mozilla {
 
 /**
- * class GonkRecorderProfileBase
+ * Gonk-specific video profile.
  */
-template<class A, class V>
-class GonkRecorderProfileBase : public ICameraControl::RecorderProfile
+class GonkRecorderVideoProfile : public RecorderVideoProfile
 {
 public:
-  GonkRecorderProfileBase(uint32_t aCameraId, uint32_t aProfileIndex, const nsAString& aName)
-    : RecorderProfile(aName)
-    , mAudio(aCameraId, aProfileIndex)
-    , mVideo(aCameraId, aProfileIndex)
-  { }
-
-  virtual const Audio& GetAudio() const MOZ_OVERRIDE { return mAudio; }
-  virtual const Video& GetVideo() const MOZ_OVERRIDE { return mVideo; }
+  GonkRecorderVideoProfile(uint32_t aCameraId, uint32_t aQualityIndex);
+  ~GonkRecorderVideoProfile();
+  android::video_encoder GetPlatformCodec() const { return mPlatformCodec; }
 
 protected:
-  virtual ~GonkRecorderProfileBase() { }
-  A mAudio;
-  V mVideo;
+  android::video_encoder mPlatformCodec;
 };
 
 /**
- * class GonkRecorderVideo
+ * Gonk-specific audio profile.
  */
-class GonkRecorderVideo : public ICameraControl::RecorderProfile::Video
+class GonkRecorderAudioProfile : public RecorderAudioProfile
 {
 public:
-  GonkRecorderVideo(uint32_t aCameraId, uint32_t aProfileIndex);
-  virtual ~GonkRecorderVideo() { }
-
-  android::video_encoder GetPlatformEncoder() const { return mPlatformEncoder; }
-  bool IsValid() const { return mIsValid; }
+  GonkRecorderAudioProfile(uint32_t aCameraId, uint32_t aQualityIndex);
+  ~GonkRecorderAudioProfile();
+  android::audio_encoder GetPlatformCodec() const { return mPlatformCodec; }
 
 protected:
-  int GetProfileParameter(const char* aParameter);
-  static bool Translate(android::video_encoder aCodec, nsAString& aCodecName);
-
-  uint32_t mCameraId;
-  uint32_t mProfileIndex;
-  bool mIsValid;
-  android::video_encoder mPlatformEncoder;
+  android::audio_encoder mPlatformCodec;
 };
 
 /**
- * class GonkRecorderAudio
+ * Gonk-specific recorder profile.
  */
-class GonkRecorderAudio : public ICameraControl::RecorderProfile::Audio
+class GonkRecorderProfile : public RecorderProfileBase<GonkRecorderAudioProfile, GonkRecorderVideoProfile>
 {
 public:
-  GonkRecorderAudio(uint32_t aCameraId, uint32_t aProfileIndex);
-  virtual ~GonkRecorderAudio() { }
+  GonkRecorderProfile(uint32_t aCameraId, uint32_t aQualityIndex);
+
+  GonkRecorderAudioProfile* GetGonkAudioProfile() { return &mAudio; }
+  GonkRecorderVideoProfile* GetGonkVideoProfile() { return &mVideo; }
+
+  android::output_format GetOutputFormat() const { return mPlatformOutputFormat; }
 
-  android::audio_encoder GetPlatformEncoder() const { return mPlatformEncoder; }
-  bool IsValid() const { return mIsValid; }
+  // Configures the specified recorder using this profile.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aRecorder' is null;
+  //  - NS_ERROR_NOT_AVAILABLE if the recorder rejected this profile.
+  nsresult ConfigureRecorder(android::GonkRecorder* aRecorder);
 
 protected:
-  int GetProfileParameter(const char* aParameter);
-  static bool Translate(android::audio_encoder aCodec, nsAString& aCodecName);
+  virtual ~GonkRecorderProfile();
 
-  uint32_t mCameraId;
-  uint32_t mProfileIndex;
-  bool mIsValid;
-  android::audio_encoder mPlatformEncoder;
+  android::output_format mPlatformOutputFormat;
 };
 
 /**
- * class GonkRecorderProfile
+ * Gonk-specific profile manager.
  */
-typedef nsRefPtrHashtable<nsStringHashKey, GonkRecorderProfile> ProfileHashtable;
-
-class GonkRecorderProfile
-  : public GonkRecorderProfileBase<GonkRecorderAudio, GonkRecorderVideo>
+class GonkRecorderProfileManager : public RecorderProfileManager
 {
 public:
-  static nsresult GetAll(uint32_t aCameraId,
-                         nsTArray<nsRefPtr<ICameraControl::RecorderProfile>>& aProfiles);
+  GonkRecorderProfileManager(uint32_t aCameraId);
+
+  /**
+   * Call this function to indicate that the specified resolutions are in fact
+   * supported by the camera hardware.  (Just because it appears in a recorder
+   * profile doesn't mean the hardware can handle it.)
+   */
+  void SetSupportedResolutions(const nsTArray<ICameraControl::Size>& aSizes)
+    { mSupportedSizes = aSizes; }
 
-  // Configures the specified recorder using the specified profile.
-  //
-  // Return values:
-  //  - NS_OK on success;
-  //  - NS_ERROR_INVALID_ARG if the profile isn't supported;
-  //  - NS_ERROR_NOT_AVAILABLE if the recorder rejected the profile.
-  static nsresult ConfigureRecorder(android::GonkRecorder& aRecorder,
-                                    uint32_t aCameraId,
-                                    const nsAString& aProfileName);
+  /**
+   * Call this function to remove all resolutions set by calling
+   * SetSupportedResolutions().
+   */
+  void ClearSupportedResolutions() { mSupportedSizes.Clear(); }
+
+  bool IsSupported(uint32_t aQualityIndex) const;
+
+  already_AddRefed<RecorderProfile> Get(uint32_t aQualityIndex) const;
+  already_AddRefed<GonkRecorderProfile> Get(const char* aProfileName) const;
 
 protected:
-  GonkRecorderProfile(uint32_t aCameraId,
-                      uint32_t aProfileIndex,
-                      const nsAString& aName);
-
-  int GetProfileParameter(const char* aParameter);
-
-  bool Translate(android::output_format aContainer, nsAString& aContainerName);
-  bool GetMimeType(android::output_format aContainer, nsAString& aMimeType);
-  bool IsValid() const { return mIsValid; };
+  virtual ~GonkRecorderProfileManager();
 
-  nsresult ConfigureRecorder(android::GonkRecorder& aRecorder);
-  static ProfileHashtable* GetProfileHashtable(uint32_t aCameraId);
-  static PLDHashOperator Enumerate(const nsAString& aProfileName,
-                                   GonkRecorderProfile* aProfile,
-                                   void* aUserArg);
-
-  uint32_t mCameraId;
-  uint32_t mProfileIndex;
-  bool mIsValid;
-  android::output_format mOutputFormat;
-
-  static nsClassHashtable<nsUint32HashKey, ProfileHashtable> sProfiles;
-
-private:
-  DISALLOW_EVIL_CONSTRUCTORS(GonkRecorderProfile);
+  nsTArray<ICameraControl::Size> mSupportedSizes;
 };
 
 }; // namespace mozilla
 
 #endif // DOM_CAMERA_GONK_RECORDER_PROFILES_H
--- a/dom/camera/ICameraControl.h
+++ b/dom/camera/ICameraControl.h
@@ -4,25 +4,25 @@
 
 #ifndef DOM_CAMERA_ICAMERACONTROL_H
 #define DOM_CAMERA_ICAMERACONTROL_H
 
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "nsISupportsImpl.h"
-#include "base/basictypes.h"
 
 struct DeviceStorageFileDescriptor;
 
 class nsIFile;
 
 namespace mozilla {
 
 class CameraControlListener;
+class RecorderProfileManager;
 
 // XXXmikeh - In some future patch this should move into the ICameraControl
 //  class as well, and the names updated to the 'k'-style;
 //  e.g. kParamPreviewSize, etc.
 enum {
   // current settings
   CAMERA_PARAM_PREVIEWSIZE,
   CAMERA_PARAM_PREVIEWFORMAT,
@@ -157,86 +157,16 @@ public:
     bool      hasLeftEye;
     Point     leftEye;
     bool      hasRightEye;
     Point     rightEye;
     bool      hasMouth;
     Point     mouth;
   };
 
-  class RecorderProfile
-  {
-  public:
-    class Video
-    {
-    public:
-      Video() { }
-      virtual ~Video() { }
-
-      const nsString& GetCodec() const    { return mCodec; }
-      const Size& GetSize() const         { return mSize; }
-      uint32_t GetBitsPerSecond() const   { return mBitsPerSecond; }
-      uint32_t GetFramesPerSecond() const { return mFramesPerSecond; }
-
-    protected:
-      nsString  mCodec;
-      Size      mSize;
-      uint32_t  mBitsPerSecond;
-      uint32_t  mFramesPerSecond;
-
-    private:
-      DISALLOW_EVIL_CONSTRUCTORS(Video);
-    };
-
-    class Audio
-    {
-    public:
-      Audio() { }
-      virtual ~Audio() { }
-
-      const nsString& GetCodec() const    { return mCodec; }
-
-      uint32_t GetChannels() const        { return mChannels; }
-      uint32_t GetBitsPerSecond() const   { return mBitsPerSecond; }
-      uint32_t GetSamplesPerSecond() const { return mSamplesPerSecond; }
-
-    protected:
-      nsString  mCodec;
-      uint32_t  mChannels;
-      uint32_t  mBitsPerSecond;
-      uint32_t  mSamplesPerSecond;
-
-    private:
-      DISALLOW_EVIL_CONSTRUCTORS(Audio);
-    };
-
-    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RecorderProfile)
-
-    RecorderProfile(const nsAString& aName)
-      : mName(aName)
-    { }
-
-    const nsString& GetName() const       { return mName; }
-    const nsString& GetContainer() const  { return mContainer; }
-    const nsString& GetMimeType() const   { return mMimeType; }
-
-    virtual const Video& GetVideo() const = 0;
-    virtual const Audio& GetAudio() const = 0;
-
-  protected:
-    virtual ~RecorderProfile() { }
-
-    nsString    mName;
-    nsString    mContainer;
-    nsString    mMimeType;
-
-  private:
-    DISALLOW_EVIL_CONSTRUCTORS(RecorderProfile);
-  };
-
   static already_AddRefed<ICameraControl> Create(uint32_t aCameraId);
 
   virtual void AddListener(CameraControlListener* aListener) = 0;
   virtual void RemoveListener(CameraControlListener* aListener) = 0;
 
   // Camera control methods.
   //
   // Return values:
@@ -285,19 +215,17 @@ public:
   virtual nsresult Get(uint32_t aKey, nsTArray<Region>& aRegions) = 0;
 
   virtual nsresult SetLocation(const Position& aLocation) = 0;
 
   virtual nsresult Get(uint32_t aKey, nsTArray<Size>& aSizes) = 0;
   virtual nsresult Get(uint32_t aKey, nsTArray<nsString>& aValues) = 0;
   virtual nsresult Get(uint32_t aKey, nsTArray<double>& aValues) = 0;
 
-  virtual nsresult GetRecorderProfiles(nsTArray<nsString>& aProfiles) = 0;
-  virtual RecorderProfile* GetProfileInfo(const nsAString& aProfile) = 0;
-
+  virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManager() = 0;
   virtual uint32_t GetCameraId() = 0;
 
   virtual void Shutdown() = 0;
 
 protected:
   virtual ~ICameraControl() { }
 
   friend class ICameraControlParameterSetAutoEnter;
--- a/dom/camera/moz.build
+++ b/dom/camera/moz.build
@@ -12,16 +12,17 @@ EXPORTS += [
     'CameraPreferences.h',
     'DOMCameraManager.h',
 ]
 
 UNIFIED_SOURCES += [
     'CameraControlImpl.cpp',
     'CameraPreferences.cpp',
     'CameraPreviewMediaStream.cpp',
+    'CameraRecorderProfiles.cpp',
     'DOMCameraCapabilities.cpp',
     'DOMCameraControl.cpp',
     'DOMCameraControlListener.cpp',
     'DOMCameraDetectedFace.cpp',
     'DOMCameraManager.cpp',
 ]
 
 if CONFIG['MOZ_B2G_CAMERA']:
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -207,24 +207,16 @@ var interfaceNamesInGlobalScope =
     {name: "CameraControl", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CameraDetectedFace", b2g: true, pref: "camera.control.face_detection.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CameraFacesDetectedEvent", b2g: true, pref: "camera.control.face_detection.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CameraManager", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "CameraRecorderAudioProfile", b2g: true},
-// IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "CameraRecorderProfile", b2g: true},
-// IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "CameraRecorderProfiles", b2g: true},
-// IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "CameraRecorderVideoProfile", b2g: true},
-// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CameraStateChangeEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "CanvasGradient",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "CanvasPattern",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "CanvasRenderingContext2D",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/webidl/CameraCapabilities.webidl
+++ b/dom/webidl/CameraCapabilities.webidl
@@ -1,64 +1,15 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* 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/.
  */
 
-/* The capabilities of the video recorder. These are guaranteed not to change
-   over the lifetime of that partcicular instance.
-*/
-[Func="CameraCapabilities::HasSupport"]
-interface CameraRecorderAudioProfile
-{
-  [Constant, Cached] readonly attribute DOMString codec;
-  [Constant, Cached] readonly attribute unsigned long bitsPerSecond;
-  [Constant, Cached] readonly attribute unsigned long samplesPerSecond;
-  [Constant, Cached] readonly attribute unsigned long channels;
-
-  jsonifier;
-};
-
-[Func="CameraCapabilities::HasSupport"]
-interface CameraRecorderVideoProfile
-{
-  [Constant, Cached] readonly attribute DOMString codec;
-  [Constant, Cached] readonly attribute unsigned long bitsPerSecond;
-  [Constant, Cached] readonly attribute unsigned long framesPerSecond;
-  [Constant, Cached] readonly attribute CameraSize size;
-
-  [Constant, Cached] readonly attribute unsigned long width;
-  [Constant, Cached] readonly attribute unsigned long height;
-
-  jsonifier;
-};
-
-[Func="CameraCapabilities::HasSupport"]
-interface CameraRecorderProfile
-{
-  [Constant, Cached] readonly attribute DOMString name;
-  [Constant, Cached] readonly attribute DOMString containerFormat;
-  [Constant, Cached] readonly attribute DOMString mimeType;
-
-  [Constant, Cached] readonly attribute CameraRecorderAudioProfile audio;
-  [Constant, Cached] readonly attribute CameraRecorderVideoProfile video;
-
-  jsonifier;
-};
-
-[Func="CameraCapabilities::HasSupport"]
-interface CameraRecorderProfiles
-{
-  getter CameraRecorderProfile(DOMString profile);
-
-  jsonifier;
-};
-
 /* The capabilities of a CameraControl instance. These are guaranteed
    not to change over the lifetime of that particular instance.
 */
 [Func="CameraCapabilities::HasSupport"]
 interface CameraCapabilities
 {
   [Constant, Cached] readonly attribute sequence<CameraSize> previewSizes;
   [Constant, Cached] readonly attribute sequence<CameraSize> pictureSizes;
@@ -78,14 +29,12 @@ interface CameraCapabilities
   [Constant, Cached] readonly attribute unsigned long maxFocusAreas;
   [Constant, Cached] readonly attribute unsigned long maxMeteringAreas;
   [Constant, Cached] readonly attribute unsigned long maxDetectedFaces;
 
   [Constant, Cached] readonly attribute double minExposureCompensation;
   [Constant, Cached] readonly attribute double maxExposureCompensation;
   [Constant, Cached] readonly attribute double exposureCompensationStep;
 
-  [Constant, Cached] readonly attribute CameraRecorderProfiles recorderProfiles;
+  [Constant, Cached] readonly attribute any recorderProfiles;
 
   [Constant, Cached] readonly attribute sequence<DOMString> isoModes;
-
-  jsonifier;
 };
--- a/js/src/asmjs/AsmJSModule.cpp
+++ b/js/src/asmjs/AsmJSModule.cpp
@@ -495,17 +495,17 @@ OnDetached()
     JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_OUT_OF_MEMORY);
 }
 
 static bool
 AsmJSHandleExecutionInterrupt()
 {
     AsmJSActivation *act = PerThreadData::innermostAsmJSActivation();
     act->module().setInterrupted(true);
-    bool ret = CheckForInterrupt(act->cx());
+    bool ret = HandleExecutionInterrupt(act->cx());
     act->module().setInterrupted(false);
     return ret;
 }
 
 static int32_t
 CoerceInPlace_ToInt32(MutableHandleValue val)
 {
     JSContext *cx = PerThreadData::innermostAsmJSActivation()->cx();
@@ -667,18 +667,18 @@ RedirectCall(void *fun, ABIFunctionType 
 }
 
 static void *
 AddressOf(AsmJSImmKind kind, ExclusiveContext *cx)
 {
     switch (kind) {
       case AsmJSImm_Runtime:
         return cx->runtimeAddressForJit();
-      case AsmJSImm_RuntimeInterruptUint32:
-        return cx->runtimeAddressOfInterruptUint32();
+      case AsmJSImm_RuntimeInterrupt:
+        return cx->runtimeAddressOfInterrupt();
       case AsmJSImm_StackLimit:
         return cx->stackLimitAddressForJitCode(StackForUntrustedScript);
       case AsmJSImm_ReportOverRecursed:
         return RedirectCall(FuncCast(AsmJSReportOverRecursed), Args_General0);
       case AsmJSImm_OnDetached:
         return RedirectCall(FuncCast(OnDetached), Args_General0);
       case AsmJSImm_HandleExecutionInterrupt:
         return RedirectCall(FuncCast(AsmJSHandleExecutionInterrupt), Args_General0);
--- a/js/src/irregexp/NativeRegExpMacroAssembler.cpp
+++ b/js/src/irregexp/NativeRegExpMacroAssembler.cpp
@@ -147,17 +147,17 @@ NativeRegExpMacroAssembler::GenerateCode
     frameSize = JS_ROUNDUP(frameSize + masm.framePushed(), ABIStackAlignment) - masm.framePushed();
 
     // Actually emit code to start a new stack frame.
     masm.reserveStack(frameSize);
     masm.checkStackAlignment();
 
     // Check if we have space on the stack.
     Label stack_ok;
-    void *stack_limit = runtime->mainThread.addressofJitStackLimit();
+    void *stack_limit = &runtime->mainThread.jitStackLimit;
     masm.branchPtr(Assembler::Below, AbsoluteAddress(stack_limit), StackPointer, &stack_ok);
 
     // Exit with an exception. There is not enough space on the stack
     // for our working registers.
     masm.movePtr(ImmWord(RegExpRunStatus_Error), temp0);
     masm.jump(&return_temp0);
 
     masm.bind(&stack_ok);
@@ -497,17 +497,17 @@ NativeRegExpMacroAssembler::AdvanceRegis
 void
 NativeRegExpMacroAssembler::Backtrack()
 {
     JitSpew(SPEW_PREFIX "Backtrack");
 
     // Check for an interrupt.
     Label noInterrupt;
     masm.branch32(Assembler::Equal,
-                  AbsoluteAddress(runtime->addressOfInterruptUint32()), Imm32(0),
+                  AbsoluteAddress(&runtime->interrupt), Imm32(0),
                   &noInterrupt);
     masm.movePtr(ImmWord(RegExpRunStatus_Error), temp0);
     masm.jump(&exit_label_);
     masm.bind(&noInterrupt);
 
     // Pop code location from backtrack stack and jump to location.
     PopBacktrack(temp0);
     masm.jump(temp0);
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -491,17 +491,17 @@ BaselineCompiler::emitIC(ICStub *stub, I
 typedef bool (*CheckOverRecursedWithExtraFn)(JSContext *, BaselineFrame *, uint32_t, uint32_t);
 static const VMFunction CheckOverRecursedWithExtraInfo =
     FunctionInfo<CheckOverRecursedWithExtraFn>(CheckOverRecursedWithExtra);
 
 bool
 BaselineCompiler::emitStackCheck(bool earlyCheck)
 {
     Label skipCall;
-    void *limitAddr = cx->runtime()->mainThread.addressofJitStackLimit();
+    uintptr_t *limitAddr = &cx->runtime()->mainThread.jitStackLimit;
     uint32_t slotsSize = script->nslots() * sizeof(Value);
     uint32_t tolerance = earlyCheck ? slotsSize : 0;
 
     masm.movePtr(BaselineStackReg, R1.scratchReg());
 
     // If this is the early stack check, locals haven't been pushed yet.  Adjust the
     // stack pointer to account for the locals that would be pushed before performing
     // the guard around the vmcall to the stack check.
@@ -641,17 +641,17 @@ typedef bool (*InterruptCheckFn)(JSConte
 static const VMFunction InterruptCheckInfo = FunctionInfo<InterruptCheckFn>(InterruptCheck);
 
 bool
 BaselineCompiler::emitInterruptCheck()
 {
     frame.syncStack(0);
 
     Label done;
-    void *interrupt = cx->runtimeAddressOfInterruptUint32();
+    void *interrupt = (void *)&cx->runtime()->interrupt;
     masm.branch32(Assembler::Equal, AbsoluteAddress(interrupt), Imm32(0), &done);
 
     prepareVMCall();
     if (!callVM(InterruptCheckInfo))
         return false;
 
     masm.bind(&done);
     return true;
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -3742,17 +3742,17 @@ CodeGenerator::visitCheckOverRecursedPar
     // on interrupt or abort, only the stack limit for the main thread
     // is reset, not the worker threads.  See comment in vm/ForkJoin.h
     // for more details.
 
     Register cxReg = ToRegister(lir->forkJoinContext());
     Register tempReg = ToRegister(lir->getTempReg());
 
     masm.loadPtr(Address(cxReg, offsetof(ForkJoinContext, perThreadData)), tempReg);
-    masm.loadPtr(Address(tempReg, PerThreadData::offsetOfJitStackLimit()), tempReg);
+    masm.loadPtr(Address(tempReg, offsetof(PerThreadData, jitStackLimit)), tempReg);
 
     // Conditional forward (unlikely) branch to failure.
     CheckOverRecursedFailure *ool = new(alloc()) CheckOverRecursedFailure(lir);
     if (!addOutOfLineCode(ool, lir->mir()))
         return false;
 
     masm.branchPtr(Assembler::BelowOrEqual, StackPointer, tempReg, ool->entry());
     masm.checkInterruptFlagPar(tempReg, ool->entry());
@@ -9784,28 +9784,28 @@ CodeGenerator::visitAssertRangeV(LAssert
 
 bool
 CodeGenerator::visitInterruptCheck(LInterruptCheck *lir)
 {
     OutOfLineCode *ool = oolCallVM(InterruptCheckInfo, lir, (ArgList()), StoreNothing());
     if (!ool)
         return false;
 
-    AbsoluteAddress interruptAddr(GetIonContext()->runtime->addressOfInterruptUint32());
+    AbsoluteAddress interruptAddr(GetIonContext()->runtime->addressOfInterrupt());
     masm.branch32(Assembler::NotEqual, interruptAddr, Imm32(0), ool->entry());
     masm.bind(ool->rejoin());
     return true;
 }
 
 bool
 CodeGenerator::visitAsmJSInterruptCheck(LAsmJSInterruptCheck *lir)
 {
     Register scratch = ToRegister(lir->scratch());
-    masm.movePtr(AsmJSImmPtr(AsmJSImm_RuntimeInterruptUint32), scratch);
-    masm.load32(Address(scratch, 0), scratch);
+    masm.movePtr(AsmJSImmPtr(AsmJSImm_RuntimeInterrupt), scratch);
+    masm.load8ZeroExtend(Address(scratch, 0), scratch);
     Label rejoin;
     masm.branch32(Assembler::Equal, scratch, Imm32(0), &rejoin);
     {
         uint32_t stackFixup = ComputeByteAlignment(masm.framePushed() + sizeof(AsmJSFrame),
                                                    ABIStackAlignment);
         masm.reserveStack(stackFixup);
         masm.call(lir->funcDesc(), lir->interruptExit());
         masm.freeStack(stackFixup);
--- a/js/src/jit/CompileWrappers.cpp
+++ b/js/src/jit/CompileWrappers.cpp
@@ -38,17 +38,17 @@ const void *
 CompileRuntime::addressOfJitTop()
 {
     return &runtime()->mainThread.jitTop;
 }
 
 const void *
 CompileRuntime::addressOfJitStackLimit()
 {
-    return runtime()->mainThread.addressofJitStackLimit();
+    return &runtime()->mainThread.jitStackLimit;
 }
 
 const void *
 CompileRuntime::addressOfJSContext()
 {
     return &runtime()->mainThread.jitJSContext;
 }
 
@@ -68,25 +68,25 @@ CompileRuntime::addressOfLastCachedNativ
 const void *
 CompileRuntime::addressOfGCZeal()
 {
     return runtime()->gc.addressOfZealMode();
 }
 #endif
 
 const void *
-CompileRuntime::addressOfInterruptUint32()
+CompileRuntime::addressOfInterrupt()
 {
-    return runtime()->addressOfInterruptUint32();
+    return &runtime()->interrupt;
 }
 
 const void *
-CompileRuntime::addressOfInterruptParUint32()
+CompileRuntime::addressOfInterruptPar()
 {
-    return runtime()->addressOfInterruptParUint32();
+    return &runtime()->interruptPar;
 }
 
 const void *
 CompileRuntime::addressOfThreadPool()
 {
     return &runtime()->threadPool;
 }
 
--- a/js/src/jit/CompileWrappers.h
+++ b/js/src/jit/CompileWrappers.h
@@ -45,18 +45,18 @@ class CompileRuntime
 
     // &GetIonContext()->runtime->nativeIterCache.last
     const void *addressOfLastCachedNativeIterator();
 
 #ifdef JS_GC_ZEAL
     const void *addressOfGCZeal();
 #endif
 
-    const void *addressOfInterruptUint32();
-    const void *addressOfInterruptParUint32();
+    const void *addressOfInterrupt();
+    const void *addressOfInterruptPar();
 
     const void *addressOfThreadPool();
 
     const JitRuntime *jitRuntime();
 
     // Compilation does not occur off thread when the SPS profiler is enabled.
     SPSProfiler &spsProfiler();
 
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -418,17 +418,17 @@ JitRuntime::ensureIonCodeAccessible(JSRu
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
 #endif
 
     if (ionCodeProtected_) {
         ionAlloc_->toggleAllCodeAsAccessible(true);
         ionCodeProtected_ = false;
     }
 
-    if (rt->hasPendingInterrupt()) {
+    if (rt->interrupt) {
         // The interrupt handler needs to be invoked by this thread, but we may
         // be inside a signal handler and have no idea what is above us on the
         // stack (probably we are executing Ion code at an arbitrary point, but
         // we could be elsewhere, say repatching a jump for an IonCache).
         // Patch all backedges in the runtime so they will invoke the interrupt
         // handler the next time they execute.
         patchIonBackedges(rt, BackedgeInterruptCheck);
     }
@@ -1152,17 +1152,17 @@ IonScript::copyPatchableBackedges(JSCont
         CodeLocationLabel loopHeader(code, CodeOffsetLabel(loopHeaderOffset));
         CodeLocationLabel interruptCheck(code, CodeOffsetLabel(interruptCheckOffset));
         new(patchableBackedge) PatchableBackedge(backedge, loopHeader, interruptCheck);
 
         // Point the backedge to either of its possible targets, according to
         // whether an interrupt is currently desired, matching the targets
         // established by ensureIonCodeAccessible() above. We don't handle the
         // interrupt immediately as the interrupt lock is held here.
-        if (cx->runtime()->hasPendingInterrupt())
+        if (cx->runtime()->interrupt)
             PatchBackedge(backedge, interruptCheck, JitRuntime::BackedgeInterruptCheck);
         else
             PatchBackedge(backedge, loopHeader, JitRuntime::BackedgeLoopHeader);
 
         cx->runtime()->jitRuntime()->addPatchableBackedge(patchableBackedge);
     }
 }
 
--- a/js/src/jit/IonMacroAssembler.cpp
+++ b/js/src/jit/IonMacroAssembler.cpp
@@ -1163,17 +1163,17 @@ MacroAssembler::loadStringChar(Register 
     load8ZeroExtend(BaseIndex(output, index, TimesOne), output);
 
     bind(&done);
 }
 
 void
 MacroAssembler::checkInterruptFlagPar(Register tempReg, Label *fail)
 {
-    movePtr(ImmPtr(GetIonContext()->runtime->addressOfInterruptParUint32()), tempReg);
+    movePtr(ImmPtr(GetIonContext()->runtime->addressOfInterruptPar()), tempReg);
     branch32(Assembler::NonZero, Address(tempReg, 0), Imm32(0), fail);
 }
 
 // Save an exit frame (which must be aligned to the stack pointer) to
 // PerThreadData::jitTop of the main thread.
 void
 MacroAssembler::linkExitFrame()
 {
--- a/js/src/jit/ParallelFunctions.cpp
+++ b/js/src/jit/ParallelFunctions.cpp
@@ -142,17 +142,17 @@ jit::CheckOverRecursedPar(ForkJoinContex
 
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
     if (Simulator::Current()->overRecursed()) {
         cx->bailoutRecord->joinCause(ParallelBailoutOverRecursed);
         return false;
     }
 #endif
 
-    if (!JS_CHECK_STACK_SIZE(cx->perThreadData->jitStackLimit(), &stackDummy_)) {
+    if (!JS_CHECK_STACK_SIZE(cx->perThreadData->jitStackLimit, &stackDummy_)) {
         cx->bailoutRecord->joinCause(ParallelBailoutOverRecursed);
         return false;
     }
 
     return InterruptCheckPar(cx);
 }
 
 bool
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -108,27 +108,38 @@ JSObject *
 NewGCObject(JSContext *cx, gc::AllocKind allocKind, gc::InitialHeap initialHeap)
 {
     return js::NewGCObject<CanGC>(cx, allocKind, 0, initialHeap);
 }
 
 bool
 CheckOverRecursed(JSContext *cx)
 {
-    // We just failed the jitStackLimit check. There are two possible reasons:
-    //  - jitStackLimit was the real stack limit and we're over-recursed
-    //  - jitStackLimit was set to UINTPTR_MAX by JSRuntime::requestInterrupt
-    //    and we need to call JSRuntime::handleInterrupt.
+    // IonMonkey's stackLimit is equal to nativeStackLimit by default. When we
+    // request an interrupt, we set the jitStackLimit to nullptr, which causes
+    // the stack limit check to fail.
+    //
+    // There are two states we're concerned about here:
+    //   (1) The interrupt bit is set, and we need to fire the interrupt callback.
+    //   (2) The stack limit has been exceeded, and we need to throw an error.
+    //
+    // Note that we can reach here if jitStackLimit is MAXADDR, but interrupt
+    // has not yet been set to 1. That's okay; it will be set to 1 very shortly,
+    // and in the interim we might just fire a few useless calls to
+    // CheckOverRecursed.
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
     JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, 0, return false);
 #else
     JS_CHECK_RECURSION(cx, return false);
 #endif
-    gc::MaybeVerifyBarriers(cx);
-    return cx->runtime()->handleInterrupt(cx);
+
+    if (cx->runtime()->interrupt)
+        return InterruptCheck(cx);
+
+    return true;
 }
 
 // This function can get called in two contexts.  In the usual context, it's
 // called with ealyCheck=false, after the scope chain has been initialized on
 // a baseline frame.  In this case, it's ok to throw an exception, so a failed
 // stack check returns false, and a successful stack check promps a check for
 // an interrupt from the runtime, which may also cause a false return.
 //
@@ -162,18 +173,20 @@ CheckOverRecursedWithExtra(JSContext *cx
         return false;
 
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
     JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, return false);
 #else
     JS_CHECK_RECURSION_WITH_SP(cx, checkSp, return false);
 #endif
 
-    gc::MaybeVerifyBarriers(cx);
-    return cx->runtime()->handleInterrupt(cx);
+    if (cx->runtime()->interrupt)
+        return InterruptCheck(cx);
+
+    return true;
 }
 
 bool
 DefVarOrConst(JSContext *cx, HandlePropertyName dn, unsigned attrs, HandleObject scopeChain)
 {
     // Given the ScopeChain, extract the VarObj.
     RootedObject obj(cx, scopeChain);
     while (!obj->isQualifiedVarObj())
--- a/js/src/jit/shared/Assembler-shared.h
+++ b/js/src/jit/shared/Assembler-shared.h
@@ -778,17 +778,17 @@ enum AsmJSImmKind
     AsmJSImm_CeilF           = AsmJSExit::Builtin_CeilF,
     AsmJSImm_FloorD          = AsmJSExit::Builtin_FloorD,
     AsmJSImm_FloorF          = AsmJSExit::Builtin_FloorF,
     AsmJSImm_ExpD            = AsmJSExit::Builtin_ExpD,
     AsmJSImm_LogD            = AsmJSExit::Builtin_LogD,
     AsmJSImm_PowD            = AsmJSExit::Builtin_PowD,
     AsmJSImm_ATan2D          = AsmJSExit::Builtin_ATan2D,
     AsmJSImm_Runtime,
-    AsmJSImm_RuntimeInterruptUint32,
+    AsmJSImm_RuntimeInterrupt,
     AsmJSImm_StackLimit,
     AsmJSImm_ReportOverRecursed,
     AsmJSImm_OnDetached,
     AsmJSImm_HandleExecutionInterrupt,
     AsmJSImm_InvokeFromAsmJS_Ignore,
     AsmJSImm_InvokeFromAsmJS_ToInt32,
     AsmJSImm_InvokeFromAsmJS_ToNumber,
     AsmJSImm_CoerceInPlace_ToInt32,
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2024,58 +2024,78 @@ JS_IsExternalString(JSString *str)
 
 extern JS_PUBLIC_API(const JSStringFinalizer *)
 JS_GetExternalStringFinalizer(JSString *str)
 {
     return str->asExternal().externalFinalizer();
 }
 
 static void
-SetNativeStackQuotaAndLimit(JSRuntime *rt, StackKind kind, size_t stackSize)
+SetNativeStackQuota(JSRuntime *rt, StackKind kind, size_t stackSize)
 {
     rt->nativeStackQuota[kind] = stackSize;
-
+    if (rt->nativeStackBase)
+        RecomputeStackLimit(rt, kind);
+}
+
+void
+js::RecomputeStackLimit(JSRuntime *rt, StackKind kind)
+{
+    size_t stackSize = rt->nativeStackQuota[kind];
 #if JS_STACK_GROWTH_DIRECTION > 0
     if (stackSize == 0) {
         rt->mainThread.nativeStackLimit[kind] = UINTPTR_MAX;
     } else {
         MOZ_ASSERT(rt->nativeStackBase <= size_t(-1) - stackSize);
-        rt->mainThread.nativeStackLimit[kind] = rt->nativeStackBase + stackSize - 1;
+        rt->mainThread.nativeStackLimit[kind] =
+          rt->nativeStackBase + stackSize - 1;
     }
 #else
     if (stackSize == 0) {
         rt->mainThread.nativeStackLimit[kind] = 0;
     } else {
         MOZ_ASSERT(rt->nativeStackBase >= stackSize);
-        rt->mainThread.nativeStackLimit[kind] = rt->nativeStackBase - (stackSize - 1);
+        rt->mainThread.nativeStackLimit[kind] =
+          rt->nativeStackBase - (stackSize - 1);
     }
 #endif
+
+    // If there's no pending interrupt request set on the runtime's main thread's
+    // jitStackLimit, then update it so that it reflects the new nativeStacklimit.
+    //
+    // Note that, for now, we use the untrusted limit for ion. This is fine,
+    // because it's the most conservative limit, and if we hit it, we'll bail
+    // out of ion into the interpeter, which will do a proper recursion check.
+    if (kind == StackForUntrustedScript) {
+        JSRuntime::AutoLockForInterrupt lock(rt);
+        if (rt->mainThread.jitStackLimit != uintptr_t(-1)) {
+            rt->mainThread.jitStackLimit = rt->mainThread.nativeStackLimit[kind];
+#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
+            rt->mainThread.jitStackLimit = jit::Simulator::StackLimit();
+#endif
+        }
+    }
 }
 
 JS_PUBLIC_API(void)
-JS_SetNativeStackQuota(JSRuntime *rt, size_t systemCodeStackSize, size_t trustedScriptStackSize,
+JS_SetNativeStackQuota(JSRuntime *rt, size_t systemCodeStackSize,
+                       size_t trustedScriptStackSize,
                        size_t untrustedScriptStackSize)
 {
-    MOZ_ASSERT(rt->requestDepth == 0);
-
+    MOZ_ASSERT_IF(trustedScriptStackSize,
+                  trustedScriptStackSize < systemCodeStackSize);
     if (!trustedScriptStackSize)
         trustedScriptStackSize = systemCodeStackSize;
-    else
-        MOZ_ASSERT(trustedScriptStackSize < systemCodeStackSize);
-
+    MOZ_ASSERT_IF(untrustedScriptStackSize,
+                  untrustedScriptStackSize < trustedScriptStackSize);
     if (!untrustedScriptStackSize)
         untrustedScriptStackSize = trustedScriptStackSize;
-    else
-        MOZ_ASSERT(untrustedScriptStackSize < trustedScriptStackSize);
-
-    SetNativeStackQuotaAndLimit(rt, StackForSystemCode, systemCodeStackSize);
-    SetNativeStackQuotaAndLimit(rt, StackForTrustedScript, trustedScriptStackSize);
-    SetNativeStackQuotaAndLimit(rt, StackForUntrustedScript, untrustedScriptStackSize);
-
-    rt->mainThread.initJitStackLimit();
+    SetNativeStackQuota(rt, StackForSystemCode, systemCodeStackSize);
+    SetNativeStackQuota(rt, StackForTrustedScript, trustedScriptStackSize);
+    SetNativeStackQuota(rt, StackForUntrustedScript, untrustedScriptStackSize);
 }
 
 /************************************************************************/
 
 JS_PUBLIC_API(int)
 JS_IdArrayLength(JSContext *cx, JSIdArray *ida)
 {
     return ida->length;
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2267,19 +2267,16 @@ JS_GetExternalStringFinalizer(JSString *
  * behalf of such script), trusted script (as determined by JS_SetTrustedPrincipals),
  * and untrusted script. Each kind of code may have a different stack quota,
  * allowing embedders to keep higher-priority machinery running in the face of
  * scripted stack exhaustion by something else.
  *
  * The stack quotas for each kind of code should be monotonically descending,
  * and may be specified with this function. If 0 is passed for a given kind
  * of code, it defaults to the value of the next-highest-priority kind.
- *
- * This function may only be called immediately after the runtime is initialized
- * and before any code is executed and/or interrupts requested.
  */
 extern JS_PUBLIC_API(void)
 JS_SetNativeStackQuota(JSRuntime *cx, size_t systemCodeStackSize,
                        size_t trustedScriptStackSize = 0,
                        size_t untrustedScriptStackSize = 0);
 
 /************************************************************************/
 
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -36,16 +36,17 @@
 #include "jsscript.h"
 #include "jsstr.h"
 #include "jstypes.h"
 #include "jswatchpoint.h"
 
 #include "gc/Marking.h"
 #include "jit/Ion.h"
 #include "js/CharacterEncoding.h"
+#include "vm/Debugger.h"
 #include "vm/HelperThreads.h"
 #include "vm/Shape.h"
 
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/Stack-inl.h"
 
@@ -965,16 +966,100 @@ const JSErrorFormatString js_ErrorFormat
 JS_FRIEND_API(const JSErrorFormatString *)
 js_GetErrorMessage(void *userRef, const unsigned errorNumber)
 {
     if (errorNumber > 0 && errorNumber < JSErr_Limit)
         return &js_ErrorFormatString[errorNumber];
     return nullptr;
 }
 
+bool
+js::InvokeInterruptCallback(JSContext *cx)
+{
+    MOZ_ASSERT(cx->runtime()->requestDepth >= 1);
+
+    JSRuntime *rt = cx->runtime();
+    MOZ_ASSERT(rt->interrupt);
+
+    // Reset the callback counter first, then run GC and yield. If another
+    // thread is racing us here we will accumulate another callback request
+    // which will be serviced at the next opportunity.
+    rt->interrupt = false;
+
+    // IonMonkey sets its stack limit to UINTPTR_MAX to trigger interrupt
+    // callbacks.
+    rt->resetJitStackLimit();
+
+    cx->gcIfNeeded();
+
+    rt->interruptPar = false;
+
+    // A worker thread may have requested an interrupt after finishing an Ion
+    // compilation.
+    jit::AttachFinishedCompilations(cx);
+
+    // Important: Additional callbacks can occur inside the callback handler
+    // if it re-enters the JS engine. The embedding must ensure that the
+    // callback is disconnected before attempting such re-entry.
+    JSInterruptCallback cb = cx->runtime()->interruptCallback;
+    if (!cb)
+        return true;
+
+    if (cb(cx)) {
+        // Debugger treats invoking the interrupt callback as a "step", so
+        // invoke the onStep handler.
+        if (cx->compartment()->debugMode()) {
+            ScriptFrameIter iter(cx);
+            if (iter.script()->stepModeEnabled()) {
+                RootedValue rval(cx);
+                switch (Debugger::onSingleStep(cx, &rval)) {
+                  case JSTRAP_ERROR:
+                    return false;
+                  case JSTRAP_CONTINUE:
+                    return true;
+                  case JSTRAP_RETURN:
+                    // See note in Debugger::propagateForcedReturn.
+                    Debugger::propagateForcedReturn(cx, iter.abstractFramePtr(), rval);
+                    return false;
+                  case JSTRAP_THROW:
+                    cx->setPendingException(rval);
+                    return false;
+                  default:;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    // No need to set aside any pending exception here: ComputeStackString
+    // already does that.
+    JSString *stack = ComputeStackString(cx);
+    JSFlatString *flat = stack ? stack->ensureFlat(cx) : nullptr;
+
+    const char16_t *chars;
+    AutoStableStringChars stableChars(cx);
+    if (flat && stableChars.initTwoByte(cx, flat))
+        chars = stableChars.twoByteRange().start().get();
+    else
+        chars = MOZ_UTF16("(stack not available)");
+    JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_WARNING, js_GetErrorMessage, nullptr,
+                                   JSMSG_TERMINATED, chars);
+
+    return false;
+}
+
+bool
+js::HandleExecutionInterrupt(JSContext *cx)
+{
+    if (cx->runtime()->interrupt)
+        return InvokeInterruptCallback(cx);
+    return true;
+}
+
 ThreadSafeContext::ThreadSafeContext(JSRuntime *rt, PerThreadData *pt, ContextKind kind)
   : ContextFriendFields(rt),
     contextKind_(kind),
     perThreadData(pt),
     allocator_(nullptr)
 {
 }
 
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -284,17 +284,17 @@ struct ThreadSafeContext : ContextFriend
     JSAtomState &names() { return *runtime_->commonNames; }
     StaticStrings &staticStrings() { return *runtime_->staticStrings; }
     AtomSet &permanentAtoms() { return *runtime_->permanentAtoms; }
     WellKnownSymbols &wellKnownSymbols() { return *runtime_->wellKnownSymbols; }
     const JS::AsmJSCacheOps &asmJSCacheOps() { return runtime_->asmJSCacheOps; }
     PropertyName *emptyString() { return runtime_->emptyString; }
     FreeOp *defaultFreeOp() { return runtime_->defaultFreeOp(); }
     void *runtimeAddressForJit() { return runtime_; }
-    void *runtimeAddressOfInterruptUint32() { return runtime_->addressOfInterruptUint32(); }
+    void *runtimeAddressOfInterrupt() { return &runtime_->interrupt; }
     void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; }
     void *stackLimitAddressForJitCode(StackKind kind);
     size_t gcSystemPageSize() { return gc::SystemPageSize(); }
     bool signalHandlersInstalled() const { return runtime_->signalHandlersInstalled(); }
     bool canUseSignalHandlers() const { return runtime_->canUseSignalHandlers(); }
     bool jitSupportsFloatingPoint() const { return runtime_->jitSupportsFloatingPoint; }
     bool jitSupportsSimd() const { return runtime_->jitSupportsSimd; }
 
@@ -777,25 +777,43 @@ js_ReportValueErrorFlags(JSContext *cx, 
 #define js_ReportValueError3(cx,errorNumber,spindex,v,fallback,arg1,arg2)     \
     ((void)js_ReportValueErrorFlags(cx, JSREPORT_ERROR, errorNumber,          \
                                     spindex, v, fallback, arg1, arg2))
 
 extern const JSErrorFormatString js_ErrorFormatString[JSErr_Limit];
 
 namespace js {
 
+/*
+ * Invoke the interrupt callback and return false if the current execution
+ * is to be terminated.
+ */
+bool
+InvokeInterruptCallback(JSContext *cx);
+
+bool
+HandleExecutionInterrupt(JSContext *cx);
+
+/*
+ * Process any pending interrupt requests. Long-running inner loops in C++ must
+ * call this periodically to make sure they are interruptible --- that is, to
+ * make sure they do not prevent the slow script dialog from appearing.
+ *
+ * This can run a full GC or call the interrupt callback, which could do
+ * anything. In the browser, it displays the slow script dialog.
+ *
+ * If this returns true, the caller can continue; if false, the caller must
+ * break out of its loop. This happens if, for example, the user clicks "Stop
+ * script" on the slow script dialog; treat it as an uncatchable error.
+ */
 MOZ_ALWAYS_INLINE bool
 CheckForInterrupt(JSContext *cx)
 {
-    // Add an inline fast-path since we have to check for interrupts in some hot
-    // C++ loops of library builtins.
-    JSRuntime *rt = cx->runtime();
-    if (rt->hasPendingInterrupt())
-        return rt->handleInterrupt(cx);
-    return true;
+    MOZ_ASSERT(cx->runtime()->requestDepth >= 1);
+    return !cx->runtime()->interrupt || InvokeInterruptCallback(cx);
 }
 
 /************************************************************************/
 
 class AutoStringVector : public AutoVectorRooter<JSString *>
 {
   public:
     explicit AutoStringVector(JSContext *cx
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -521,17 +521,17 @@ CheckAllocatorState(ThreadSafeContext *c
     }
 
     if (allowGC) {
 #ifdef JS_GC_ZEAL
         if (rt->gc.needZealousGC())
             rt->gc.runDebugGC();
 #endif
 
-        if (rt->hasPendingInterrupt()) {
+        if (rt->interrupt) {
             // Invoking the interrupt callback can fail and we can't usefully
             // handle that here. Just check in case we need to collect instead.
             ncx->gcIfNeeded();
         }
     }
 
     return true;
 }
--- a/js/src/jsnativestack.h
+++ b/js/src/jsnativestack.h
@@ -13,16 +13,15 @@ namespace js {
 
 extern void *
 GetNativeStackBaseImpl();
 
 inline uintptr_t
 GetNativeStackBase()
 {
     uintptr_t stackBase = reinterpret_cast<uintptr_t>(GetNativeStackBaseImpl());
-    MOZ_ASSERT(stackBase != 0);
     MOZ_ASSERT(stackBase % sizeof(void *) == 0);
     return stackBase;
 }
 
 } /* namespace js */
 
 #endif /* jsnativestack_h */
--- a/js/src/vm/ForkJoin.cpp
+++ b/js/src/vm/ForkJoin.cpp
@@ -1435,17 +1435,17 @@ ForkJoinShared::~ForkJoinShared()
 }
 
 ParallelResult
 ForkJoinShared::execute()
 {
     // Sometimes a GC request occurs *just before* we enter into the
     // parallel section.  Rather than enter into the parallel section
     // and then abort, we just check here and abort early.
-    if (cx_->runtime()->hasPendingInterruptPar())
+    if (cx_->runtime()->interruptPar)
         return TP_RETRY_SEQUENTIALLY;
 
     AutoLockMonitor lock(*this);
 
     ParallelResult jobResult = TP_SUCCESS;
     {
         AutoUnlockMonitor unlock(*this);
 
@@ -1513,17 +1513,17 @@ ForkJoinShared::executeFromWorker(Thread
     TlsPerThreadData.set(&thisThread);
 
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
     stackLimit = Simulator::StackLimit();
 #endif
 
     // Don't use setIonStackLimit() because that acquires the ionStackLimitLock, and the
     // lock has not been initialized in these cases.
-    thisThread.initJitStackLimitPar(stackLimit);
+    thisThread.jitStackLimit = stackLimit;
     executePortion(&thisThread, worker);
     TlsPerThreadData.set(nullptr);
 
     return !abort_;
 }
 
 bool
 ForkJoinShared::executeFromMainThread(ThreadPoolWorker *worker)
@@ -1546,17 +1546,17 @@ ForkJoinShared::executeFromMainThread(Th
     // In turn, the reason that it is okay for runtime->interrupt to be
     // set and for us to still continue PJS execution is because PJS, being
     // unable to use the signal-based interrupt handling like sequential JIT
     // code, keeps a separate flag, interruptPar, to filter out interrupts
     // which should not interrupt JIT code.
     //
     // Thus, use GetNativeStackLimit instead of just propagating the
     // main thread's.
-    thisThread.initJitStackLimitPar(GetNativeStackLimit(cx_));
+    thisThread.jitStackLimit = GetNativeStackLimit(cx_);
     executePortion(&thisThread, worker);
     TlsPerThreadData.set(oldData);
 
     return !abort_;
 }
 
 void
 ForkJoinShared::executePortion(PerThreadData *perThread, ThreadPoolWorker *worker)
@@ -1642,17 +1642,17 @@ ForkJoinShared::executePortion(PerThread
     }
 
     Spew(SpewOps, "Down");
 }
 
 void
 ForkJoinShared::setAbortFlagDueToInterrupt(ForkJoinContext &cx)
 {
-    MOZ_ASSERT(cx_->runtime()->hasPendingInterruptPar());
+    MOZ_ASSERT(cx_->runtime()->interruptPar);
     // The GC Needed flag should not be set during parallel
     // execution.  Instead, one of the requestGC() or
     // requestZoneGC() methods should be invoked.
     MOZ_ASSERT(!cx_->runtime()->gc.isGcNeeded());
 
     if (!abort_) {
         cx.bailoutRecord->joinCause(ParallelBailoutInterrupt);
         setAbortFlagAndRequestInterrupt(false);
@@ -1821,17 +1821,17 @@ bool
 ForkJoinContext::hasAcquiredJSContext() const
 {
     return acquiredJSContext_;
 }
 
 bool
 ForkJoinContext::check()
 {
-    if (runtime()->hasPendingInterruptPar()) {
+    if (runtime()->interruptPar) {
         shared_->setAbortFlagDueToInterrupt(*this);
         return false;
     }
     return true;
 }
 
 void
 ForkJoinContext::requestGC(JS::gcreason::Reason reason)
@@ -2268,16 +2268,23 @@ js::ParallelTestsShouldPass(JSContext *c
 {
     return IsIonEnabled(cx) &&
            IsBaselineEnabled(cx) &&
            !js_JitOptions.eagerCompilation &&
            js_JitOptions.baselineWarmUpThreshold != 0 &&
            cx->runtime()->gcZeal() == 0;
 }
 
+void
+js::RequestInterruptForForkJoin(JSRuntime *rt, JSRuntime::InterruptMode mode)
+{
+    if (mode != JSRuntime::RequestInterruptAnyThreadDontStopIon)
+        rt->interruptPar = true;
+}
+
 bool
 js::intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp)
 {
     // This version of SetForkJoinTargetRegion is called during
     // sequential execution. It is a no-op. The parallel version
     // is intrinsic_SetForkJoinTargetRegionPar(), below.
     return true;
 }
--- a/js/src/vm/ForkJoin.h
+++ b/js/src/vm/ForkJoin.h
@@ -541,16 +541,18 @@ class LockedJSContext
     operator JSContext *() { return jscx_; }
     JSContext *operator->() { return jscx_; }
 };
 
 bool InExclusiveParallelSection();
 
 bool ParallelTestsShouldPass(JSContext *cx);
 
+void RequestInterruptForForkJoin(JSRuntime *rt, JSRuntime::InterruptMode mode);
+
 bool intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp);
 extern const JSJitInfo intrinsic_SetForkJoinTargetRegionInfo;
 
 bool intrinsic_ClearThreadLocalArenas(JSContext *cx, unsigned argc, Value *vp);
 extern const JSJitInfo intrinsic_ClearThreadLocalArenasInfo;
 
 ///////////////////////////////////////////////////////////////////////////
 // Debug Spew
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -616,18 +616,24 @@ RegExpShared::execute(JSContext *cx, Han
         }
 
         if (result == RegExpRunStatus_Error) {
             // The RegExp engine might exit with an exception if an interrupt
             // was requested. If this happens, break out and retry the regexp
             // in the bytecode interpreter, which can execute while tolerating
             // future interrupts. Otherwise, if we keep getting interrupted we
             // will never finish executing the regexp.
-            if (cx->runtime()->hasPendingInterrupt()) {
-                if (!cx->runtime()->handleInterrupt(cx))
+            bool interrupted;
+            {
+                JSRuntime::AutoLockForInterrupt lock(cx->runtime());
+                interrupted = cx->runtime()->interrupt;
+            }
+
+            if (interrupted) {
+                if (!InvokeInterruptCallback(cx))
                     return RegExpRunStatus_Error;
                 break;
             }
 
             js_ReportOverRecursed(cx);
             return RegExpRunStatus_Error;
         }
 
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -31,17 +31,16 @@
 
 #include "asmjs/AsmJSSignalHandlers.h"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/JitCompartment.h"
 #include "jit/mips/Simulator-mips.h"
 #include "jit/PcScriptCache.h"
 #include "js/MemoryMetrics.h"
 #include "js/SliceBudget.h"
-#include "vm/Debugger.h"
 
 #include "jscntxtinlines.h"
 #include "jsgcinlines.h"
 
 using namespace js;
 using namespace js::gc;
 
 using mozilla::Atomic;
@@ -69,17 +68,17 @@ js::DisableExtraThreads()
 
 const JSSecurityCallbacks js::NullSecurityCallbacks = { };
 
 PerThreadData::PerThreadData(JSRuntime *runtime)
   : PerThreadDataFriendFields(),
     runtime_(runtime),
     jitTop(nullptr),
     jitJSContext(nullptr),
-    jitStackLimit_(0xbad),
+    jitStackLimit(0),
 #ifdef JS_TRACE_LOGGING
     traceLogger(nullptr),
 #endif
     activation_(nullptr),
     profilingActivation_(nullptr),
     asmJSActivationStack_(nullptr),
     autoFlushICache_(nullptr),
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
@@ -131,18 +130,18 @@ ReturnZeroSize(const void *p)
 JSRuntime::JSRuntime(JSRuntime *parentRuntime)
   : JS::shadow::Runtime(
 #ifdef JSGC_GENERATIONAL
         &gc.storeBuffer
 #endif
     ),
     mainThread(this),
     parentRuntime(parentRuntime),
-    interrupt_(false),
-    interruptPar_(false),
+    interrupt(false),
+    interruptPar(false),
     handlingSignal(false),
     interruptCallback(nullptr),
     interruptLock(nullptr),
     interruptLockOwner(nullptr),
     exclusiveAccessLock(nullptr),
     exclusiveAccessOwner(nullptr),
     mainThreadHasExclusiveAccess(false),
     numExclusiveThreads(0),
@@ -152,17 +151,17 @@ JSRuntime::JSRuntime(JSRuntime *parentRu
     defaultVersion_(JSVERSION_DEFAULT),
     futexAPI_(nullptr),
     ownerThread_(nullptr),
     tempLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     freeLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     execAlloc_(nullptr),
     jitRuntime_(nullptr),
     selfHostingGlobal_(nullptr),
-    nativeStackBase(GetNativeStackBase()),
+    nativeStackBase(0),
     cxCallback(nullptr),
     destroyCompartmentCallback(nullptr),
     destroyZoneCallback(nullptr),
     sweepZoneCallback(nullptr),
     compartmentNameCallback(nullptr),
     activityCallback(nullptr),
     activityCallbackArg(nullptr),
     requestDepth(0),
@@ -318,16 +317,18 @@ JSRuntime::init(uint32_t maxbytes, uint3
     dateTimeInfo.updateTimeZoneAdjustment();
 
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
     simulatorRuntime_ = js::jit::CreateSimulatorRuntime();
     if (!simulatorRuntime_)
         return false;
 #endif
 
+    nativeStackBase = GetNativeStackBase();
+
     jitSupportsFloatingPoint = js::jit::JitSupportsFloatingPoint();
     jitSupportsSimd = js::jit::JitSupportsSimd();
 
     signalHandlersInstalled_ = EnsureAsmJSSignalHandlersInstalled(this);
     canUseSignalHandlers_ = signalHandlersInstalled_ && !SignalBasedTriggersDisabled();
 
     if (!spsProfiler.init())
         return false;
@@ -460,16 +461,27 @@ NewObjectCache::clearNurseryObjects(JSRu
         {
             PodZero(&e);
         }
     }
 #endif
 }
 
 void
+JSRuntime::resetJitStackLimit()
+{
+    AutoLockForInterrupt lock(this);
+    mainThread.setJitStackLimit(mainThread.nativeStackLimit[js::StackForUntrustedScript]);
+
+#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
+    mainThread.setJitStackLimit(js::jit::Simulator::StackLimit());
+#endif
+}
+
+void
 JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes *rtSizes)
 {
     // Several tables in the runtime enumerated below can be used off thread.
     AutoLockForExclusiveAccess lock(this);
 
     rtSizes->object += mallocSizeOf(this);
 
     rtSizes->atomsTable += atoms().sizeOfIncludingThis(mallocSizeOf);
@@ -513,130 +525,43 @@ JSRuntime::addSizeOfIncludingThis(mozill
 #ifdef JSGC_GENERATIONAL
     rtSizes->gc.nurseryCommitted += gc.nursery.sizeOfHeapCommitted();
     rtSizes->gc.nurseryDecommitted += gc.nursery.sizeOfHeapDecommitted();
     rtSizes->gc.nurseryHugeSlots += gc.nursery.sizeOfHugeSlots(mallocSizeOf);
     gc.storeBuffer.addSizeOfExcludingThis(mallocSizeOf, &rtSizes->gc);
 #endif
 }
 
-static bool
-InvokeInterruptCallback(JSContext *cx)
-{
-    MOZ_ASSERT(cx->runtime()->requestDepth >= 1);
-
-    cx->gcIfNeeded();
-
-    // A worker thread may have requested an interrupt after finishing an Ion
-    // compilation.
-    jit::AttachFinishedCompilations(cx);
-
-    // Important: Additional callbacks can occur inside the callback handler
-    // if it re-enters the JS engine. The embedding must ensure that the
-    // callback is disconnected before attempting such re-entry.
-    JSInterruptCallback cb = cx->runtime()->interruptCallback;
-    if (!cb)
-        return true;
-
-    if (cb(cx)) {
-        // Debugger treats invoking the interrupt callback as a "step", so
-        // invoke the onStep handler.
-        if (cx->compartment()->debugMode()) {
-            ScriptFrameIter iter(cx);
-            if (iter.script()->stepModeEnabled()) {
-                RootedValue rval(cx);
-                switch (Debugger::onSingleStep(cx, &rval)) {
-                  case JSTRAP_ERROR:
-                    return false;
-                  case JSTRAP_CONTINUE:
-                    return true;
-                  case JSTRAP_RETURN:
-                    // See note in Debugger::propagateForcedReturn.
-                    Debugger::propagateForcedReturn(cx, iter.abstractFramePtr(), rval);
-                    return false;
-                  case JSTRAP_THROW:
-                    cx->setPendingException(rval);
-                    return false;
-                  default:;
-                }
-            }
-        }
-
-        return true;
-    }
-
-    // No need to set aside any pending exception here: ComputeStackString
-    // already does that.
-    JSString *stack = ComputeStackString(cx);
-    JSFlatString *flat = stack ? stack->ensureFlat(cx) : nullptr;
-
-    const char16_t *chars;
-    AutoStableStringChars stableChars(cx);
-    if (flat && stableChars.initTwoByte(cx, flat))
-        chars = stableChars.twoByteRange().start().get();
-    else
-        chars = MOZ_UTF16("(stack not available)");
-    JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_WARNING, js_GetErrorMessage, nullptr,
-                                   JSMSG_TERMINATED, chars);
-
-    return false;
-}
-
-void
-PerThreadData::resetJitStackLimit()
-{
-    // Note that, for now, we use the untrusted limit for ion. This is fine,
-    // because it's the most conservative limit, and if we hit it, we'll bail
-    // out of ion into the interpeter, which will do a proper recursion check.
-#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
-    jitStackLimit_ = jit::Simulator::StackLimit();
-#else
-    jitStackLimit_ = nativeStackLimit[StackForUntrustedScript];
-#endif
-}
-
-void
-PerThreadData::initJitStackLimit()
-{
-    resetJitStackLimit();
-}
-
-void
-PerThreadData::initJitStackLimitPar(uintptr_t limit)
-{
-    jitStackLimit_ = limit;
-}
-
 void
 JSRuntime::requestInterrupt(InterruptMode mode)
 {
-    interrupt_ = true;
-    interruptPar_ = true;
-    mainThread.jitStackLimit_ = UINTPTR_MAX;
+    AutoLockForInterrupt lock(this);
+
+    /*
+     * Invalidate ionTop to trigger its over-recursion check. Note this must be
+     * set before interrupt, to avoid racing with js::InvokeInterruptCallback,
+     * into a weird state where interrupt is stuck at 0 but jitStackLimit is
+     * MAXADDR.
+     */
+    mainThread.setJitStackLimit(-1);
 
+    interrupt = true;
+
+    RequestInterruptForForkJoin(this, mode);
+
+    /*
+     * asm.js and normal Ion code optionally use memory protection and signal
+     * handlers to halt running code.
+     */
     if (canUseSignalHandlers()) {
-        AutoLockForInterrupt lock(this);
         RequestInterruptForAsmJSCode(this, mode);
         jit::RequestInterruptForIonCode(this, mode);
     }
 }
 
-bool
-JSRuntime::handleInterrupt(JSContext *cx)
-{
-    MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
-    if (interrupt_ || mainThread.jitStackLimit_ == UINTPTR_MAX) {
-        interrupt_ = false;
-        interruptPar_ = false;
-        mainThread.resetJitStackLimit();
-        return InvokeInterruptCallback(cx);
-    }
-    return true;
-}
-
 jit::ExecutableAllocator *
 JSRuntime::createExecutableAllocator(JSContext *cx)
 {
     MOZ_ASSERT(!execAlloc_);
     MOZ_ASSERT(cx->runtime() == this);
 
     execAlloc_ = js_new<jit::ExecutableAllocator>();
     if (!execAlloc_)
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -514,30 +514,23 @@ class PerThreadData : public PerThreadDa
 
     /*
      * The current JSContext when entering JIT code. This field may only be used
      * from JIT code and C++ directly called by JIT code (otherwise it may refer
      * to the wrong JSContext).
      */
     JSContext           *jitJSContext;
 
-    /* See comment for JSRuntime::interrupt_. */
-  private:
-    mozilla::Atomic<uintptr_t, mozilla::Relaxed> jitStackLimit_;
-    void resetJitStackLimit();
-    friend struct ::JSRuntime;
-  public:
-    void initJitStackLimit();
-    void initJitStackLimitPar(uintptr_t limit);
+    /*
+     * The stack limit checked by JIT code. This stack limit may be temporarily
+     * set to null to force JIT code to exit (e.g., for the operation callback).
+     */
+    uintptr_t            jitStackLimit;
 
-    uintptr_t jitStackLimit() const { return jitStackLimit_; }
-
-    // For read-only JIT use:
-    void *addressofJitStackLimit() { return &jitStackLimit_; }
-    static size_t offsetOfJitStackLimit() { return offsetof(PerThreadData, jitStackLimit_); }
+    inline void setJitStackLimit(uintptr_t limit);
 
     // Information about the heap allocated backtrack stack used by RegExp JIT code.
     irregexp::RegExpStack regexpStack;
 
 #ifdef JS_TRACE_LOGGING
     TraceLogger         *traceLogger;
 #endif
 
@@ -680,16 +673,18 @@ class PerThreadData : public PerThreadDa
     void setSimulator(js::jit::Simulator *sim);
     js::jit::SimulatorRuntime *simulatorRuntime() const;
     uintptr_t *addressOfSimulatorStackLimit();
 #endif
 };
 
 class AutoLockForExclusiveAccess;
 
+void RecomputeStackLimit(JSRuntime *rt, StackKind kind);
+
 } // namespace js
 
 struct JSRuntime : public JS::shadow::Runtime,
                    public js::MallocProvider<JSRuntime>
 {
     /*
      * Per-thread data for the main thread that is associated with
      * this JSRuntime, as opposed to any worker threads used in
@@ -703,66 +698,28 @@ struct JSRuntime : public JS::shadow::Ru
     js::PerThreadData mainThread;
 
     /*
      * If non-null, another runtime guaranteed to outlive this one and whose
      * permanent data may be used by this one where possible.
      */
     JSRuntime *parentRuntime;
 
-  private:
-    mozilla::Atomic<uint32_t, mozilla::Relaxed> interrupt_;
-    mozilla::Atomic<uint32_t, mozilla::Relaxed> interruptPar_;
-  public:
-
-    enum InterruptMode {
-        RequestInterruptMainThread,
-        RequestInterruptAnyThread,
-        RequestInterruptAnyThreadDontStopIon,
-        RequestInterruptAnyThreadForkJoin
-    };
+    /*
+     * If true, we've been asked to call the interrupt callback as soon as
+     * possible.
+     */
+    mozilla::Atomic<bool, mozilla::Relaxed> interrupt;
 
-    // Any thread can call requestInterrupt() to request that the main JS thread
-    // stop running and call the interrupt callback (allowing the interrupt
-    // callback to halt execution). To stop the main JS thread, requestInterrupt
-    // sets two fields: interrupt_ (set to true) and jitStackLimit_ (set to
-    // UINTPTR_MAX). The JS engine must continually poll one of these fields
-    // and call handleInterrupt if either field has the interrupt value. (The
-    // point of setting jitStackLimit_ to UINTPTR_MAX is that JIT code already
-    // needs to guard on jitStackLimit_ in every function prologue to avoid
-    // stack overflow, so we avoid a second branch on interrupt_ by setting
-    // jitStackLimit_ to a value that is guaranteed to fail the guard.)
-    //
-    // Note that the writes to interrupt_ and jitStackLimit_ use a Relaxed
-    // Atomic so, while the writes are guaranteed to eventually be visible to
-    // the main thread, it can happen in any order. handleInterrupt calls the
-    // interrupt callback if either is set, so it really doesn't matter as long
-    // as the JS engine is continually polling at least one field. In corner
-    // cases, this relaxed ordering could lead to an interrupt handler being
-    // called twice in succession after a single requestInterrupt call, but
-    // that's fine.
-    void requestInterrupt(InterruptMode mode);
-    bool handleInterrupt(JSContext *cx);
-
-    MOZ_ALWAYS_INLINE bool hasPendingInterrupt() const {
-        return interrupt_;
-    }
-    MOZ_ALWAYS_INLINE bool hasPendingInterruptPar() const {
-        return interruptPar_;
-    }
-
-    // For read-only JIT use:
-    void *addressOfInterruptUint32() {
-        static_assert(sizeof(interrupt_) == sizeof(uint32_t), "Assumed by JIT callers");
-        return &interrupt_;
-    }
-    void *addressOfInterruptParUint32() {
-        static_assert(sizeof(interruptPar_) == sizeof(uint32_t), "Assumed by JIT callers");
-        return &interruptPar_;
-    }
+    /*
+     * If non-zero, ForkJoin should service an interrupt. This is a separate
+     * flag from |interrupt| because we cannot use the mprotect trick with PJS
+     * code and ignore the TriggerCallbackAnyThreadDontStopIon trigger.
+     */
+    mozilla::Atomic<bool, mozilla::Relaxed> interruptPar;
 
     /* Set when handling a signal for a thread associated with this runtime. */
     bool handlingSignal;
 
     JSInterruptCallback interruptCallback;
 
 #ifdef DEBUG
     void assertCanLock(js::RuntimeLock which);
@@ -944,17 +901,17 @@ struct JSRuntime : public JS::shadow::Ru
 
     /* Gets current default locale. String remains owned by context. */
     const char *getDefaultLocale();
 
     JSVersion defaultVersion() { return defaultVersion_; }
     void setDefaultVersion(JSVersion v) { defaultVersion_ = v; }
 
     /* Base address of the native stack for the current thread. */
-    const uintptr_t     nativeStackBase;
+    uintptr_t           nativeStackBase;
 
     /* The native stack size limit that runtime should not exceed. */
     size_t              nativeStackQuota[js::StackKindCount];
 
     /* Context create/destroy callback. */
     JSContextCallback   cxCallback;
     void               *cxCallbackData;
 
@@ -1310,16 +1267,20 @@ struct JSRuntime : public JS::shadow::Ru
     js::ScriptDataTable &scriptDataTable() {
         MOZ_ASSERT(currentThreadHasExclusiveAccess());
         return scriptDataTable_;
     }
 
     bool                jitSupportsFloatingPoint;
     bool                jitSupportsSimd;
 
+    // Used to reset stack limit after a signaled interrupt (i.e. jitStackLimit_ = -1)
+    // has been noticed by Ion/Baseline.
+    void resetJitStackLimit();
+
     // Cache for jit::GetPcScript().
     js::jit::PcScriptCache *ionPcScriptCache;
 
     js::ThreadPool threadPool;
 
     js::DefaultJSContextCallback defaultJSContextCallback;
 
     js::CTypesActivityCallback  ctypesActivityCallback;
@@ -1370,16 +1331,27 @@ struct JSRuntime : public JS::shadow::Ru
      * The function must be called outside the GC lock.
      */
     JS_FRIEND_API(void *) onOutOfMemory(void *p, size_t nbytes);
     JS_FRIEND_API(void *) onOutOfMemory(void *p, size_t nbytes, JSContext *cx);
 
     /*  onOutOfMemory but can call the largeAllocationFailureCallback. */
     JS_FRIEND_API(void *) onOutOfMemoryCanGC(void *p, size_t bytes);
 
+    // Ways in which the interrupt callback on the runtime can be triggered,
+    // varying based on which thread is triggering the callback.
+    enum InterruptMode {
+        RequestInterruptMainThread,
+        RequestInterruptAnyThread,
+        RequestInterruptAnyThreadDontStopIon,
+        RequestInterruptAnyThreadForkJoin
+    };
+
+    void requestInterrupt(InterruptMode mode);
+
     void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes *runtime);
 
   private:
     JS::RuntimeOptions options_;
 
     // Settings for how helper threads can be used.
     bool offthreadIonCompilationEnabled_;
     bool parallelParsingEnabled_;
@@ -1593,16 +1565,23 @@ class MOZ_STACK_CLASS AutoKeepAtoms
     ~AutoKeepAtoms() {
         if (JSRuntime *rt = pt->runtimeIfOnOwnerThread()) {
             MOZ_ASSERT(rt->keepAtoms_);
             rt->keepAtoms_--;
         }
     }
 };
 
+inline void
+PerThreadData::setJitStackLimit(uintptr_t limit)
+{
+    MOZ_ASSERT(runtime_->currentThreadOwnsInterruptLock());
+    jitStackLimit = limit;
+}
+
 inline JSRuntime *
 PerThreadData::runtimeFromMainThread()
 {
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
     return runtime_;
 }
 
 inline JSRuntime *