Bug 909542 - refactor CameraControl API, r=dhylands,jst,jesup,onecyrenus
☠☠ backed out by b47b10e405a5 ☠ ☠
authorMike Habicher <mikeh@mozilla.com>
Fri, 14 Feb 2014 00:28:57 -0500
changeset 169300 6e588c56764b60b96ed24af813b955db02a3f936
parent 169299 9b164ae482fe69e3d914a54af1bbbddc809e8ee1
child 169301 4e167aeb19fa608af1561bc4dcd7456e952f37c8
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersdhylands, jst, jesup, onecyrenus
bugs909542
milestone30.0a1
Bug 909542 - refactor CameraControl API, r=dhylands,jst,jesup,onecyrenus
b2g/installer/package-manifest.in
browser/installer/package-manifest.in
content/media/webrtc/MediaEngineWebRTC.cpp
content/media/webrtc/MediaEngineWebRTC.h
content/media/webrtc/MediaEngineWebRTCVideo.cpp
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/bindings/Bindings.conf
dom/camera/AutoRwLock.h
dom/camera/CameraCommon.h
dom/camera/CameraControlImpl.cpp
dom/camera/CameraControlImpl.h
dom/camera/CameraControlListener.h
dom/camera/CameraRecorderProfiles.cpp
dom/camera/CameraRecorderProfiles.h
dom/camera/DOMCameraCapabilities.cpp
dom/camera/DOMCameraCapabilities.h
dom/camera/DOMCameraControl.cpp
dom/camera/DOMCameraControl.h
dom/camera/DOMCameraControlListener.cpp
dom/camera/DOMCameraControlListener.h
dom/camera/DOMCameraManager.cpp
dom/camera/DOMCameraManager.h
dom/camera/DOMCameraPreview.cpp
dom/camera/DOMCameraPreview.h
dom/camera/FallbackCameraCapabilities.cpp
dom/camera/FallbackCameraControl.cpp
dom/camera/FallbackCameraManager.cpp
dom/camera/GonkCameraControl.cpp
dom/camera/GonkCameraControl.h
dom/camera/GonkCameraHwMgr.cpp
dom/camera/GonkCameraHwMgr.h
dom/camera/GonkCameraManager.cpp
dom/camera/GonkCameraParameters.cpp
dom/camera/GonkCameraParameters.h
dom/camera/GonkCameraSource.cpp
dom/camera/GonkRecorderProfiles.cpp
dom/camera/GonkRecorderProfiles.h
dom/camera/ICameraControl.h
dom/camera/moz.build
dom/camera/nsIDOMCameraManager.idl
dom/camera/test/mochitest.ini
dom/camera/test/test_camera.html
dom/camera/test/test_camera_2.html
dom/media/MediaManager.cpp
dom/webidl/CameraCapabilities.webidl
dom/webidl/CameraControl.webidl
dom/webidl/CameraManager.webidl
dom/webidl/moz.build
js/xpconnect/src/dictionary_helper_gen.conf
mobile/android/installer/package-manifest.in
testing/mochitest/b2g-debug.json
widget/gonk/nativewindow/GonkNativeWindowICS.cpp
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -162,17 +162,16 @@
 @BINPATH@/components/dom_icc.xpt
 @BINPATH@/components/dom_cellbroadcast.xpt
 @BINPATH@/components/dom_wappush.xpt
 @BINPATH@/components/dom_mobileconnection.xpt
 #endif
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #endif
-@BINPATH@/components/dom_camera.xpt
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_contacts.xpt
 @BINPATH@/components/dom_alarm.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_devicestorage.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_file.xpt
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -192,17 +192,16 @@
 @BINPATH@/components/dom_messages.xpt
 #endif
 @BINPATH@/components/dom_apps.xpt
 @BINPATH@/components/dom_base.xpt
 @BINPATH@/components/dom_system.xpt
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #endif
-@BINPATH@/components/dom_camera.xpt
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_alarm.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_devicestorage.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_file.xpt
 @BINPATH@/components/dom_geolocation.xpt
--- a/content/media/webrtc/MediaEngineWebRTC.cpp
+++ b/content/media/webrtc/MediaEngineWebRTC.cpp
@@ -37,76 +37,75 @@ GetUserMediaLog()
 #include "AndroidJNIWrapper.h"
 #include "AndroidBridge.h"
 #endif
 
 #undef LOG
 #define LOG(args) PR_LOG(GetUserMediaLog(), PR_LOG_DEBUG, args)
 
 namespace mozilla {
-#ifndef MOZ_B2G_CAMERA
+
 MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs &aPrefs)
   : mMutex("mozilla::MediaEngineWebRTC")
   , mVideoEngine(nullptr)
   , mVoiceEngine(nullptr)
   , mVideoEngineInit(false)
   , mAudioEngineInit(false)
   , mHasTabVideoSource(false)
 {
+#ifndef MOZ_B2G_CAMERA
   nsCOMPtr<nsIComponentRegistrar> compMgr;
   NS_GetComponentRegistrar(getter_AddRefs(compMgr));
   if (compMgr) {
     compMgr->IsContractIDRegistered(NS_TABSOURCESERVICE_CONTRACTID, &mHasTabVideoSource);
   }
+#else
+  AsyncLatencyLogger::Get()->AddRef();
+#endif
   if (aPrefs.mLoadAdapt) {
       mLoadMonitor = new LoadMonitor();
       mLoadMonitor->Init(mLoadMonitor);
   }
 }
-#endif
-
 
 void
 MediaEngineWebRTC::EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSource> >* aVSources)
 {
 #ifdef MOZ_B2G_CAMERA
   MutexAutoLock lock(mMutex);
-  if (!mCameraManager) {
-    return;
-  }
 
   /**
    * We still enumerate every time, in case a new device was plugged in since
    * the last call. TODO: Verify that WebRTC actually does deal with hotplugging
    * new devices (with or without new engine creation) and accordingly adjust.
    * Enumeration is not neccessary if GIPS reports the same set of devices
    * for a given instance of the engine. Likewise, if a device was plugged out,
    * mVideoSources must be updated.
    */
   int num = 0;
   nsresult result;
-  result = mCameraManager->GetNumberOfCameras(num);
+  result = ICameraControl::GetNumberOfCameras(num);
   if (num <= 0 || result != NS_OK) {
     return;
   }
 
   for (int i = 0; i < num; i++) {
     nsCString cameraName;
-    result = mCameraManager->GetCameraName(i, cameraName);
+    result = ICameraControl::GetCameraName(i, cameraName);
     if (result != NS_OK) {
       continue;
     }
 
     nsRefPtr<MediaEngineWebRTCVideoSource> vSource;
     NS_ConvertUTF8toUTF16 uuid(cameraName);
     if (mVideoSources.Get(uuid, getter_AddRefs(vSource))) {
       // We've already seen this device, just append.
       aVSources->AppendElement(vSource.get());
     } else {
-      vSource = new MediaEngineWebRTCVideoSource(mCameraManager, i, mWindowId);
+      vSource = new MediaEngineWebRTCVideoSource(i);
       mVideoSources.Put(uuid, vSource); // Hashtable takes ownership.
       aVSources->AppendElement(vSource);
     }
   }
 
   return;
 #else
   webrtc::ViEBase* ptrViEBase;
--- a/content/media/webrtc/MediaEngineWebRTC.h
+++ b/content/media/webrtc/MediaEngineWebRTC.h
@@ -41,19 +41,18 @@
 #include "webrtc/voice_engine/include/voe_audio_processing.h"
 
 // Video Engine
 #include "webrtc/video_engine/include/vie_base.h"
 #include "webrtc/video_engine/include/vie_codec.h"
 #include "webrtc/video_engine/include/vie_render.h"
 #include "webrtc/video_engine/include/vie_capture.h"
 #ifdef MOZ_B2G_CAMERA
-#include "CameraPreviewMediaStream.h"
-#include "DOMCameraManager.h"
-#include "GonkCameraControl.h"
+#include "CameraControlListener.h"
+#include "ICameraControl.h"
 #include "ImageContainer.h"
 #include "nsGlobalWindow.h"
 #include "prprf.h"
 #endif
 
 #include "NullTransport.h"
 
 namespace mozilla {
@@ -68,55 +67,45 @@ class GetCameraNameRunnable;
  *
  * On B2G platform, member data may accessed from different thread after construction:
  *
  * MediaThread:
  *   mState, mImage, mWidth, mHeight, mCapability, mPrefs, mDeviceName, mUniqueId, mInitDone,
  *   mSources, mImageContainer, mSources, mState, mImage, mLastCapture
  *
  * MainThread:
- *   mDOMCameraControl, mCaptureIndex, mCameraThread, mWindowId, mCameraManager,
+ *   mCaptureIndex, mWindowId,
  *   mNativeCameraControl, mPreviewStream, mState, mLastCapture, mWidth, mHeight
  *
  * Where mWidth, mHeight, mImage are protected by mMonitor
  *       mState, mLastCapture is protected by mCallbackMonitor
  * Other variable is accessed only from single thread
  */
 class MediaEngineWebRTCVideoSource : public MediaEngineVideoSource
                                    , public nsRunnable
 #ifdef MOZ_B2G_CAMERA
-                                   , public nsICameraGetCameraCallback
-                                   , public nsICameraPreviewStreamCallback
-                                   , public nsICameraTakePictureCallback
-                                   , public nsICameraReleaseCallback
-                                   , public nsICameraErrorCallback
-                                   , public CameraPreviewFrameCallback
+                                   , public CameraControlListener
 #else
                                    , public webrtc::ExternalRenderer
 #endif
 {
 public:
 #ifdef MOZ_B2G_CAMERA
-  MediaEngineWebRTCVideoSource(nsDOMCameraManager* aCameraManager,
-    int aIndex, uint64_t aWindowId)
-    : mCameraManager(aCameraManager)
-    , mNativeCameraControl(nullptr)
-    , mPreviewStream(nullptr)
-    , mWindowId(aWindowId)
+  MediaEngineWebRTCVideoSource(int aIndex)
+    : mCameraControl(nullptr)
     , mCallbackMonitor("WebRTCCamera.CallbackMonitor")
     , mCaptureIndex(aIndex)
     , mMonitor("WebRTCCamera.Monitor")
     , mWidth(0)
     , mHeight(0)
     , mInitDone(false)
     , mInSnapshotMode(false)
     , mSnapshotPath(nullptr)
   {
     mState = kReleased;
-    NS_NewNamedThread("CameraThread", getter_AddRefs(mCameraThread));
     Init();
   }
 #else
   // ViEExternalRenderer.
   virtual int FrameSizeChange(unsigned int, unsigned int, unsigned int);
   virtual int DeliverFrame(unsigned char*,int, uint32_t , int64_t,
                            void *handle);
   /**
@@ -162,30 +151,27 @@ public:
                           TrackTicks &aLastEndTime);
 
   virtual bool IsFake() {
     return false;
   }
 
   NS_DECL_THREADSAFE_ISUPPORTS
 #ifdef MOZ_B2G_CAMERA
-  NS_DECL_NSICAMERAGETCAMERACALLBACK
-  NS_DECL_NSICAMERAPREVIEWSTREAMCALLBACK
-  NS_DECL_NSICAMERATAKEPICTURECALLBACK
-  NS_DECL_NSICAMERARELEASECALLBACK
-  NS_DECL_NSICAMERAERRORCALLBACK
+  void OnHardwareStateChange(HardwareState aState);
+  void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration);
+  bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);
+  void OnError(CameraErrorContext aContext, const nsACString& aError);
+  void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType);
 
   void AllocImpl();
   void DeallocImpl();
   void StartImpl(webrtc::CaptureCapability aCapability);
   void StopImpl();
   void SnapshotImpl();
-
-  virtual void OnNewFrame(const gfxIntSize& aIntrinsicSize, layers::Image* aImage);
-
 #endif
 
   // This runnable is for creating a temporary file on the main thread.
   NS_IMETHODIMP
   Run()
   {
     nsCOMPtr<nsIFile> tmp;
     nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmp));
@@ -207,30 +193,18 @@ private:
   static const unsigned int KMaxUniqueIdLength = 256;
 
   // Initialize the needed Video engine interfaces.
   void Init();
   void Shutdown();
 
   // Engine variables.
 #ifdef MOZ_B2G_CAMERA
-  // MediaEngine hold this DOM object, and the MediaEngine is hold by Navigator
-  // Their life time is always much longer than this object. Use a raw-pointer
-  // here should be safe.
-  // We need raw pointer here since such DOM-object should not addref/release on
-  // any thread other than main thread, but we must use this object for now. To
-  // avoid any bad thing do to addref/release DOM-object on other thread, we use
-  // raw-pointer for now.
-  nsDOMCameraManager* mCameraManager;
-  nsRefPtr<nsDOMCameraControl> mDOMCameraControl;
-  nsRefPtr<nsGonkCameraControl> mNativeCameraControl;
-  nsRefPtr<DOMCameraPreview> mPreviewStream;
-  uint64_t mWindowId;
+  nsRefPtr<ICameraControl> mCameraControl;
   mozilla::ReentrantMonitor mCallbackMonitor; // Monitor for camera callback handling
-  nsRefPtr<nsIThread> mCameraThread;
   nsRefPtr<nsIDOMFile> mLastCapture;
 #else
   webrtc::VideoEngine* mVideoEngine; // Weak reference, don't free.
   webrtc::ViEBase* mViEBase;
   webrtc::ViECapture* mViECapture;
   webrtc::ViERender* mViERender;
 #endif
   webrtc::CaptureCapability mCapability; // Doesn't work on OS X.
@@ -346,34 +320,17 @@ private:
   webrtc::NsModes  mNoiseSuppress;
 
   NullTransport *mNullTransport;
 };
 
 class MediaEngineWebRTC : public MediaEngine
 {
 public:
-#ifdef MOZ_B2G_CAMERA
-  MediaEngineWebRTC(nsDOMCameraManager* aCameraManager, uint64_t aWindowId)
-    : mMutex("mozilla::MediaEngineWebRTC")
-    , mVideoEngine(nullptr)
-    , mVoiceEngine(nullptr)
-    , mVideoEngineInit(false)
-    , mAudioEngineInit(false)
-    , mCameraManager(aCameraManager)
-    , mWindowId(aWindowId)
-    , mHasTabVideoSource(false)
-  {
-    AsyncLatencyLogger::Get(true)->AddRef();
-    mLoadMonitor = new LoadMonitor();
-    mLoadMonitor->Init(mLoadMonitor);
-  }
-#else
   MediaEngineWebRTC(MediaEnginePrefs &aPrefs);
-#endif
   ~MediaEngineWebRTC() {
     Shutdown();
 #ifdef MOZ_B2G_CAMERA
     AsyncLatencyLogger::Get()->Release();
 #endif
   }
 
   // Clients should ensure to clean-up sources video/audio sources
@@ -395,26 +352,14 @@ private:
   bool mAudioEngineInit;
   bool mHasTabVideoSource;
 
   // Store devices we've already seen in a hashtable for quick return.
   // Maps UUID to MediaEngineSource (one set for audio, one for video).
   nsRefPtrHashtable<nsStringHashKey, MediaEngineWebRTCVideoSource > mVideoSources;
   nsRefPtrHashtable<nsStringHashKey, MediaEngineWebRTCAudioSource > mAudioSources;
 
-#ifdef MOZ_B2G_CAMERA
-  // MediaEngine hold this DOM object, and the MediaEngine is hold by Navigator
-  // Their life time is always much longer than this object. Use a raw-pointer
-  // here should be safe.
-  // We need raw pointer here since such DOM-object should not addref/release on
-  // any thread other than main thread, but we must use this object for now. To
-  // avoid any bad thing do to addref/release DOM-object on other thread, we use
-  // raw-pointer for now.
-  nsDOMCameraManager* mCameraManager;
-  uint64_t mWindowId;
-#endif
-
-   nsRefPtr<LoadMonitor> mLoadMonitor;
+  nsRefPtr<LoadMonitor> mLoadMonitor;
 };
 
 }
 
 #endif /* NSMEDIAENGINEWEBRTC_H_ */
--- a/content/media/webrtc/MediaEngineWebRTCVideo.cpp
+++ b/content/media/webrtc/MediaEngineWebRTCVideo.cpp
@@ -307,17 +307,19 @@ MediaEngineWebRTCVideoSource::Deallocate
   }
   return NS_OK;
 }
 
 nsresult
 MediaEngineWebRTCVideoSource::Start(SourceMediaStream* aStream, TrackID aID)
 {
   LOG((__FUNCTION__));
+#ifndef MOZ_B2G_CAMERA
   int error = 0;
+#endif
   if (!mInitDone || !aStream) {
     return NS_ERROR_FAILURE;
   }
 
   mSources.AppendElement(aStream);
 
   aStream->AddTrack(aID, USECS_PER_S, 0, new VideoSegment());
   aStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
@@ -408,17 +410,17 @@ MediaEngineWebRTCVideoSource::Snapshot(u
  * constructor and destructor respectively.
  */
 
 void
 MediaEngineWebRTCVideoSource::Init()
 {
 #ifdef MOZ_B2G_CAMERA
   nsAutoCString deviceName;
-  mCameraManager->GetCameraName(mCaptureIndex, deviceName);
+  ICameraControl::GetCameraName(mCaptureIndex, deviceName);
   CopyUTF8toUTF16(deviceName, mDeviceName);
   CopyUTF8toUTF16(deviceName, mUniqueId);
 #else
   // fix compile warning for these being unused. (remove once used)
   (void) mFps;
   (void) mMinFps;
 
   LOG((__FUNCTION__));
@@ -487,131 +489,101 @@ MediaEngineWebRTCVideoSource::Shutdown()
 
 #ifdef MOZ_B2G_CAMERA
 
 // All these functions must be run on MainThread!
 void
 MediaEngineWebRTCVideoSource::AllocImpl() {
   MOZ_ASSERT(NS_IsMainThread());
 
-  mDOMCameraControl = new nsDOMCameraControl(mCaptureIndex,
-                                             mCameraThread,
-                                             this,
-                                             this,
-                                             nsGlobalWindow::GetInnerWindowWithId(mWindowId));
-  mCameraManager->Register(mDOMCameraControl);
+  mCameraControl = ICameraControl::Create(mCaptureIndex, nullptr);
+  mCameraControl->AddListener(this);
 }
 
 void
 MediaEngineWebRTCVideoSource::DeallocImpl() {
   MOZ_ASSERT(NS_IsMainThread());
 
-  mNativeCameraControl->ReleaseHardware(this, this);
-  mNativeCameraControl = nullptr;
+  mCameraControl->ReleaseHardware();
+  mCameraControl = nullptr;
 }
 
 void
 MediaEngineWebRTCVideoSource::StartImpl(webrtc::CaptureCapability aCapability) {
   MOZ_ASSERT(NS_IsMainThread());
 
-  idl::CameraSize size;
-  size.width = aCapability.width;
-  size.height = aCapability.height;
-  mNativeCameraControl->GetPreviewStream(size, this, this);
+  ICameraControl::Configuration config;
+  config.mMode = ICameraControl::kPictureMode;
+  config.mPreviewSize.width = aCapability.width;
+  config.mPreviewSize.height = aCapability.height;
+  mCameraControl->SetConfiguration(config);
+  mCameraControl->Set(CAMERA_PARAM_PICTURESIZE, config.mPreviewSize);
 }
 
 void
 MediaEngineWebRTCVideoSource::StopImpl() {
   MOZ_ASSERT(NS_IsMainThread());
 
-  mNativeCameraControl->StopPreview();
-  mPreviewStream = nullptr;
+  mCameraControl->StopPreview();
 }
 
 void
 MediaEngineWebRTCVideoSource::SnapshotImpl() {
-
   MOZ_ASSERT(NS_IsMainThread());
-
-  idl::CameraSize size;
-  size.width = mCapability.width;
-  size.height = mCapability.height;
-
-  idl::CameraPosition cameraPosition;
-  cameraPosition.latitude = NAN;
-  cameraPosition.longitude = NAN;
-  cameraPosition.altitude = NAN;
-  cameraPosition.timestamp = NAN;
-
-  mNativeCameraControl->TakePicture(size, 0, NS_LITERAL_STRING("jpeg"), cameraPosition, PR_Now() / 1000000, this, this);
+  mCameraControl->TakePicture();
 }
 
-// nsICameraGetCameraCallback
-nsresult
-MediaEngineWebRTCVideoSource::HandleEvent(nsISupports* /* unused */) {
-  MOZ_ASSERT(NS_IsMainThread());
+void
+MediaEngineWebRTCVideoSource::OnHardwareStateChange(HardwareState aState)
+{
   ReentrantMonitorAutoEnter sync(mCallbackMonitor);
-  mNativeCameraControl = static_cast<nsGonkCameraControl*>(mDOMCameraControl->GetNativeCameraControl().get());
-  mState = kAllocated;
+  if (aState == CameraControlListener::kHardwareOpen) {
+    mState = kAllocated;
+  } else {
+    mState = kReleased;
+    mCameraControl->RemoveListener(this);
+  }
   mCallbackMonitor.Notify();
-  return NS_OK;
 }
 
-// nsICameraPreviewStreamCallback
-nsresult
-MediaEngineWebRTCVideoSource::HandleEvent(nsIDOMMediaStream* stream) {
-  MOZ_ASSERT(NS_IsMainThread());
+void
+MediaEngineWebRTCVideoSource::OnConfigurationChange(const CameraListenerConfiguration& aConfiguration)
+{
   ReentrantMonitorAutoEnter sync(mCallbackMonitor);
-  mPreviewStream = static_cast<DOMCameraPreview*>(stream);
-  mPreviewStream->Start();
-  CameraPreviewMediaStream* cameraStream = static_cast<CameraPreviewMediaStream*>(mPreviewStream->GetStream());
-  cameraStream->SetFrameCallback(this);
   mState = kStarted;
   mCallbackMonitor.Notify();
-  return NS_OK;
 }
 
-// nsICameraTakePictureCallback
-nsresult
-MediaEngineWebRTCVideoSource::HandleEvent(nsIDOMBlob* picture) {
-  MOZ_ASSERT(NS_IsMainThread());
+void
+MediaEngineWebRTCVideoSource::OnError(CameraErrorContext aContext, const nsACString& aError)
+{
   ReentrantMonitorAutoEnter sync(mCallbackMonitor);
-  mLastCapture = static_cast<nsIDOMFile*>(picture);
   mCallbackMonitor.Notify();
-  return NS_OK;
-}
-
-// nsICameraReleaseCallback
-nsresult
-MediaEngineWebRTCVideoSource::HandleEvent() {
-  MOZ_ASSERT(NS_IsMainThread());
-  ReentrantMonitorAutoEnter sync(mCallbackMonitor);
-  mState = kReleased;
-  mCallbackMonitor.Notify();
-  return NS_OK;
 }
 
-// nsICameraErrorCallback
-nsresult
-MediaEngineWebRTCVideoSource::HandleEvent(const nsAString& error) {
-  MOZ_ASSERT(NS_IsMainThread());
+void
+MediaEngineWebRTCVideoSource::OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType)
+{
   ReentrantMonitorAutoEnter sync(mCallbackMonitor);
+  mLastCapture =
+    static_cast<nsIDOMFile*>(new nsDOMMemoryFile(static_cast<void*>(aData),
+                                                 static_cast<uint64_t>(aLength),
+                                                 aMimeType));
   mCallbackMonitor.Notify();
-  return NS_OK;
 }
 
-//Except this one. This callback should called on camera preview thread.
-void
-MediaEngineWebRTCVideoSource::OnNewFrame(const gfxIntSize& aIntrinsicSize, layers::Image* aImage) {
+bool
+MediaEngineWebRTCVideoSource::OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight) {
   MonitorAutoLock enter(mMonitor);
   if (mState == kStopped) {
-    return;
+    return false;
   }
   mImage = aImage;
-  if (mWidth != aIntrinsicSize.width || mHeight != aIntrinsicSize.height) {
-    mWidth = aIntrinsicSize.width;
-    mHeight = aIntrinsicSize.height;
+  if (mWidth != static_cast<int>(aWidth) || mHeight != static_cast<int>(aHeight)) {
+    mWidth = aWidth;
+    mHeight = aHeight;
     LOG(("Video FrameSizeChange: %ux%u", mWidth, mHeight));
   }
+  return true; // return true because we're accepting the frame
 }
 #endif
 
 }
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -155,17 +155,16 @@
 #include "nsIDOMIccManager.h"
 #include "nsIDOMMobileConnection.h"
 #endif // MOZ_B2G_RIL
 
 #ifdef MOZ_B2G_FM
 #include "FMRadio.h"
 #endif
 
-#include "nsIDOMCameraManager.h"
 #include "nsIDOMGlobalObjectConstructor.h"
 #include "nsIDOMLockedFile.h"
 #include "nsDebug.h"
 
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/Likely.h"
 #include "WindowNamedPropertiesHandler.h"
 #include "nsIInterfaceInfoManager.h"
@@ -465,19 +464,16 @@ static nsDOMClassInfoData sClassInfoData
   NS_DEFINE_CLASSINFO_DATA(CSSPageRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
 #ifdef MOZ_B2G_RIL
   NS_DEFINE_CLASSINFO_DATA(MozIccManager, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 #endif
 
-  NS_DEFINE_CLASSINFO_DATA(CameraCapabilities, nsDOMGenericSH,
-                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
-
   NS_DEFINE_CLASSINFO_DATA(LockedFile, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(CSSFontFeatureValuesRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(UserDataHandler, nsDOMGenericSH,
                                       DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(XPathNamespace, nsDOMGenericSH,
@@ -1171,20 +1167,16 @@ nsDOMClassInfo::Init()
 #ifdef MOZ_B2G_RIL
   DOM_CLASSINFO_MAP_BEGIN(MozIccManager, nsIDOMMozIccManager)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozIccManager)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
   DOM_CLASSINFO_MAP_END
 
 #endif
 
-  DOM_CLASSINFO_MAP_BEGIN(CameraCapabilities, nsICameraCapabilities)
-    DOM_CLASSINFO_MAP_ENTRY(nsICameraCapabilities)
-  DOM_CLASSINFO_MAP_END
-
   DOM_CLASSINFO_MAP_BEGIN(LockedFile, nsIDOMLockedFile)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMLockedFile)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(CSSFontFeatureValuesRule, nsIDOMCSSFontFeatureValuesRule)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSFontFeatureValuesRule)
   DOM_CLASSINFO_MAP_END
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -98,18 +98,16 @@ DOMCI_CLASS(MozCSSKeyframeRule)
 DOMCI_CLASS(MozCSSKeyframesRule)
 
 DOMCI_CLASS(CSSPageRule)
 
 #ifdef MOZ_B2G_RIL
 DOMCI_CLASS(MozIccManager)
 #endif
 
-DOMCI_CLASS(CameraCapabilities)
-
 DOMCI_CLASS(LockedFile)
 
 DOMCI_CLASS(CSSFontFeatureValuesRule)
 
 DOMCI_CLASS(UserDataHandler)
 DOMCI_CLASS(XPathNamespace)
 DOMCI_CLASS(XULControlElement)
 DOMCI_CLASS(XULLabeledControlElement)
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -175,16 +175,21 @@ DOMInterfaces = {
     'headerFile': 'BluetoothDevice.h'
 },
 
 'BluetoothManager': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothManager',
     'headerFile': 'BluetoothManager.h'
 },
 
+'CameraCapabilities': {
+    'nativeType': 'mozilla::dom::CameraCapabilities',
+    'headerFile': 'DOMCameraCapabilities.h'
+},
+
 'CameraControl': {
     'nativeType': 'mozilla::nsDOMCameraControl',
     'headerFile': 'DOMCameraControl.h',
     'binaryNames': {
         "release": "ReleaseHardware"
     }
 },
 
@@ -1957,20 +1962,8 @@ addExternalIface('SVGNumber')
 addExternalIface('URI', nativeType='nsIURI', headerFile='nsIURI.h',
                  notflattened=True)
 addExternalIface('UserDataHandler')
 addExternalIface('XPathResult', nativeType='nsISupports')
 addExternalIface('XPathExpression')
 addExternalIface('XPathNSResolver')
 addExternalIface('XULCommandDispatcher')
 addExternalIface('DataTransfer', notflattened=True)
-addExternalIface('GetCameraCallback', nativeType='nsICameraGetCameraCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraErrorCallback', nativeType='nsICameraErrorCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraCapabilities', nativeType='nsICameraCapabilities', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraAutoFocusCallback', nativeType='nsICameraAutoFocusCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraShutterCallback', nativeType='nsICameraShutterCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraClosedCallback', nativeType='nsICameraClosedCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraTakePictureCallback', nativeType='nsICameraTakePictureCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraReleaseCallback', nativeType='nsICameraReleaseCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraStartRecordingCallback', nativeType='nsICameraStartRecordingCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraPreviewStateChange', nativeType='nsICameraPreviewStateChange', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraPreviewStreamCallback', nativeType='nsICameraPreviewStreamCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraRecorderStateChange', nativeType='nsICameraRecorderStateChange', headerFile='nsIDOMCameraManager.h')
new file mode 100644
--- /dev/null
+++ b/dom/camera/AutoRwLock.h
@@ -0,0 +1,49 @@
+/* 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 RWLOCK_AUTO_ENTER_H
+#define RWLOCK_AUTO_ENTER_H
+
+#include "prrwlock.h"
+#include "mozilla/Assertions.h"
+
+class RwLockAutoEnterRead
+{
+public:
+  RwLockAutoEnterRead(PRRWLock* aRwLock)
+    : mRwLock(aRwLock)
+  {
+    MOZ_ASSERT(mRwLock);
+    PR_RWLock_Rlock(mRwLock);
+  }
+
+  ~RwLockAutoEnterRead()
+  {
+    PR_RWLock_Unlock(mRwLock);
+  }
+
+protected:
+  PRRWLock* mRwLock;
+};
+
+class RwLockAutoEnterWrite
+{
+public:
+  RwLockAutoEnterWrite(PRRWLock* aRwLock)
+    : mRwLock(aRwLock)
+  {
+    MOZ_ASSERT(mRwLock);
+    PR_RWLock_Wlock(mRwLock);
+  }
+
+  ~RwLockAutoEnterWrite()
+  {
+    PR_RWLock_Unlock(mRwLock);
+  }
+
+protected:
+  PRRWLock* mRwLock;
+};
+
+#endif // RWLOCK_AUTO_ENTER_H
--- a/dom/camera/CameraCommon.h
+++ b/dom/camera/CameraCommon.h
@@ -14,17 +14,16 @@
 #define __func__ __FILE__
 #endif
 #endif
 
 #ifndef NAN
 #define NAN std::numeric_limits<double>::quiet_NaN()
 #endif
 
-#include "nsIDOMCameraManager.h"
 #include "prlog.h"
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* GetCameraLog();
 #define DOM_CAMERA_LOG( type, ... ) PR_LOG(GetCameraLog(), (PRLogModuleLevel)type, ( __VA_ARGS__ ))
 #else
 #define DOM_CAMERA_LOG( type, ... )
 #endif
@@ -57,54 +56,16 @@ enum {
 #else
 #define DOM_CAMERA_LOGR( ... )
 #endif
 #define DOM_CAMERA_LOGT( ... )      DOM_CAMERA_LOG( DOM_CAMERA_LOG_TRACE, __VA_ARGS__ )
 #define DOM_CAMERA_LOGI( ... )      DOM_CAMERA_LOG( DOM_CAMERA_LOG_INFO, __VA_ARGS__ )
 #define DOM_CAMERA_LOGW( ... )      DOM_CAMERA_LOG( DOM_CAMERA_LOG_WARNING, __VA_ARGS__ )
 #define DOM_CAMERA_LOGE( ... )      DOM_CAMERA_LOG( DOM_CAMERA_LOG_ERROR, __VA_ARGS__ )
 
-enum {
-  CAMERA_PARAM_EFFECT,
-  CAMERA_PARAM_WHITEBALANCE,
-  CAMERA_PARAM_SCENEMODE,
-  CAMERA_PARAM_FLASHMODE,
-  CAMERA_PARAM_FOCUSMODE,
-  CAMERA_PARAM_ZOOM,
-  CAMERA_PARAM_METERINGAREAS,
-  CAMERA_PARAM_FOCUSAREAS,
-  CAMERA_PARAM_FOCALLENGTH,
-  CAMERA_PARAM_FOCUSDISTANCENEAR,
-  CAMERA_PARAM_FOCUSDISTANCEOPTIMUM,
-  CAMERA_PARAM_FOCUSDISTANCEFAR,
-  CAMERA_PARAM_EXPOSURECOMPENSATION,
-  CAMERA_PARAM_PICTURESIZE,
-  CAMERA_PARAM_THUMBNAILSIZE,
-  CAMERA_PARAM_THUMBNAILQUALITY,
-  CAMERA_PARAM_SENSORANGLE,
-
-  CAMERA_PARAM_SUPPORTED_PREVIEWSIZES,
-  CAMERA_PARAM_SUPPORTED_VIDEOSIZES,
-  CAMERA_PARAM_SUPPORTED_PICTURESIZES,
-  CAMERA_PARAM_SUPPORTED_PICTUREFORMATS,
-  CAMERA_PARAM_SUPPORTED_WHITEBALANCES,
-  CAMERA_PARAM_SUPPORTED_SCENEMODES,
-  CAMERA_PARAM_SUPPORTED_EFFECTS,
-  CAMERA_PARAM_SUPPORTED_FLASHMODES,
-  CAMERA_PARAM_SUPPORTED_FOCUSMODES,
-  CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS,
-  CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS,
-  CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION,
-  CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION,
-  CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP,
-  CAMERA_PARAM_SUPPORTED_ZOOM,
-  CAMERA_PARAM_SUPPORTED_ZOOMRATIOS,
-  CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES
-};
-
 #ifdef PR_LOGGING
 
 static inline void nsLogAddRefCamera(const char *file, uint32_t line, void* p, uint32_t count, const char *clazz, uint32_t size)
 {
   if (count == 1) {
     DOM_CAMERA_LOGR("++++++++++++++++++++++++++++++++++++++++");
   }
   DOM_CAMERA_LOGR("%s:%d : CAMREF-ADD(%s): this=%p, mRefCnt=%d\n", file, line, clazz, p, count);
--- a/dom/camera/CameraControlImpl.cpp
+++ b/dom/camera/CameraControlImpl.cpp
@@ -1,523 +1,576 @@
 /* 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 "CameraControlImpl.h"
 #include "base/basictypes.h"
 #include "mozilla/Assertions.h"
-#include "DOMCameraPreview.h"
+#include "mozilla/unused.h"
+#include "nsIWeakReferenceUtils.h"
 #include "CameraRecorderProfiles.h"
-#include "CameraControlImpl.h"
 #include "CameraCommon.h"
 #include "nsGlobalWindow.h"
 #include "DeviceStorageFileDescriptor.h"
+#include "CameraControlListener.h"
 
 using namespace mozilla;
-using namespace mozilla::dom;
-using namespace mozilla::idl;
 
-CameraControlImpl::CameraControlImpl(uint32_t aCameraId, nsIThread* aCameraThread, uint64_t aWindowId)
+nsWeakPtr CameraControlImpl::sCameraThread;
+
+CameraControlImpl::CameraControlImpl(uint32_t aCameraId)
   : mCameraId(aCameraId)
-  , mCameraThread(aCameraThread)
-  , mWindowId(aWindowId)
-  , mFileFormat()
-  , mMaxMeteringAreas(0)
-  , mMaxFocusAreas(0)
-  , mPreviewState(PREVIEW_STOPPED)
-  , mDOMPreview(nullptr)
-  , mAutoFocusOnSuccessCb(nullptr)
-  , mAutoFocusOnErrorCb(nullptr)
-  , mTakePictureOnSuccessCb(nullptr)
-  , mTakePictureOnErrorCb(nullptr)
-  , mOnShutterCb(nullptr)
-  , mOnClosedCb(nullptr)
-  , mOnRecorderStateChangeCb(nullptr)
-  , mOnPreviewStateChangeCb(nullptr)
+  , mPreviewState(CameraControlListener::kPreviewStopped)
+  , mHardwareState(CameraControlListener::kHardwareClosed)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+
+  // reuse the same camera thread to conserve resources
+  nsCOMPtr<nsIThread> ct = do_QueryReferent(sCameraThread);
+  if (ct) {
+    mCameraThread = ct.forget();
+  } else {
+    nsresult rv = NS_NewNamedThread("CameraThread", getter_AddRefs(mCameraThread));
+    unused << rv; // swallow rv to suppress a compiler warning when the macro
+                  // is #defined to nothing (i.e. in non-DEBUG builds).
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    // keep a weak reference to the new thread
+    sCameraThread = do_GetWeakReference(mCameraThread);
+  }
+
+  mListenerLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "CameraControlImpl.Listeners.Lock");
 }
 
 CameraControlImpl::~CameraControlImpl()
 {
-  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-}
-
-// Helpers for string properties.
-nsresult
-CameraControlImpl::Set(uint32_t aKey, const nsAString& aValue)
-{
-  SetParameter(aKey, NS_ConvertUTF16toUTF8(aValue).get());
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(uint32_t aKey, nsAString& aValue)
-{
-  const char* value = GetParameterConstChar(aKey);
-  if (!value) {
-    return NS_ERROR_FAILURE;
-  }
-
-  aValue.AssignASCII(value);
-  return NS_OK;
-}
-
-// Helpers for doubles.
-nsresult
-CameraControlImpl::Set(uint32_t aKey, double aValue)
-{
-  SetParameter(aKey, aValue);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(uint32_t aKey, double* aValue)
-{
-  MOZ_ASSERT(aValue);
-  *aValue = GetParameterDouble(aKey);
-  return NS_OK;
-}
-
-// Helper for weighted regions.
-nsresult
-CameraControlImpl::Set(JSContext* aCx, uint32_t aKey, const JS::Value& aValue, uint32_t aLimit)
-{
-  if (aLimit == 0) {
-    DOM_CAMERA_LOGI("%s:%d : aLimit = 0, nothing to do\n", __func__, __LINE__);
-    return NS_OK;
-  }
-
-  if (!aValue.isObject()) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  uint32_t length = 0;
-
-  JS::Rooted<JSObject*> regions(aCx, &aValue.toObject());
-  if (!JS_GetArrayLength(aCx, regions, &length)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  DOM_CAMERA_LOGI("%s:%d : got %d regions (limited to %d)\n", __func__, __LINE__, length, aLimit);
-  if (length > aLimit) {
-    length = aLimit;
-  }
-
-  nsTArray<CameraRegion> regionArray;
-  regionArray.SetCapacity(length);
-
-  for (uint32_t i = 0; i < length; ++i) {
-    JS::Rooted<JS::Value> v(aCx);
-
-    if (!JS_GetElement(aCx, regions, i, &v)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    CameraRegion* r = regionArray.AppendElement();
-    /**
-     * These are the default values.  We can remove these when the xpidl
-     * dictionary parser gains the ability to grok default values.
-     */
-    r->top = -1000;
-    r->left = -1000;
-    r->bottom = 1000;
-    r->right = 1000;
-    r->weight = 1000;
-
-    nsresult rv = r->Init(aCx, v.address());
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    DOM_CAMERA_LOGI("region %d: top=%d, left=%d, bottom=%d, right=%d, weight=%d\n",
-      i,
-      r->top,
-      r->left,
-      r->bottom,
-      r->right,
-      r->weight
-    );
-  }
-  SetParameter(aKey, regionArray);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(JSContext* aCx, uint32_t aKey, JS::Value* aValue)
-{
-  nsTArray<CameraRegion> regionArray;
-
-  GetParameter(aKey, regionArray);
-
-  JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, 0));
-  if (!array) {
-    return NS_ERROR_OUT_OF_MEMORY;
+  if (mListenerLock) {
+    PR_DestroyRWLock(mListenerLock);
+    mListenerLock = nullptr;
   }
-
-  uint32_t length = regionArray.Length();
-  DOM_CAMERA_LOGI("%s:%d : got %d regions\n", __func__, __LINE__, length);
-
-  for (uint32_t i = 0; i < length; ++i) {
-    CameraRegion* r = &regionArray[i];
-    JS::Rooted<JS::Value> v(aCx);
-
-    JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
-    if (!o) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    DOM_CAMERA_LOGI("top=%d\n", r->top);
-    v = INT_TO_JSVAL(r->top);
-    if (!JS_SetProperty(aCx, o, "top", v)) {
-      return NS_ERROR_FAILURE;
-    }
-    DOM_CAMERA_LOGI("left=%d\n", r->left);
-    v = INT_TO_JSVAL(r->left);
-    if (!JS_SetProperty(aCx, o, "left", v)) {
-      return NS_ERROR_FAILURE;
-    }
-    DOM_CAMERA_LOGI("bottom=%d\n", r->bottom);
-    v = INT_TO_JSVAL(r->bottom);
-    if (!JS_SetProperty(aCx, o, "bottom", v)) {
-      return NS_ERROR_FAILURE;
-    }
-    DOM_CAMERA_LOGI("right=%d\n", r->right);
-    v = INT_TO_JSVAL(r->right);
-    if (!JS_SetProperty(aCx, o, "right", v)) {
-      return NS_ERROR_FAILURE;
-    }
-    DOM_CAMERA_LOGI("weight=%d\n", r->weight);
-    v = INT_TO_JSVAL(r->weight);
-    if (!JS_SetProperty(aCx, o, "weight", v)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    if (!JS_SetElement(aCx, array, i, o)) {
-      return NS_ERROR_FAILURE;
-    }
-  }
-
-  *aValue = JS::ObjectValue(*array);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Set(nsICameraShutterCallback* aOnShutter)
-{
-  mOnShutterCb = new nsMainThreadPtrHolder<nsICameraShutterCallback>(aOnShutter);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(nsICameraShutterCallback** aOnShutter)
-{
-  *aOnShutter = mOnShutterCb;
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Set(nsICameraClosedCallback* aOnClosed)
-{
-  mOnClosedCb = new nsMainThreadPtrHolder<nsICameraClosedCallback>(aOnClosed);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(nsICameraClosedCallback** aOnClosed)
-{
-  *aOnClosed = mOnClosedCb;
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Set(nsICameraRecorderStateChange* aOnRecorderStateChange)
-{
-  mOnRecorderStateChangeCb = new nsMainThreadPtrHolder<nsICameraRecorderStateChange>(aOnRecorderStateChange);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(nsICameraRecorderStateChange** aOnRecorderStateChange)
-{
-  *aOnRecorderStateChange = mOnRecorderStateChangeCb;
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Set(nsICameraPreviewStateChange* aOnPreviewStateChange)
-{
-  mOnPreviewStateChangeCb = new nsMainThreadPtrHolder<nsICameraPreviewStateChange>(aOnPreviewStateChange);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(nsICameraPreviewStateChange** aOnPreviewStateChange)
-{
-  *aOnPreviewStateChange = mOnPreviewStateChangeCb;
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Set(uint32_t aKey, const idl::CameraSize& aSize)
-{
-  SetParameter(aKey, aSize);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(uint32_t aKey, idl::CameraSize& aSize)
-{
-  GetParameter(aKey, aSize);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(uint32_t aKey, int32_t* aValue)
-{
-  MOZ_ASSERT(aValue);
-  *aValue = GetParameterInt32(aKey);
-  return NS_OK;
 }
 
 already_AddRefed<RecorderProfileManager>
 CameraControlImpl::GetRecorderProfileManager()
 {
   return GetRecorderProfileManagerImpl();
 }
 
 void
 CameraControlImpl::Shutdown()
 {
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-  mAutoFocusOnSuccessCb = nullptr;
-  mAutoFocusOnErrorCb = nullptr;
-  mTakePictureOnSuccessCb = nullptr;
-  mTakePictureOnErrorCb = nullptr;
-  mOnShutterCb = nullptr;
-  mOnClosedCb = nullptr;
-  mOnRecorderStateChangeCb = nullptr;
-  mOnPreviewStateChangeCb = nullptr;
+}
+
+void
+CameraControlImpl::OnHardwareStateChange(CameraControlListener::HardwareState aNewState)
+{
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread. On Gonk, it may be called from the camera's
+  //  local binder thread, should the mediaserver process die.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  if (aNewState == mHardwareState) {
+    DOM_CAMERA_LOGI("OnHardwareStateChange: state did not change from %d\n", mHardwareState);
+    return;
+  }
+
+#ifdef PR_LOGGING
+  const char* state[] = { "open", "closed", "failed" };
+  MOZ_ASSERT(aNewState >= 0);
+  if (static_cast<unsigned int>(aNewState) < sizeof(state) / sizeof(state[0])) {
+    DOM_CAMERA_LOGI("New hardware state is '%s'\n", state[aNewState]);
+  } else {
+    DOM_CAMERA_LOGE("OnHardwareStateChange: got invalid HardwareState value %d\n", aNewState);
+  }
+#endif
+
+  mHardwareState = aNewState;
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnHardwareStateChange(mHardwareState);
+  }
 }
 
 void
-CameraControlImpl::OnShutterInternal()
+CameraControlImpl::OnConfigurationChange()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  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
+CameraControlImpl::OnAutoFocusComplete(bool aAutoFocusSucceeded)
 {
-  DOM_CAMERA_LOGI("** SNAP **\n");
-  if (mOnShutterCb.get()) {
-    mOnShutterCb->HandleEvent();
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread. On Gonk, it is called from the camera
+  //  library's auto focus thread.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnAutoFocusComplete(aAutoFocusSucceeded);
+  }
+}
+
+void
+CameraControlImpl::OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType)
+{
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread. On Gonk, it is called from the camera
+  //  library's snapshot thread.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnTakePictureComplete(aData, aLength, aMimeType);
   }
 }
 
 void
 CameraControlImpl::OnShutter()
 {
-  nsCOMPtr<nsIRunnable> onShutter = NS_NewRunnableMethod(this, &CameraControlImpl::OnShutterInternal);
-  nsresult rv = NS_DispatchToMainThread(onShutter);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGW("Failed to dispatch onShutter event to main thread (%d)\n", rv);
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread. On Gonk, it is called from the camera driver's
+  //  preview thread.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnShutter();
   }
 }
 
-class OnClosedTask : public nsRunnable
-{
-public:
-  OnClosedTask(nsMainThreadPtrHandle<nsICameraClosedCallback> onClosed, uint64_t aWindowId)
-    : mOnClosedCb(onClosed)
-    , mWindowId(aWindowId)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~OnClosedTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnClosedCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      mOnClosedCb->HandleEvent();
-    }
-    return NS_OK;
-  }
-
-protected:
-  nsMainThreadPtrHandle<nsICameraClosedCallback> mOnClosedCb;
-  uint64_t mWindowId;
-};
-
 void
 CameraControlImpl::OnClosed()
 {
-  nsCOMPtr<nsIRunnable> onClosed = new OnClosedTask(mOnClosedCb, mWindowId);
-  nsresult rv = NS_DispatchToMainThread(onClosed);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGW("Failed to dispatch onClosed event to main thread (%d)\n", rv);
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnHardwareStateChange(CameraControlListener::kHardwareClosed);
   }
 }
 
 void
-CameraControlImpl::OnRecorderStateChange(const nsString& aStateMsg, int32_t aStatus, int32_t aTrackNumber)
+CameraControlImpl::OnRecorderStateChange(CameraControlListener::RecorderState aState,
+                                         int32_t aStatus, int32_t aTrackNumber)
 {
-  DOM_CAMERA_LOGI("OnRecorderStateChange: '%s'\n", NS_ConvertUTF16toUTF8(aStateMsg).get());
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread. On Gonk, it is called from the media encoder
+  //  thread.
+  RwLockAutoEnterRead lock(mListenerLock);
 
-  nsCOMPtr<nsIRunnable> onRecorderStateChange = new CameraRecorderStateChange(mOnRecorderStateChangeCb, aStateMsg, aStatus, aTrackNumber, mWindowId);
-  nsresult rv = NS_DispatchToMainThread(onRecorderStateChange);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGE("Failed to dispatch onRecorderStateChange event to main thread (%d)\n", rv);
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnRecorderStateChange(aState, aStatus, aTrackNumber);
   }
 }
 
 void
-CameraControlImpl::OnPreviewStateChange(PreviewState aNewState)
+CameraControlImpl::OnPreviewStateChange(CameraControlListener::PreviewState aNewState)
 {
+  // This callback runs on the Main Thread and the Camera Thread, and
+  //  may run on the local binder thread, should the mediaserver
+  //  process die.
+  RwLockAutoEnterRead lock(mListenerLock);
+
   if (aNewState == mPreviewState) {
     DOM_CAMERA_LOGI("OnPreviewStateChange: state did not change from %d\n", mPreviewState);
     return;
   }
 
-  nsString msg;
-  switch (aNewState) {
-    case PREVIEW_STOPPED:
-      msg = NS_LITERAL_STRING("stopped");
-      break;
+#ifdef PR_LOGGING
+  const char* state[] = { "stopped", "paused", "started" };
+  MOZ_ASSERT(aNewState >= 0);
+  if (static_cast<unsigned int>(aNewState) < sizeof(state) / sizeof(state[0])) {
+    DOM_CAMERA_LOGI("New preview state is '%s'\n", state[aNewState]);
+  } else {
+    DOM_CAMERA_LOGE("OnPreviewStateChange: got unknown PreviewState value %d\n", aNewState);
+  }
+#endif
 
-    case PREVIEW_STARTED:
-      msg = NS_LITERAL_STRING("started");
-      break;
-
-    default:
-      MOZ_ASSUME_UNREACHABLE("Preview state can only be PREVIEW_STOPPED or _STARTED!");
-  }
-
-  // const nsString& aStateMsg)
-  DOM_CAMERA_LOGI("OnPreviewStateChange: '%s'\n", NS_ConvertUTF16toUTF8(msg).get());
   mPreviewState = aNewState;
 
-  nsCOMPtr<nsIRunnable> onPreviewStateChange = new CameraPreviewStateChange(mOnPreviewStateChangeCb, msg, mWindowId);
-  nsresult rv = NS_DispatchToMainThread(onPreviewStateChange);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGE("Failed to dispatch onPreviewStateChange event to main thread (%d)\n", rv);
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnPreviewStateChange(mPreviewState);
+  }
+}
+
+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 %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;
+  }
+  return consumed;
+}
+
+void
+CameraControlImpl::OnError(CameraControlListener::CameraErrorContext aContext,
+                           CameraControlListener::CameraError aError)
+{
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+#ifdef PR_LOGGING
+  const char* error[] = { "camera-service-failed", "unknown" };
+  if (static_cast<unsigned int>(aError) < sizeof(error) / sizeof(error[0])) {
+    DOM_CAMERA_LOGW("CameraControlImpl::OnError : aContext=%u, msg='%s'\n",
+      aContext, error[aError]);
+  } else {
+    DOM_CAMERA_LOGE("CameraControlImpl::OnError : aContext=%u, unknown error=%d\n",
+      aContext, aError);
+  }
+#endif
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnError(aContext, aError);
   }
 }
 
-nsresult
-CameraControlImpl::GetPreviewStream(CameraSize aSize, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError)
+// Camera control asynchronous message; these are dispatched from
+//  the Main Thread to the Camera Thread, where they are consumed.
+
+class CameraControlImpl::ControlMessage : public nsRunnable
 {
-  nsCOMPtr<nsIRunnable> getPreviewStreamTask = new GetPreviewStreamTask(this, aSize, onSuccess, onError);
-  return mCameraThread->Dispatch(getPreviewStreamTask, NS_DISPATCH_NORMAL);
+public:
+  ControlMessage(CameraControlImpl* aCameraControl,
+                 CameraControlListener::CameraErrorContext aContext)
+    : mCameraControl(aCameraControl)
+    , mContext(aContext)
+  {
+    MOZ_COUNT_CTOR(CameraControlImpl::ControlMessage);
+  }
+
+  virtual ~ControlMessage()
+  {
+    MOZ_COUNT_DTOR(CameraControlImpl::ControlMessage);
+  }
+
+  virtual nsresult RunImpl() = 0;
+
+  NS_IMETHOD
+  Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(mCameraControl);
+    MOZ_ASSERT(NS_GetCurrentThread() == mCameraControl->mCameraThread);
+
+    nsresult rv = RunImpl();
+    if (NS_FAILED(rv)) {
+      DOM_CAMERA_LOGW("Camera control API failed at %d with 0x%x\n", mContext, rv);
+      // XXXmikeh - do we want to report a more specific error code?
+      mCameraControl->OnError(mContext, CameraControlListener::kErrorApiFailed);
+    }
+
+    return NS_OK;
+  }
+
+protected:
+  nsRefPtr<CameraControlImpl> mCameraControl;
+  CameraControlListener::CameraErrorContext mContext;
+};
+
+nsresult
+CameraControlImpl::SetConfiguration(const Configuration& aConfig)
+{
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext,
+            const Configuration& aConfig)
+      : ControlMessage(aCameraControl, aContext)
+      , mConfig(aConfig)
+    { }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->SetConfigurationImpl(mConfig);
+    }
+
+  protected:
+    Configuration mConfig;
+  };
+
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInSetConfiguration, aConfig), NS_DISPATCH_NORMAL);
 }
 
 nsresult
-CameraControlImpl::AutoFocus(nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError)
+CameraControlImpl::AutoFocus(bool aCancelExistingCall)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  bool cancel = false;
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext,
+            bool aCancelExistingCall)
+      : ControlMessage(aCameraControl, aContext)
+      , mCancelExistingCall(aCancelExistingCall)
+    { }
 
-  nsCOMPtr<nsICameraAutoFocusCallback> cb = mAutoFocusOnSuccessCb.get();
-  if (cb) {
-    /**
-     * We already have a callback, so someone has already
-     * called autoFocus() -- cancel it.
-     */
-    mAutoFocusOnSuccessCb = nullptr;
-    mAutoFocusOnErrorCb = nullptr;
-    cancel = true;
-  }
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->AutoFocusImpl(mCancelExistingCall);
+    }
 
-  nsCOMPtr<nsIRunnable> autoFocusTask = new AutoFocusTask(this, cancel, onSuccess, onError);
-  return mCameraThread->Dispatch(autoFocusTask, NS_DISPATCH_NORMAL);
+  protected:
+    bool mCancelExistingCall;
+  };
+
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInAutoFocus, aCancelExistingCall), NS_DISPATCH_NORMAL);
 }
 
 nsresult
-CameraControlImpl::TakePicture(const CameraSize& aSize, int32_t aRotation, const nsAString& aFileFormat, CameraPosition aPosition, uint64_t aDateTime, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError)
+CameraControlImpl::TakePicture()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  bool cancel = false;
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext)
+      : ControlMessage(aCameraControl, aContext)
+    { }
 
-  nsCOMPtr<nsICameraTakePictureCallback> cb = mTakePictureOnSuccessCb.get();
-  if (cb) {
-    /**
-     * We already have a callback, so someone has already
-     * called takePicture() -- cancel it.
-     */
-    mTakePictureOnSuccessCb = nullptr;
-    mTakePictureOnErrorCb = nullptr;
-    cancel = true;
-  }
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->TakePictureImpl();
+    }
+  };
 
-  nsCOMPtr<nsIRunnable> takePictureTask = new TakePictureTask(this, cancel, aSize, aRotation, aFileFormat, aPosition, aDateTime, onSuccess, onError);
-  return mCameraThread->Dispatch(takePictureTask, NS_DISPATCH_NORMAL);
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInTakePicture), NS_DISPATCH_NORMAL);
 }
 
 nsresult
-CameraControlImpl::StartRecording(CameraStartRecordingOptions* aOptions, DeviceStorageFileDescriptor* aFileDescriptor, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError)
+CameraControlImpl::StartRecording(DeviceStorageFileDescriptor* aFileDescriptor,
+                                  const StartRecordingOptions* aOptions)
 {
-  nsCOMPtr<nsIRunnable> startRecordingTask = new StartRecordingTask(this, *aOptions, aFileDescriptor, onSuccess, onError, mWindowId);
-  return mCameraThread->Dispatch(startRecordingTask, NS_DISPATCH_NORMAL);
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext,
+            const StartRecordingOptions* aOptions,
+            DeviceStorageFileDescriptor* aFileDescriptor)
+      : ControlMessage(aCameraControl, aContext)
+      , mOptionsPassed(false)
+      , mFileDescriptor(aFileDescriptor)
+    {
+      if (aOptions) {
+        mOptions = *aOptions;
+        mOptionsPassed = true;
+      }
+    }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->StartRecordingImpl(mFileDescriptor,
+        mOptionsPassed ? &mOptions : nullptr);
+    }
+
+  protected:
+    StartRecordingOptions mOptions;
+    bool mOptionsPassed;
+    nsRefPtr<DeviceStorageFileDescriptor> mFileDescriptor;
+  };
+
+
+  return mCameraThread->Dispatch(new Message(this, CameraControlListener::kInStartRecording,
+    aOptions, aFileDescriptor), NS_DISPATCH_NORMAL);
 }
 
 nsresult
 CameraControlImpl::StopRecording()
 {
-  nsCOMPtr<nsIRunnable> stopRecordingTask = new StopRecordingTask(this);
-  return mCameraThread->Dispatch(stopRecordingTask, NS_DISPATCH_NORMAL);
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext)
+      : ControlMessage(aCameraControl, aContext)
+    { }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->StopRecordingImpl();
+    }
+  };
+
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInStopRecording), NS_DISPATCH_NORMAL);
 }
 
 nsresult
-CameraControlImpl::StartPreview(DOMCameraPreview* aDOMPreview)
+CameraControlImpl::StartPreview()
 {
-  nsCOMPtr<nsIRunnable> startPreviewTask = new StartPreviewTask(this, aDOMPreview);
-  return mCameraThread->Dispatch(startPreviewTask, NS_DISPATCH_NORMAL);
-}
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext)
+      : ControlMessage(aCameraControl, aContext)
+    { }
 
-void
-CameraControlImpl::StopPreview()
-{
-  nsCOMPtr<nsIRunnable> stopPreviewTask = new StopPreviewTask(this);
-  mCameraThread->Dispatch(stopPreviewTask, NS_DISPATCH_NORMAL);
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->StartPreviewImpl();
+    }
+  };
+
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInStartPreview), NS_DISPATCH_NORMAL);
 }
 
 nsresult
-CameraControlImpl::GetPreviewStreamVideoMode(CameraRecorderOptions* aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError)
+CameraControlImpl::StopPreview()
 {
-  nsCOMPtr<nsIRunnable> getPreviewStreamVideoModeTask = new GetPreviewStreamVideoModeTask(this, *aOptions, onSuccess, onError);
-  return mCameraThread->Dispatch(getPreviewStreamVideoModeTask, NS_DISPATCH_NORMAL);
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext)
+      : ControlMessage(aCameraControl, aContext)
+    { }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->StopPreviewImpl();
+    }
+  };
+
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInStopPreview), NS_DISPATCH_NORMAL);
 }
 
 nsresult
-CameraControlImpl::ReleaseHardware(nsICameraReleaseCallback* onSuccess, nsICameraErrorCallback* onError)
+CameraControlImpl::ReleaseHardware()
 {
-  nsCOMPtr<nsIRunnable> releaseHardwareTask = new ReleaseHardwareTask(this, onSuccess, onError);
-  return mCameraThread->Dispatch(releaseHardwareTask, NS_DISPATCH_NORMAL);
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext)
+      : ControlMessage(aCameraControl, aContext)
+    { }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->ReleaseHardwareImpl();
+    }
+  };
+
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInReleaseHardware), NS_DISPATCH_NORMAL);
 }
 
-bool
-CameraControlImpl::ReceiveFrame(void* aBuffer, ImageFormat aFormat, FrameBuilder aBuilder)
+class CameraControlImpl::ListenerMessage : public CameraControlImpl::ControlMessage
 {
-  if (!mDOMPreview) {
-    return false;
-  }
+public:
+  ListenerMessage(CameraControlImpl* aCameraControl,
+                  CameraControlListener* aListener)
+    : ControlMessage(aCameraControl, CameraControlListener::kInUnspecified)
+    , mListener(aListener)
+  { }
+
+protected:
+  nsRefPtr<CameraControlListener> mListener;
+};
 
-  return mDOMPreview->ReceiveFrame(aBuffer, aFormat, aBuilder);
+void
+CameraControlImpl::AddListenerImpl(already_AddRefed<CameraControlListener> aListener)
+{
+  RwLockAutoEnterWrite lock(mListenerLock);
+
+  CameraControlListener* l = *mListeners.AppendElement() = aListener;
+
+  // Update the newly-added listener's state
+  l->OnConfigurationChange(mCurrentConfiguration);
+  l->OnHardwareStateChange(mHardwareState);
+  l->OnPreviewStateChange(mPreviewState);
 }
 
-NS_IMETHODIMP
-GetPreviewStreamResult::Run()
-{
-  /**
-   * The camera preview stream object is DOM-facing, and as such
-   * must be a cycle-collection participant created on the main
-   * thread.
-   */
-  MOZ_ASSERT(NS_IsMainThread());
+void
+CameraControlImpl::AddListener(CameraControlListener* aListener)
+ {
+  class Message : public ListenerMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener* aListener)
+      : ListenerMessage(aCameraControl, aListener)
+    { }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      mCameraControl->AddListenerImpl(mListener.forget());
+      return NS_OK;
+    }
+  };
+
+  mCameraThread->Dispatch(new Message(this, aListener), NS_DISPATCH_NORMAL);
+}
 
-  nsCOMPtr<nsICameraPreviewStreamCallback> onSuccess = mOnSuccessCb.get();
-  nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId);
-  if (onSuccess && nsDOMCameraManager::IsWindowStillActive(mWindowId) && window) {
-    nsCOMPtr<nsIDOMMediaStream> stream =
-      new DOMCameraPreview(window, mCameraControl, mWidth, mHeight,
-	                         mFramesPerSecond);
-    onSuccess->HandleEvent(stream);
-  }
-  return NS_OK;
+void
+CameraControlImpl::RemoveListenerImpl(CameraControlListener* aListener)
+{
+  RwLockAutoEnterWrite lock(mListenerLock);
+
+  nsRefPtr<CameraControlListener> l(aListener);
+  mListeners.RemoveElement(l);
+  // XXXmikeh - do we want to notify the listener that it has been removed?
 }
+
+void
+CameraControlImpl::RemoveListener(CameraControlListener* aListener)
+ {
+  class Message : public ListenerMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl, CameraControlListener* aListener)
+      : ListenerMessage(aCameraControl, aListener)
+    { }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      mCameraControl->RemoveListenerImpl(mListener);
+      return NS_OK;
+    }
+  };
+
+  mCameraThread->Dispatch(new Message(this, aListener), NS_DISPATCH_NORMAL);
+}
--- a/dom/camera/CameraControlImpl.h
+++ b/dom/camera/CameraControlImpl.h
@@ -1,750 +1,123 @@
 /* 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_CAMERACONTROLIMPL_H
 #define DOM_CAMERA_CAMERACONTROLIMPL_H
 
+#include "nsTArray.h"
+#include "nsWeakPtr.h"
 #include "mozilla/Attributes.h"
-#include "nsDOMFile.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "nsIFile.h"
 #include "nsProxyRelease.h"
-#include "DictionaryHelpers.h"
+#include "AutoRwLock.h"
 #include "nsIDOMDeviceStorage.h"
-#include "DOMCameraManager.h"
-#include "DOMCameraPreview.h"
 #include "ICameraControl.h"
 #include "CameraCommon.h"
 #include "DeviceStorage.h"
 #include "DeviceStorageFileDescriptor.h"
+#include "CameraControlListener.h"
 
 class DeviceStorageFileDescriptor;
 
 namespace mozilla {
 
-class GetPreviewStreamTask;
-class StartPreviewTask;
-class StopPreviewTask;
-class AutoFocusTask;
-class TakePictureTask;
-class StartRecordingTask;
-class StopRecordingTask;
-class SetParameterTask;
-class GetParameterTask;
-class GetPreviewStreamVideoModeTask;
-class ReleaseHardwareTask;
+namespace layers {
+  class Image;
+}
 
-class DOMCameraPreview;
 class RecorderProfileManager;
 
 class CameraControlImpl : public ICameraControl
 {
-  friend class GetPreviewStreamTask;
-  friend class StartPreviewTask;
-  friend class StopPreviewTask;
-  friend class AutoFocusTask;
-  friend class TakePictureTask;
-  friend class StartRecordingTask;
-  friend class StopRecordingTask;
-  friend class SetParameterTask;
-  friend class GetParameterTask;
-  friend class GetPreviewStreamVideoModeTask;
-  friend class ReleaseHardwareTask;
-
 public:
-  CameraControlImpl(uint32_t aCameraId, nsIThread* aCameraThread, uint64_t aWindowId);
-
-  nsresult GetPreviewStream(idl::CameraSize aSize, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError);
-  nsresult StartPreview(DOMCameraPreview* aDOMPreview);
-  void StopPreview();
-  nsresult AutoFocus(nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError);
-  nsresult TakePicture(const idl::CameraSize& aSize, int32_t aRotation, const nsAString& aFileFormat, idl::CameraPosition aPosition, uint64_t aDateTime, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError);
-  nsresult StartRecording(idl::CameraStartRecordingOptions* aOptions, DeviceStorageFileDescriptor *aDSFileDescriptor, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError);
-  nsresult StopRecording();
-  nsresult GetPreviewStreamVideoMode(idl::CameraRecorderOptions* aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError);
-  nsresult ReleaseHardware(nsICameraReleaseCallback* onSuccess, nsICameraErrorCallback* onError);
+  CameraControlImpl(uint32_t aCameraId);
+  virtual void AddListener(CameraControlListener* aListener) MOZ_OVERRIDE;
+  virtual void RemoveListener(CameraControlListener* aListener) MOZ_OVERRIDE;
 
-  nsresult Set(uint32_t aKey, const nsAString& aValue);
-  nsresult Get(uint32_t aKey, nsAString& aValue);
-  nsresult Set(uint32_t aKey, double aValue);
-  nsresult Get(uint32_t aKey, double* aValue);
-  nsresult Set(JSContext* aCx, uint32_t aKey, const JS::Value& aValue, uint32_t aLimit);
-  nsresult Get(JSContext* aCx, uint32_t aKey, JS::Value* aValue);
-  nsresult Set(nsICameraShutterCallback* aOnShutter);
-  nsresult Get(nsICameraShutterCallback** aOnShutter);
-  nsresult Set(nsICameraClosedCallback* aOnClosed);
-  nsresult Get(nsICameraClosedCallback** aOnClosed);
-  nsresult Set(nsICameraRecorderStateChange* aOnRecorderStateChange);
-  nsresult Get(nsICameraRecorderStateChange** aOnRecorderStateChange);
-  nsresult Set(nsICameraPreviewStateChange* aOnPreviewStateChange);
-  nsresult Get(nsICameraPreviewStateChange** aOnPreviewStateChange);
-  nsresult Set(uint32_t aKey, const idl::CameraSize& aSize);
-  nsresult Get(uint32_t aKey, idl::CameraSize& aSize);
-  nsresult Get(uint32_t aKey, int32_t* aValue);
-
-  nsresult SetFocusAreas(JSContext* aCx, const JS::Value& aValue)
-  {
-    return Set(aCx, CAMERA_PARAM_FOCUSAREAS, aValue, mMaxFocusAreas);
-  }
-
-  nsresult SetMeteringAreas(JSContext* aCx, const JS::Value& aValue)
-  {
-    return Set(aCx, CAMERA_PARAM_METERINGAREAS, aValue, mMaxMeteringAreas);
-  }
+  virtual nsresult SetConfiguration(const Configuration& aConfig) MOZ_OVERRIDE;
+  virtual nsresult StartPreview() MOZ_OVERRIDE;
+  virtual nsresult StopPreview() MOZ_OVERRIDE;
+  virtual nsresult AutoFocus(bool aCancelExistingCall) MOZ_OVERRIDE;
+  virtual nsresult TakePicture() MOZ_OVERRIDE;
+  virtual nsresult StartRecording(DeviceStorageFileDescriptor* aFileDescriptor,
+                                  const StartRecordingOptions* aOptions) MOZ_OVERRIDE;
+  virtual nsresult StopRecording() MOZ_OVERRIDE;
+  virtual nsresult ReleaseHardware() MOZ_OVERRIDE;
 
   already_AddRefed<RecorderProfileManager> GetRecorderProfileManager();
   uint32_t GetCameraId() { return mCameraId; }
 
-  virtual const char* GetParameter(const char* aKey) = 0;
-  virtual const char* GetParameterConstChar(uint32_t aKey) = 0;
-  virtual double GetParameterDouble(uint32_t aKey) = 0;
-  virtual int32_t GetParameterInt32(uint32_t aKey) = 0;
-  virtual void GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions) = 0;
-  virtual void GetParameter(uint32_t aKey, idl::CameraSize& aSize) = 0;
-  virtual void SetParameter(const char* aKey, const char* aValue) = 0;
-  virtual void SetParameter(uint32_t aKey, const char* aValue) = 0;
-  virtual void SetParameter(uint32_t aKey, double aValue) = 0;
-  virtual void SetParameter(uint32_t aKey, const nsTArray<idl::CameraRegion>& aRegions) = 0;
-  virtual void SetParameter(uint32_t aKey, const idl::CameraSize& aSize) = 0;
-  virtual nsresult GetVideoSizes(nsTArray<idl::CameraSize>& aVideoSizes) = 0;
-  virtual nsresult PushParameters() = 0;
-  virtual void Shutdown();
+  virtual void Shutdown() MOZ_OVERRIDE;
 
-  bool ReceiveFrame(void* aBuffer, ImageFormat aFormat, FrameBuilder aBuilder);
   void OnShutter();
   void OnClosed();
-  void OnRecorderStateChange(const nsString& aStateMsg, int32_t aStatus, int32_t aTrackNumber);
-
-  enum PreviewState {
-    PREVIEW_STOPPED,
-    PREVIEW_STARTED
-  };
-  void OnPreviewStateChange(PreviewState aNewState);
-
-  uint64_t GetWindowId()
-  {
-    return mWindowId;
-  }
+  void OnError(CameraControlListener::CameraErrorContext aContext,
+               CameraControlListener::CameraError aError);
 
 protected:
+  // Event handlers.
+  void OnAutoFocusComplete(bool aAutoFocusSucceeded);
+  void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType);
+
+  bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);
+  void OnRecorderStateChange(CameraControlListener::RecorderState aState,
+                             int32_t aStatus, int32_t aTrackNumber);
+  void OnPreviewStateChange(CameraControlListener::PreviewState aState);
+  void OnHardwareStateChange(CameraControlListener::HardwareState aState);
+  void OnConfigurationChange();
+
+  // When we create a new CameraThread, we keep a static reference to it so
+  // that multiple CameraControl instances can find and reuse it; but we
+  // don't want that reference to keep the thread object around unnecessarily,
+  // so we make it a weak reference. The strong dynamic references will keep
+  // the thread object alive as needed.
+  static nsWeakPtr sCameraThread;
+  nsCOMPtr<nsIThread> mCameraThread;
+
   virtual ~CameraControlImpl();
 
-  virtual nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream) = 0;
-  virtual nsresult StartPreviewImpl(StartPreviewTask* aStartPreview) = 0;
-  virtual nsresult StopPreviewImpl(StopPreviewTask* aStopPreview) = 0;
-  virtual nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus) = 0;
-  virtual nsresult TakePictureImpl(TakePictureTask* aTakePicture) = 0;
-  virtual nsresult StartRecordingImpl(StartRecordingTask* aStartRecording) = 0;
-  virtual nsresult StopRecordingImpl(StopRecordingTask* aStopRecording) = 0;
+  virtual void BeginBatchParameterSet() MOZ_OVERRIDE { }
+  virtual void EndBatchParameterSet() MOZ_OVERRIDE { }
+
+  // Manage camera event listeners.
+  void AddListenerImpl(already_AddRefed<CameraControlListener> aListener);
+  void RemoveListenerImpl(CameraControlListener* aListener);
+  nsTArray<nsRefPtr<CameraControlListener> > mListeners;
+  PRRWLock* mListenerLock;
+
+  class ControlMessage;
+  class ListenerMessage;
+
+  virtual nsresult SetConfigurationImpl(const Configuration& aConfig) = 0;
+  virtual nsresult StartPreviewImpl() = 0;
+  virtual nsresult StopPreviewImpl() = 0;
+  virtual nsresult AutoFocusImpl(bool aCancelExistingCall) = 0;
+  virtual nsresult TakePictureImpl() = 0;
+  virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
+                                      const StartRecordingOptions* aOptions) = 0;
+  virtual nsresult StopRecordingImpl() = 0;
   virtual nsresult PushParametersImpl() = 0;
   virtual nsresult PullParametersImpl() = 0;
-  virtual nsresult GetPreviewStreamVideoModeImpl(GetPreviewStreamVideoModeTask* aGetPreviewStreamVideoMode) = 0;
-  virtual nsresult ReleaseHardwareImpl(ReleaseHardwareTask* aReleaseHardware) = 0;
+  virtual nsresult ReleaseHardwareImpl() = 0;
   virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl() = 0;
 
   void OnShutterInternal();
   void OnClosedInternal();
 
-  uint32_t            mCameraId;
-  nsCOMPtr<nsIThread> mCameraThread;
-  uint64_t            mWindowId;
-  nsString            mFileFormat;
-  uint32_t            mMaxMeteringAreas;
-  uint32_t            mMaxFocusAreas;
-  PreviewState        mPreviewState;
+  uint32_t mCameraId;
 
-  /**
-   * 'mDOMPreview' is a raw pointer to the object that will receive incoming
-   * preview frames.  This is guaranteed to be valid, or null.
-   *
-   * It is set by a call to StartPreview(), and set to null on StopPreview().
-   * It is up to the caller to ensure that the object will not disappear
-   * out from under this pointer--usually by calling NS_ADDREF().
-   */
-  DOMCameraPreview*   mDOMPreview;
+  CameraControlListener::CameraListenerConfiguration mCurrentConfiguration;
 
-  nsMainThreadPtrHandle<nsICameraAutoFocusCallback>   mAutoFocusOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback>       mAutoFocusOnErrorCb;
-  nsMainThreadPtrHandle<nsICameraTakePictureCallback> mTakePictureOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback>       mTakePictureOnErrorCb;
-  nsMainThreadPtrHandle<nsICameraShutterCallback>     mOnShutterCb;
-  nsMainThreadPtrHandle<nsICameraClosedCallback>      mOnClosedCb;
-  nsMainThreadPtrHandle<nsICameraRecorderStateChange> mOnRecorderStateChangeCb;
-  nsMainThreadPtrHandle<nsICameraPreviewStateChange>  mOnPreviewStateChangeCb;
+  CameraControlListener::PreviewState   mPreviewState;
+  CameraControlListener::HardwareState  mHardwareState;
 
 private:
   CameraControlImpl(const CameraControlImpl&) MOZ_DELETE;
   CameraControlImpl& operator=(const CameraControlImpl&) MOZ_DELETE;
 };
 
-// Error result runnable
-class CameraErrorResult : public nsRunnable
-{
-public:
-  CameraErrorResult(nsMainThreadPtrHandle<nsICameraErrorCallback> onError, const nsString& aErrorMsg, uint64_t aWindowId)
-    : mOnErrorCb(onError)
-    , mErrorMsg(aErrorMsg)
-    , mWindowId(aWindowId)
-  { }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnErrorCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      mOnErrorCb->HandleEvent(mErrorMsg);
-    }
-    return NS_OK;
-  }
-
-protected:
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-  const nsString mErrorMsg;
-  uint64_t mWindowId;
-};
-
-// Return the resulting preview stream to JS.  Runs on the main thread.
-class GetPreviewStreamResult : public nsRunnable
-{
-public:
-  GetPreviewStreamResult(CameraControlImpl* aCameraControl, uint32_t aWidth, uint32_t aHeight, uint32_t aFramesPerSecond, nsMainThreadPtrHandle<nsICameraPreviewStreamCallback>& onSuccess, uint64_t aWindowId)
-    : mCameraControl(aCameraControl)
-    , mWidth(aWidth)
-    , mHeight(aHeight)
-    , mFramesPerSecond(aFramesPerSecond)
-    , mOnSuccessCb(onSuccess)
-    , mWindowId(aWindowId)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~GetPreviewStreamResult()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  // Run() method is implementation specific.
-  NS_IMETHOD Run() MOZ_OVERRIDE;
-
-protected:
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  uint32_t mWidth;
-  uint32_t mHeight;
-  uint32_t mFramesPerSecond;
-  nsMainThreadPtrHandle<nsICameraPreviewStreamCallback> mOnSuccessCb;
-  uint64_t mWindowId;
-};
-
-// Get the desired preview stream.
-class GetPreviewStreamTask : public nsRunnable
-{
-public:
-  GetPreviewStreamTask(CameraControlImpl* aCameraControl, idl::CameraSize aSize, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError)
-    : mSize(aSize)
-    , mCameraControl(aCameraControl)
-    , mOnSuccessCb(new nsMainThreadPtrHolder<nsICameraPreviewStreamCallback>(onSuccess))
-    , mOnErrorCb(new nsMainThreadPtrHolder<nsICameraErrorCallback>(onError))
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~GetPreviewStreamTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    nsresult rv = mCameraControl->GetPreviewStreamImpl(this);
-
-    if (NS_FAILED(rv)) {
-      nsCOMPtr<nsIRunnable> cameraErrorResult = new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId());
-      rv = NS_DispatchToMainThread(cameraErrorResult);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    return rv;
-  }
-
-  idl::CameraSize mSize;
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  nsMainThreadPtrHandle<nsICameraPreviewStreamCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-};
-
-// Return the autofocus status to JS.  Runs on the main thread.
-class AutoFocusResult : public nsRunnable
-{
-public:
-  AutoFocusResult(bool aSuccess, nsMainThreadPtrHandle<nsICameraAutoFocusCallback> onSuccess, uint64_t aWindowId)
-    : mSuccess(aSuccess)
-    , mOnSuccessCb(onSuccess)
-    , mWindowId(aWindowId)
-  { }
-
-  virtual ~AutoFocusResult() { }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnSuccessCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      mOnSuccessCb->HandleEvent(mSuccess);
-    }
-    return NS_OK;
-  }
-
-protected:
-  bool mSuccess;
-  nsMainThreadPtrHandle<nsICameraAutoFocusCallback> mOnSuccessCb;
-  uint64_t mWindowId;
-};
-
-// Autofocus the camera.
-class AutoFocusTask : public nsRunnable
-{
-public:
-  AutoFocusTask(CameraControlImpl* aCameraControl, bool aCancel, nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError)
-    : mCameraControl(aCameraControl)
-    , mCancel(aCancel)
-    , mOnSuccessCb(new nsMainThreadPtrHolder<nsICameraAutoFocusCallback>(onSuccess))
-    , mOnErrorCb(new nsMainThreadPtrHolder<nsICameraErrorCallback>(onError))
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~AutoFocusTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-    nsresult rv = mCameraControl->AutoFocusImpl(this);
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-
-    if (NS_FAILED(rv)) {
-      nsCOMPtr<nsIRunnable> cameraErrorResult = new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId());
-      rv = NS_DispatchToMainThread(cameraErrorResult);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    return rv;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  bool mCancel;
-  nsMainThreadPtrHandle<nsICameraAutoFocusCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-};
-
-// Return the captured picture to JS.  Runs on the main thread.
-class TakePictureResult : public nsRunnable
-{
-public:
-  TakePictureResult(uint8_t* aData, uint64_t aLength, const nsAString& aMimeType, nsMainThreadPtrHandle<nsICameraTakePictureCallback> onSuccess, uint64_t aWindowId)
-    : mData(aData)
-    , mLength(aLength)
-    , mMimeType(aMimeType)
-    , mOnSuccessCb(onSuccess)
-    , mWindowId(aWindowId)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~TakePictureResult()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-    if (mOnSuccessCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      nsCOMPtr<nsIDOMBlob> image = new nsDOMMemoryFile(static_cast<void*>(mData), static_cast<uint64_t>(mLength), mMimeType);
-      mOnSuccessCb->HandleEvent(image);
-    } else {
-      delete[] mData;
-    }
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-    return NS_OK;
-  }
-
-protected:
-  uint8_t* mData;
-  uint64_t mLength;
-  nsString mMimeType;
-  nsMainThreadPtrHandle<nsICameraTakePictureCallback> mOnSuccessCb;
-  uint64_t mWindowId;
-};
-
-// Capture a still image with the camera.
-class TakePictureTask : public nsRunnable
-{
-public:
-  TakePictureTask(CameraControlImpl* aCameraControl, bool aCancel, const idl::CameraSize& aSize, int32_t aRotation, const nsAString& aFileFormat, idl::CameraPosition aPosition, uint64_t aDateTime, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError)
-    : mCameraControl(aCameraControl)
-    , mCancel(aCancel)
-    , mSize(aSize)
-    , mRotation(aRotation)
-    , mFileFormat(aFileFormat)
-    , mPosition(aPosition)
-    , mDateTime(aDateTime)
-    , mOnSuccessCb(new nsMainThreadPtrHolder<nsICameraTakePictureCallback>(onSuccess))
-    , mOnErrorCb(new nsMainThreadPtrHolder<nsICameraErrorCallback>(onError))
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~TakePictureTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-    nsresult rv = mCameraControl->TakePictureImpl(this);
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-
-    if (NS_FAILED(rv)) {
-      nsCOMPtr<nsIRunnable> cameraErrorResult = new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId());
-      rv = NS_DispatchToMainThread(cameraErrorResult);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    return rv;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  bool mCancel;
-  idl::CameraSize mSize;
-  int32_t mRotation;
-  nsString mFileFormat;
-  idl::CameraPosition mPosition;
-  uint64_t mDateTime;
-  nsMainThreadPtrHandle<nsICameraTakePictureCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-};
-
-// Return the result of starting recording.  Runs on the main thread.
-class StartRecordingResult : public nsRunnable
-{
-public:
-  StartRecordingResult(nsMainThreadPtrHandle<nsICameraStartRecordingCallback> onSuccess, uint64_t aWindowId)
-    : mOnSuccessCb(onSuccess)
-    , mWindowId(aWindowId)
-  { }
-
-  virtual ~StartRecordingResult() { }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnSuccessCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      mOnSuccessCb->HandleEvent();
-    }
-    return NS_OK;
-  }
-
-protected:
-  nsMainThreadPtrHandle<nsICameraStartRecordingCallback> mOnSuccessCb;
-  uint64_t mWindowId;
-};
-
-// Start video recording.
-class StartRecordingTask : public nsRunnable
-{
-public:
-  StartRecordingTask(CameraControlImpl* aCameraControl,
-                     idl::CameraStartRecordingOptions aOptions,
-                     DeviceStorageFileDescriptor *aDSFileDescriptor,
-                     nsICameraStartRecordingCallback* onSuccess,
-                     nsICameraErrorCallback* onError,
-                     uint64_t aWindowId)
-    : mCameraControl(aCameraControl)
-    , mOptions(aOptions)
-    , mDSFileDescriptor(aDSFileDescriptor)
-    , mOnSuccessCb(new nsMainThreadPtrHolder<nsICameraStartRecordingCallback>(onSuccess))
-    , mOnErrorCb(new nsMainThreadPtrHolder<nsICameraErrorCallback>(onError))
-    , mWindowId(aWindowId)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~StartRecordingTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-    nsresult rv = mCameraControl->StartRecordingImpl(this);
-    DOM_CAMERA_LOGT("%s:%d : result %d\n", __func__, __LINE__, rv);
-
-    // dispatch the callback
-    nsCOMPtr<nsIRunnable> startRecordingResult;
-    if (NS_SUCCEEDED(rv)) {
-      startRecordingResult = new StartRecordingResult(mOnSuccessCb, mWindowId);
-    } else {
-      startRecordingResult = new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mWindowId);
-    }
-    rv = NS_DispatchToMainThread(startRecordingResult);
-    if (NS_FAILED(rv)) {
-      DOM_CAMERA_LOGE("Failed to dispatch start recording result to main thread (%d)!", rv);
-    }
-    return rv;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  idl::CameraStartRecordingOptions mOptions;
-  nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
-  nsMainThreadPtrHandle<nsICameraStartRecordingCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-  uint64_t mWindowId;
-};
-
-// Stop video recording.
-class StopRecordingTask : public nsRunnable
-{
-public:
-  StopRecordingTask(CameraControlImpl* aCameraControl)
-    : mCameraControl(aCameraControl)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~StopRecordingTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-    nsresult rv = mCameraControl->StopRecordingImpl(this);
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-
-    NS_ENSURE_SUCCESS(rv, rv);
-    return NS_OK;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-};
-
-// Start the preview.
-class StartPreviewTask : public nsRunnable
-{
-public:
-  StartPreviewTask(CameraControlImpl* aCameraControl, DOMCameraPreview* aDOMPreview)
-    : mCameraControl(aCameraControl)
-    , mDOMPreview(aDOMPreview)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~StartPreviewTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-    nsresult rv = mCameraControl->StartPreviewImpl(this);
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-
-    NS_ENSURE_SUCCESS(rv, rv);
-    return NS_OK;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  DOMCameraPreview* mDOMPreview; // DOMCameraPreview NS_ADDREFs itself for us
-};
-
-// Stop the preview.
-class StopPreviewTask : public nsRunnable
-{
-public:
-  StopPreviewTask(CameraControlImpl* aCameraControl)
-    : mCameraControl(aCameraControl)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~StopPreviewTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-    mCameraControl->StopPreviewImpl(this);
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-
-    return NS_OK;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-};
-
-// Get the video mode preview stream.
-class GetPreviewStreamVideoModeTask : public nsRunnable
-{
-public:
-  GetPreviewStreamVideoModeTask(CameraControlImpl* aCameraControl, idl::CameraRecorderOptions aOptions,  nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError)
-    : mCameraControl(aCameraControl)
-    , mOptions(aOptions)
-    , mOnSuccessCb(new nsMainThreadPtrHolder<nsICameraPreviewStreamCallback>(onSuccess))
-    , mOnErrorCb(new nsMainThreadPtrHolder<nsICameraErrorCallback>(onError))
-  { }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGI("%s:%d -- BEFORE IMPL\n", __func__, __LINE__);
-    nsresult rv = mCameraControl->GetPreviewStreamVideoModeImpl(this);
-    DOM_CAMERA_LOGI("%s:%d -- AFTER IMPL : rv = %d\n", __func__, __LINE__, rv);
-
-    if (NS_FAILED(rv)) {
-      nsCOMPtr<nsIRunnable> cameraErrorResult = new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId());
-      rv = NS_DispatchToMainThread(cameraErrorResult);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    return NS_OK;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  idl::CameraRecorderOptions mOptions;
-  nsMainThreadPtrHandle<nsICameraPreviewStreamCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-};
-
-// Return the result of releasing the camera hardware.  Runs on the main thread.
-class ReleaseHardwareResult : public nsRunnable
-{
-public:
-  ReleaseHardwareResult(nsMainThreadPtrHandle<nsICameraReleaseCallback> onSuccess, uint64_t aWindowId)
-    : mOnSuccessCb(onSuccess)
-    , mWindowId(aWindowId)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~ReleaseHardwareResult()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnSuccessCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      mOnSuccessCb->HandleEvent();
-    }
-    return NS_OK;
-  }
-
-protected:
-  nsMainThreadPtrHandle<nsICameraReleaseCallback> mOnSuccessCb;
-  uint64_t mWindowId;
-};
-
-// Release the camera hardware.
-class ReleaseHardwareTask : public nsRunnable
-{
-public:
-  ReleaseHardwareTask(CameraControlImpl* aCameraControl, nsICameraReleaseCallback* onSuccess, nsICameraErrorCallback* onError)
-    : mCameraControl(aCameraControl)
-    , mOnSuccessCb(new nsMainThreadPtrHolder<nsICameraReleaseCallback>(onSuccess))
-    , mOnErrorCb(new nsMainThreadPtrHolder<nsICameraErrorCallback>(onError))
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~ReleaseHardwareTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-    nsresult rv = mCameraControl->ReleaseHardwareImpl(this);
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-
-    if (NS_FAILED(rv)) {
-      nsCOMPtr<nsIRunnable> cameraErrorResult = new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId());
-      rv = NS_DispatchToMainThread(cameraErrorResult);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    return rv;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  nsMainThreadPtrHandle<nsICameraReleaseCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-};
-
-// Report that the video recorder state has changed.
-class CameraRecorderStateChange : public nsRunnable
-{
-public:
-  CameraRecorderStateChange(nsMainThreadPtrHandle<nsICameraRecorderStateChange> onStateChange, const nsString& aStateMsg, int32_t aStatus, int32_t aTrackNumber, uint64_t aWindowId)
-    : mOnStateChangeCb(onStateChange)
-    , mStateMsg(aStateMsg)
-    , mStatus(aStatus)
-    , mTrackNumber(aTrackNumber)
-    , mWindowId(aWindowId)
-  { }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnStateChangeCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      // For now, just pass the state message and swallow mStatus and mTrackNumber
-      mOnStateChangeCb->HandleStateChange(mStateMsg);
-    }
-    return NS_OK;
-  }
-
-protected:
-  nsMainThreadPtrHandle<nsICameraRecorderStateChange> mOnStateChangeCb;
-  const nsString mStateMsg;
-  int32_t mStatus;
-  int32_t mTrackNumber;
-  uint64_t mWindowId;
-};
-
-// Report that the preview stream state has changed.
-class CameraPreviewStateChange : public nsRunnable
-{
-public:
-  CameraPreviewStateChange(nsMainThreadPtrHandle<nsICameraPreviewStateChange> onStateChange, const nsString& aStateMsg, uint64_t aWindowId)
-    : mOnStateChangeCb(onStateChange)
-    , mStateMsg(aStateMsg)
-    , mWindowId(aWindowId)
-  { }
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnStateChangeCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      mOnStateChangeCb->HandleStateChange(mStateMsg);
-    }
-    return NS_OK;
-  }
-
-protected:
-  nsMainThreadPtrHandle<nsICameraPreviewStateChange> mOnStateChangeCb;
-  const nsString mStateMsg;
-  uint64_t mWindowId;
-};
-
 } // namespace mozilla
 
 #endif // DOM_CAMERA_CAMERACONTROLIMPL_H
new file mode 100644
--- /dev/null
+++ b/dom/camera/CameraControlListener.h
@@ -0,0 +1,100 @@
+/* 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_CAMERACONTROLLISTENER_H
+#define DOM_CAMERA_CAMERACONTROLLISTENER_H
+
+#include <stdint.h>
+#include "ICameraControl.h"
+
+namespace mozilla {
+
+namespace layers {
+  class Image;
+}
+
+class CameraControlListener
+{
+public:
+  virtual ~CameraControlListener() { }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CameraControlListener);
+
+  enum HardwareState
+  {
+    kHardwareOpen,
+    kHardwareClosed
+  };
+  virtual void OnHardwareStateChange(HardwareState aState) { }
+
+  enum PreviewState
+  {
+    kPreviewStopped,
+    kPreviewPaused,
+    kPreviewStarted
+  };
+  virtual void OnPreviewStateChange(PreviewState aState) { }
+
+  enum RecorderState
+  {
+    kRecorderStopped,
+    kRecorderStarted,
+#ifdef MOZ_B2G_CAMERA
+    kFileSizeLimitReached,
+    kVideoLengthLimitReached,
+    kTrackCompleted,
+    kTrackFailed,
+    kMediaRecorderFailed,
+    kMediaServerFailed
+#endif
+  };
+  enum { kNoTrackNumber = -1 };
+  virtual void OnRecorderStateChange(RecorderState aState, int32_t aStatus, int32_t aTrackNum) { }
+
+  virtual void OnShutter() { }
+  virtual bool OnNewPreviewFrame(layers::Image* aFrame, uint32_t aWidth, uint32_t aHeight)
+  {
+    return false;
+  }
+
+  class CameraListenerConfiguration : public ICameraControl::Configuration
+  {
+  public:
+    uint32_t mMaxMeteringAreas;
+    uint32_t mMaxFocusAreas;
+  };
+  virtual void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration) { }
+
+  virtual void OnAutoFocusComplete(bool aAutoFocusSucceeded) { }
+  virtual void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType) { }
+
+  enum CameraErrorContext
+  {
+    kInGetCamera,
+    kInAutoFocus,
+    kInTakePicture,
+    kInStartRecording,
+    kInStopRecording,
+    kInSetConfiguration,
+    kInReleaseHardware,
+    kInStartPreview,
+    kInStopPreview,
+    kInUnspecified
+  };
+  enum CameraError
+  {
+    kErrorApiFailed,
+    kErrorInitFailed,
+    kErrorInvalidConfiguration,
+    kErrorServiceFailed,
+    kErrorSetPictureSizeFailed,
+    kErrorSetThumbnailSizeFailed,
+    kErrorUnknown
+  };
+  virtual void OnError(CameraErrorContext aContext, CameraError aError) { }
+};
+
+} // namespace mozilla
+
+#endif // DOM_CAMERA_CAMERACONTROLLISTENER_H
--- a/dom/camera/CameraRecorderProfiles.cpp
+++ b/dom/camera/CameraRecorderProfiles.cpp
@@ -1,14 +1,14 @@
 /* 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 "CameraRecorderProfiles.h"
 #include "CameraCommon.h"
 
 using namespace mozilla;
 
 /**
  * Video profile implementation.
  */
 RecorderVideoProfile::RecorderVideoProfile(uint32_t aCameraId, uint32_t aQualityIndex)
--- a/dom/camera/CameraRecorderProfiles.h
+++ b/dom/camera/CameraRecorderProfiles.h
@@ -5,20 +5,18 @@
 #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 "DictionaryHelpers.h"
 #include "CameraCommon.h"
 
-
 namespace mozilla {
 
 class CameraControlImpl;
 
 class RecorderVideoProfile
 {
 public:
   RecorderVideoProfile(uint32_t aCameraId, uint32_t aQualityIndex);
--- a/dom/camera/DOMCameraCapabilities.cpp
+++ b/dom/camera/DOMCameraCapabilities.cpp
@@ -1,424 +1,282 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
+ * 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 <cstring>
-#include <cstdlib>
-#include "base/basictypes.h"
-#include "nsDOMClassInfo.h"
-#include "jsapi.h"
-#include "CameraRecorderProfiles.h"
-#include "DOMCameraControl.h"
 #include "DOMCameraCapabilities.h"
+#include "nsPIDOMWindow.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/CameraManagerBinding.h"
+#include "mozilla/dom/CameraCapabilitiesBinding.h"
 #include "CameraCommon.h"
+#include "ICameraControl.h"
+#include "CameraRecorderProfiles.h"
 
-using namespace mozilla;
-using namespace mozilla::dom;
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CameraCapabilities)
 
-DOMCI_DATA(CameraCapabilities, nsICameraCapabilities)
+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
+
+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
 
-NS_INTERFACE_MAP_BEGIN(DOMCameraCapabilities)
+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_ENTRY(nsICameraCapabilities)
-  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraCapabilities)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_ADDREF(DOMCameraCapabilities)
-NS_IMPL_RELEASE(DOMCameraCapabilities)
+CameraCapabilities::CameraCapabilities(nsPIDOMWindow* aWindow)
+  : mRecorderProfiles(JS::UndefinedValue())
+  , mWindow(aWindow)
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  MOZ_COUNT_CTOR(CameraCapabilities);
+  mozilla::HoldJSObjects(this);
+  SetIsDOMBinding();
+}
 
-static nsresult
-ParseZoomRatioItemAndAdd(JSContext* aCx, JS::Handle<JSObject*> aArray,
-                         uint32_t aIndex, const char* aStart, char** aEnd)
+CameraCapabilities::~CameraCapabilities()
 {
-  if (!*aEnd) {
-    // make 'aEnd' follow the same semantics as strchr().
-    aEnd = nullptr;
-  }
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  mRecorderProfiles = JS::UndefinedValue();
+  mozilla::DropJSObjects(this);
+  MOZ_COUNT_DTOR(CameraCapabilities);
+}
 
-  /**
-   * The by-100 divisor is Gonk-specific.  For now, assume other platforms
-   * return actual fractional multipliers.
-   */
-  double d = strtod(aStart, aEnd);
-#if MOZ_WIDGET_GONK
-  d /= 100;
-#endif
+JSObject*
+CameraCapabilities::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return CameraCapabilitiesBinding::Wrap(aCx, aScope, this);
+}
 
-  if (!JS_SetElement(aCx, aArray, aIndex, d)) {
-    return NS_ERROR_FAILURE;
+#define LOG_IF_ERROR(rv, param)                               \
+  do {                                                        \
+    if (NS_FAILED(rv)) {                                      \
+      DOM_CAMERA_LOGW("Error %x trying to get " #param "\n",  \
+        (rv));                                                \
+    }                                                         \
+  } while(0)
+
+nsresult
+CameraCapabilities::TranslateToDictionary(ICameraControl* aCameraControl,
+                                          uint32_t aKey, nsTArray<CameraSize>& aSizes)
+{
+  nsresult rv;
+  nsTArray<ICameraControl::Size> sizes;
+
+  rv = aCameraControl->Get(aKey, sizes);
+  if (NS_FAILED(rv)) {
+    return rv;
   }
 
-  return NS_OK;
-}
-
-static nsresult
-ParseStringItemAndAdd(JSContext* aCx, JS::Handle<JSObject*> aArray,
-                      uint32_t aIndex, const char* aStart, char** aEnd)
-{
-  JS::Rooted<JSString*> s(aCx);
-
-  if (*aEnd) {
-    s = JS_NewStringCopyN(aCx, aStart, *aEnd - aStart);
-  } else {
-    s = JS_NewStringCopyZ(aCx, aStart);
-  }
-  if (!s) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (!JS_SetElement(aCx, aArray, aIndex, s)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return NS_OK;
-}
-
-static nsresult
-ParseDimensionItemAndAdd(JSContext* aCx, JS::Handle<JSObject*> aArray,
-                         uint32_t aIndex, const char* aStart, char** aEnd)
-{
-  char* x;
-
-  if (!*aEnd) {
-    // make 'aEnd' follow the same semantics as strchr().
-    aEnd = nullptr;
-  }
-
-  JS::Rooted<JS::Value> w(aCx, INT_TO_JSVAL(strtol(aStart, &x, 10)));
-  JS::Rooted<JS::Value> h(aCx, INT_TO_JSVAL(strtol(x + 1, aEnd, 10)));
-
-  JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
-  if (!o) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (!JS_SetProperty(aCx, o, "width", w)) {
-    return NS_ERROR_FAILURE;
-  }
-  if (!JS_SetProperty(aCx, o, "height", h)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  if (!JS_SetElement(aCx, aArray, aIndex, o)) {
-    return NS_ERROR_FAILURE;
+  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;
 }
 
 nsresult
-DOMCameraCapabilities::ParameterListToNewArray(JSContext* aCx,
-                                               JS::MutableHandle<JSObject*> aArray,
-                                               uint32_t aKey,
-                                               ParseItemAndAddFunc aParseItemAndAdd)
+CameraCapabilities::Populate(ICameraControl* aCameraControl)
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+  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);
 
-  const char* value = mCamera->GetParameterConstChar(aKey);
-  if (!value) {
-    // in case we get nonsense data back
-    aArray.set(nullptr);
-    return NS_OK;
-  }
+  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;
+
+  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);
 
-  aArray.set(JS_NewArrayObject(aCx, 0));
-  if (!aArray) {
-    return NS_ERROR_OUT_OF_MEMORY;
+  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 (%d)\n", rv);
+      return rv;
+    }
+
+    mRecorderProfiles = JS::ObjectValue(*o);
   }
 
-  const char* p = value;
-  uint32_t index = 0;
-  nsresult rv;
-  char* q;
-
-  while (p) {
-    /**
-     * In C's string.h, strchr() is declared as returning 'char*'; in C++'s
-     * cstring, it is declared as returning 'const char*', _except_ in MSVC,
-     * where the C version is declared to return const like the C++ version.
-     *
-     * Unfortunately, for both cases, strtod() and strtol() take a 'char**' as
-     * the end-of-conversion pointer, so we need to cast away strchr()'s
-     * const-ness here to make the MSVC build everything happy.
-     */
-    q = const_cast<char*>(strchr(p, ','));
-    if (q != p) { // skip consecutive delimiters, just in case
-      rv = aParseItemAndAdd(aCx, aArray, index, p, &q);
-      NS_ENSURE_SUCCESS(rv, rv);
-      ++index;
-    }
-    p = q;
-    if (p) {
-      ++p;
-    }
-  }
-
-  return JS_FreezeObject(aCx, aArray) ? NS_OK : NS_ERROR_FAILURE;
-}
-
-nsresult
-DOMCameraCapabilities::StringListToNewObject(JSContext* aCx,
-                                             JS::MutableHandle<JS::Value> aArray,
-                                             uint32_t aKey)
-{
-  JS::Rooted<JSObject*> array(aCx);
-
-  nsresult rv = ParameterListToNewArray(aCx, &array, aKey, ParseStringItemAndAdd);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aArray.setObjectOrNull(array);
-  return NS_OK;
-}
-
-nsresult
-DOMCameraCapabilities::DimensionListToNewObject(JSContext* aCx,
-                                                JS::MutableHandle<JS::Value> aArray,
-                                                uint32_t aKey)
-{
-  JS::Rooted<JSObject*> array(aCx);
-
-  nsresult rv = ParameterListToNewArray(aCx, &array, aKey, ParseDimensionItemAndAdd);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aArray.setObjectOrNull(array);
+  // For now, always return success, since the presence or absence of capabilities
+  // indicates whether or not they are supported.
   return NS_OK;
 }
 
-/* readonly attribute jsval previewSizes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetPreviewSizes(JSContext* cx,
-                                       JS::MutableHandle<JS::Value> aPreviewSizes)
+void
+CameraCapabilities::GetPreviewSizes(nsTArray<dom::CameraSize>& retval) const
 {
-  return DimensionListToNewObject(cx, aPreviewSizes, CAMERA_PARAM_SUPPORTED_PREVIEWSIZES);
-}
-
-/* readonly attribute jsval pictureSizes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetPictureSizes(JSContext* cx,
-                                       JS::MutableHandle<JS::Value> aPictureSizes)
-{
-  return DimensionListToNewObject(cx, aPictureSizes, CAMERA_PARAM_SUPPORTED_PICTURESIZES);
+  retval = mPreviewSizes;
 }
 
-/* readonly attribute jsval thumbnailSizes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetThumbnailSizes(JSContext* cx,
-                                         JS::MutableHandle<JS::Value> aThumbnailSizes)
+void
+CameraCapabilities::GetPictureSizes(nsTArray<dom::CameraSize>& retval) const
 {
-  return DimensionListToNewObject(cx, aThumbnailSizes, CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES);
+  retval = mPictureSizes;
 }
 
-/* readonly attribute jsval fileFormats; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetFileFormats(JSContext* cx,
-                                      JS::MutableHandle<JS::Value> aFileFormats)
+void
+CameraCapabilities::GetThumbnailSizes(nsTArray<dom::CameraSize>& retval) const
 {
-  return StringListToNewObject(cx, aFileFormats, CAMERA_PARAM_SUPPORTED_PICTUREFORMATS);
-}
-
-/* readonly attribute jsval whiteBalanceModes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetWhiteBalanceModes(JSContext* cx,
-                                            JS::MutableHandle<JS::Value> aWhiteBalanceModes)
-{
-  return StringListToNewObject(cx, aWhiteBalanceModes, CAMERA_PARAM_SUPPORTED_WHITEBALANCES);
-}
-
-/* readonly attribute jsval sceneModes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetSceneModes(JSContext* cx,
-                                     JS::MutableHandle<JS::Value> aSceneModes)
-{
-  return StringListToNewObject(cx, aSceneModes, CAMERA_PARAM_SUPPORTED_SCENEMODES);
+  retval = mThumbnailSizes;
 }
 
-/* readonly attribute jsval effects; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetEffects(JSContext* cx,
-                                  JS::MutableHandle<JS::Value> aEffects)
+void
+CameraCapabilities::GetVideoSizes(nsTArray<dom::CameraSize>& retval) const
 {
-  return StringListToNewObject(cx, aEffects, CAMERA_PARAM_SUPPORTED_EFFECTS);
+  retval = mVideoSizes;
 }
 
-/* readonly attribute jsval flashModes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetFlashModes(JSContext* cx,
-                                     JS::MutableHandle<JS::Value> aFlashModes)
+void
+CameraCapabilities::GetFileFormats(nsTArray<nsString>& retval) const
 {
-  return StringListToNewObject(cx, aFlashModes, CAMERA_PARAM_SUPPORTED_FLASHMODES);
-}
-
-/* readonly attribute jsval focusModes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetFocusModes(JSContext* cx,
-                                     JS::MutableHandle<JS::Value> aFocusModes)
-{
-  return StringListToNewObject(cx, aFocusModes, CAMERA_PARAM_SUPPORTED_FOCUSMODES);
+  retval = mFileFormats;
 }
 
-/* readonly attribute long maxFocusAreas; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetMaxFocusAreas(JSContext* cx, int32_t* aMaxFocusAreas)
+void
+CameraCapabilities::GetWhiteBalanceModes(nsTArray<nsString>& retval) const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
-
-  const char* value = mCamera->GetParameterConstChar(CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS);
-  if (!value) {
-    // in case we get nonsense data back
-    *aMaxFocusAreas = 0;
-    return NS_OK;
-  }
-
-  *aMaxFocusAreas = atoi(value);
-  return NS_OK;
+  retval = mWhiteBalanceModes;
 }
 
-/* readonly attribute double minExposureCompensation; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetMinExposureCompensation(JSContext* cx, double* aMinExposureCompensation)
+void
+CameraCapabilities::GetSceneModes(nsTArray<nsString>& retval) const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+  retval = mSceneModes;
+}
 
-  const char* value = mCamera->GetParameterConstChar(CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION);
-  if (!value) {
-    // in case we get nonsense data back
-    *aMinExposureCompensation = 0;
-    return NS_OK;
-  }
-
-  *aMinExposureCompensation = atof(value);
-  return NS_OK;
+void
+CameraCapabilities::GetEffects(nsTArray<nsString>& retval) const
+{
+  retval = mEffects;
 }
 
-/* readonly attribute double maxExposureCompensation; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetMaxExposureCompensation(JSContext* cx, double* aMaxExposureCompensation)
+void
+CameraCapabilities::GetFlashModes(nsTArray<nsString>& retval) const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
-
-  const char* value = mCamera->GetParameterConstChar(CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION);
-  if (!value) {
-    // in case we get nonsense data back
-    *aMaxExposureCompensation = 0;
-    return NS_OK;
-  }
-
-  *aMaxExposureCompensation = atof(value);
-  return NS_OK;
+  retval = mFlashModes;
 }
 
-/* readonly attribute double stepExposureCompensation; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetStepExposureCompensation(JSContext* cx, double* aStepExposureCompensation)
+void
+CameraCapabilities::GetFocusModes(nsTArray<nsString>& retval) const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
-
-  const char* value = mCamera->GetParameterConstChar(CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP);
-  if (!value) {
-    // in case we get nonsense data back
-    *aStepExposureCompensation = 0;
-    return NS_OK;
-  }
-
-  *aStepExposureCompensation = atof(value);
-  return NS_OK;
+  retval = mFocusModes;
 }
 
-/* readonly attribute long maxMeteringAreas; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetMaxMeteringAreas(JSContext* cx, int32_t* aMaxMeteringAreas)
+void
+CameraCapabilities::GetZoomRatios(nsTArray<double>& retval) const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+  retval = mZoomRatios;
+}
 
-  const char* value = mCamera->GetParameterConstChar(CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS);
-  if (!value) {
-    // in case we get nonsense data back
-    *aMaxMeteringAreas = 0;
-    return NS_OK;
-  }
-
-  *aMaxMeteringAreas = atoi(value);
-  return NS_OK;
+uint32_t
+CameraCapabilities::MaxFocusAreas() const
+{
+  return mMaxFocusAreas;
 }
 
-/* readonly attribute jsval zoomRatios; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetZoomRatios(JSContext* cx, JS::MutableHandle<JS::Value> aZoomRatios)
+uint32_t
+CameraCapabilities::MaxMeteringAreas() const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+  return mMaxMeteringAreas;
+}
 
-  const char* value = mCamera->GetParameterConstChar(CAMERA_PARAM_SUPPORTED_ZOOM);
-  if (!value || strcmp(value, "true") != 0) {
-    // if zoom is not supported, return a null object
-    aZoomRatios.setNull();
-    return NS_OK;
-  }
-
-  JS::Rooted<JSObject*> array(cx);
-
-  nsresult rv = ParameterListToNewArray(cx, &array, CAMERA_PARAM_SUPPORTED_ZOOMRATIOS, ParseZoomRatioItemAndAdd);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aZoomRatios.setObjectOrNull(array);
-  return NS_OK;
+double
+CameraCapabilities::MinExposureCompensation() const
+{
+  return mMinExposureCompensation;
 }
 
-/* readonly attribute jsval videoSizes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetVideoSizes(JSContext* cx, JS::MutableHandle<JS::Value> aVideoSizes)
+double
+CameraCapabilities::MaxExposureCompensation() const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
-
-  nsTArray<mozilla::idl::CameraSize> sizes;
-  nsresult rv = mCamera->GetVideoSizes(sizes);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (sizes.Length() == 0) {
-    // video recording not supported, return null
-    aVideoSizes.setNull();
-    return NS_OK;
-  }
-
-  JS::Rooted<JSObject*> array(cx, JS_NewArrayObject(cx, 0));
-  if (!array) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  for (uint32_t i = 0; i < sizes.Length(); ++i) {
-    JS::Rooted<JSObject*> o(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
-    JS::Rooted<JS::Value> v(cx, INT_TO_JSVAL(sizes[i].width));
-    if (!JS_SetProperty(cx, o, "width", v)) {
-      return NS_ERROR_FAILURE;
-    }
-    v = INT_TO_JSVAL(sizes[i].height);
-    if (!JS_SetProperty(cx, o, "height", v)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    if (!JS_SetElement(cx, array, i, o)) {
-      return NS_ERROR_FAILURE;
-    }
-  }
-
-  aVideoSizes.setObject(*array);
-  return NS_OK;
+  return mMaxExposureCompensation;
 }
 
-/* readonly attribute jsval recorderProfiles; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetRecorderProfiles(JSContext* cx, JS::MutableHandle<JS::Value> aRecorderProfiles)
+double
+CameraCapabilities::ExposureCompensationStep() const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+  return mExposureCompensationStep;
+}
 
-  nsRefPtr<RecorderProfileManager> profileMgr = mCamera->GetRecorderProfileManager();
-  if (!profileMgr) {
-    aRecorderProfiles.setNull();
-    return NS_OK;
-  }
+JS::Value
+CameraCapabilities::RecorderProfiles(JSContext* aCx) const
+{
+  return mRecorderProfiles;
+}
 
-  JS::Rooted<JSObject*> o(cx);
-  nsresult rv = profileMgr->GetJsObject(cx, o.address());
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aRecorderProfiles.setObject(*o);
-  return NS_OK;
-}
+} // namespace dom
+} // namespace mozilla
--- a/dom/camera/DOMCameraCapabilities.h
+++ b/dom/camera/DOMCameraCapabilities.h
@@ -1,59 +1,96 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
+ * 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__
 
-#ifndef DOM_CAMERA_DOMCAMERACAPABILITIES_H
-#define DOM_CAMERA_DOMCAMERACAPABILITIES_H
+#include "nsString.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/CameraManagerBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
 
-#include "ICameraControl.h"
-#include "nsAutoPtr.h"
-#include "CameraCommon.h"
+struct JSContext;
+class nsPIDOMWindow;
 
 namespace mozilla {
 
-typedef nsresult (*ParseItemAndAddFunc)(JSContext* aCx, JS::Handle<JSObject*> aArray,
-                                        uint32_t aIndex, const char* aStart, char** aEnd);
+class ICameraControl;
+class RecorderProfileManager;
 
-class DOMCameraCapabilities MOZ_FINAL : public nsICameraCapabilities
+namespace dom {
+
+class CameraCapabilities MOZ_FINAL : public nsISupports
+                                   , public nsWrapperCache
 {
 public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSICAMERACAPABILITIES
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CameraCapabilities)
+
+  CameraCapabilities(nsPIDOMWindow* aWindow);
+  ~CameraCapabilities();
 
-  DOMCameraCapabilities(ICameraControl* aCamera)
-    : mCamera(aCamera)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
+  nsresult Populate(ICameraControl* aCameraControl);
+
+  nsPIDOMWindow* GetParentObject() const { return mWindow; }
+
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
-  nsresult ParameterListToNewArray(
-    JSContext* cx,
-    JS::MutableHandle<JSObject*> aArray,
-    uint32_t aKey,
-    ParseItemAndAddFunc aParseItemAndAdd
-  );
-  nsresult StringListToNewObject(JSContext* aCx,
-                                 JS::MutableHandle<JS::Value> aArray,
-                                 uint32_t aKey);
-  nsresult DimensionListToNewObject(JSContext* aCx,
-                                    JS::MutableHandle<JS::Value> aArray,
-                                    uint32_t aKey);
-
-private:
-  DOMCameraCapabilities(const DOMCameraCapabilities&) MOZ_DELETE;
-  DOMCameraCapabilities& operator=(const DOMCameraCapabilities&) MOZ_DELETE;
+  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;
+  double MinExposureCompensation() const;
+  double MaxExposureCompensation() const;
+  double ExposureCompensationStep() const;
+  JS::Value RecorderProfiles(JSContext* cx) const;
 
 protected:
-  /* additional members */
-  ~DOMCameraCapabilities()
-  {
-    // destructor code
-    DOM_CAMERA_LOGT("%s:%d : this=%p, mCamera=%p\n", __func__, __LINE__, this, mCamera.get());
-  }
+  nsresult TranslateToDictionary(ICameraControl* aCameraControl,
+                                 uint32_t aKey, nsTArray<CameraSize>& aSizes);
+
+  nsTArray<CameraSize> mPreviewSizes;
+  nsTArray<CameraSize> mPictureSizes;
+  nsTArray<CameraSize> mThumbnailSizes;
+  nsTArray<CameraSize> mVideoSizes;
 
-  nsRefPtr<ICameraControl> mCamera;
+  nsTArray<nsString> mFileFormats;
+  nsTArray<nsString> mWhiteBalanceModes;
+  nsTArray<nsString> mSceneModes;
+  nsTArray<nsString> mEffects;
+  nsTArray<nsString> mFlashModes;
+  nsTArray<nsString> mFocusModes;
+
+  nsTArray<double> mZoomRatios;
+
+  uint32_t mMaxFocusAreas;
+  uint32_t mMaxMeteringAreas;
+
+  double mMinExposureCompensation;
+  double mMaxExposureCompensation;
+  double mExposureCompensationStep;
+
+  nsRefPtr<RecorderProfileManager> mRecorderProfileManager;
+  JS::Heap<JS::Value> mRecorderProfiles;
+
+  nsRefPtr<nsPIDOMWindow> mWindow;
 };
 
+} // namespace dom
 } // namespace mozilla
 
-#endif // DOM_CAMERA_DOMCAMERACAPABILITIES_H
+#endif // mozilla_dom_CameraCapabilities_h__
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -1,170 +1,478 @@
 /* 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 "DOMCameraControl.h"
 #include "base/basictypes.h"
 #include "nsCOMPtr.h"
 #include "nsDOMClassInfo.h"
 #include "nsHashPropertyBag.h"
 #include "nsThread.h"
 #include "DeviceStorage.h"
 #include "DeviceStorageFileDescriptor.h"
-#include "mozilla/dom/CameraControlBinding.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/MediaManager.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "nsIAppsService.h"
 #include "nsIObserverService.h"
 #include "nsIDOMDeviceStorage.h"
+#include "nsIDOMEventListener.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsXULAppAPI.h"
 #include "DOMCameraManager.h"
 #include "DOMCameraCapabilities.h"
-#include "DOMCameraControl.h"
 #include "CameraCommon.h"
+#include "DictionaryHelpers.h"
+#include "nsGlobalWindow.h"
+#include "CameraPreviewMediaStream.h"
+#include "mozilla/dom/CameraControlBinding.h"
 #include "mozilla/dom/CameraManagerBinding.h"
+#include "mozilla/dom/CameraCapabilitiesBinding.h"
 #include "mozilla/dom/BindingUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::idl;
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_2(nsDOMCameraControl, mDOMCapabilities, mWindow)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCameraControl)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMCameraControl)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
+  NS_INTERFACE_MAP_ENTRY(nsIDOMMediaStream)
+NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
+
+NS_IMPL_ADDREF_INHERITED(nsDOMCameraControl, DOMMediaStream)
+NS_IMPL_RELEASE_INHERITED(nsDOMCameraControl, DOMMediaStream)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMCameraControl)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMCameraControl, DOMMediaStream)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCapabilities)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mGetCameraOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mGetCameraOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAutoFocusOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAutoFocusOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTakePictureOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTakePictureOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartRecordingOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartRecordingOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mReleaseOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mReleaseOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetConfigurationOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetConfigurationOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnShutterCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnClosedCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnRecorderStateChangeCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnPreviewStateChangeCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMCameraControl, DOMMediaStream)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCapabilities)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGetCameraOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGetCameraOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAutoFocusOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAutoFocusOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTakePictureOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTakePictureOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartRecordingOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartRecordingOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReleaseOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReleaseOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSetConfigurationOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSetConfigurationOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnShutterCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnClosedCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnRecorderStateChangeCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnPreviewStateChangeCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDOMCameraControl)
+
+class mozilla::StartRecordingHelper : public nsIDOMEventListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMEVENTLISTENER
+
+  StartRecordingHelper(nsDOMCameraControl* aDOMCameraControl)
+    : mDOMCameraControl(aDOMCameraControl)
+  {
+    MOZ_COUNT_CTOR(StartRecordingHelper);
+  }
+
+protected:
+  virtual ~StartRecordingHelper()
+  {
+    MOZ_COUNT_DTOR(StartRecordingHelper);
+  }
+
+protected:
+  nsRefPtr<nsDOMCameraControl> mDOMCameraControl;
+};
+
+NS_IMETHODIMP
+StartRecordingHelper::HandleEvent(nsIDOMEvent* aEvent)
+{
+  nsString eventType;
+  aEvent->GetType(eventType);
+
+  mDOMCameraControl->OnCreatedFileDescriptor(eventType.EqualsLiteral("success"));
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS0(mozilla::StartRecordingHelper)
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCameraControl)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCameraControl)
+nsDOMCameraControl::DOMCameraConfiguration::DOMCameraConfiguration()
+  : CameraConfiguration()
+  , mMaxFocusAreas(0)
+  , mMaxMeteringAreas(0)
+{
+  MOZ_COUNT_CTOR(nsDOMCameraControl::DOMCameraConfiguration);
+}
+
+nsDOMCameraControl::DOMCameraConfiguration::DOMCameraConfiguration(const CameraConfiguration& aConfiguration)
+  : CameraConfiguration(aConfiguration)
+  , mMaxFocusAreas(0)
+  , mMaxMeteringAreas(0)
+{
+  MOZ_COUNT_CTOR(nsDOMCameraControl::DOMCameraConfiguration);
+}
+
+nsDOMCameraControl::DOMCameraConfiguration::~DOMCameraConfiguration()
+{
+  MOZ_COUNT_DTOR(nsDOMCameraControl::DOMCameraConfiguration);
+}
+
+nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
+                                       const CameraConfiguration& aInitialConfig,
+                                       GetCameraCallback* aOnSuccess,
+                                       CameraErrorCallback* aOnError,
+                                       nsPIDOMWindow* aWindow)
+  : DOMMediaStream()
+  , mCameraControl(nullptr)
+  , mAudioChannelAgent(nullptr)
+  , mGetCameraOnSuccessCb(aOnSuccess)
+  , mGetCameraOnErrorCb(aOnError)
+  , mAutoFocusOnSuccessCb(nullptr)
+  , mAutoFocusOnErrorCb(nullptr)
+  , mTakePictureOnSuccessCb(nullptr)
+  , mTakePictureOnErrorCb(nullptr)
+  , mStartRecordingOnSuccessCb(nullptr)
+  , mStartRecordingOnErrorCb(nullptr)
+  , mReleaseOnSuccessCb(nullptr)
+  , mReleaseOnErrorCb(nullptr)
+  , mSetConfigurationOnSuccessCb(nullptr)
+  , mSetConfigurationOnErrorCb(nullptr)
+  , mOnShutterCb(nullptr)
+  , mOnClosedCb(nullptr)
+  , mOnRecorderStateChangeCb(nullptr)
+  , mOnPreviewStateChangeCb(nullptr)
+  , mWindow(aWindow)
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  mInput = new CameraPreviewMediaStream(this);
+
+  SetIsDOMBinding();
+
+  nsRefPtr<DOMCameraConfiguration> initialConfig =
+    new DOMCameraConfiguration(aInitialConfig);
+
+  // Create and initialize the underlying camera.
+  ICameraControl::Configuration config;
+
+  switch (aInitialConfig.mMode) {
+    case CameraMode::Picture:
+      config.mMode = ICameraControl::kPictureMode;
+      break;
+
+    case CameraMode::Video:
+      config.mMode = ICameraControl::kVideoMode;
+      break;
+
+    default:
+      MOZ_ASSUME_UNREACHABLE("Unanticipated camera mode!");
+  }
+
+  config.mPreviewSize.width = aInitialConfig.mPreviewSize.mWidth;
+  config.mPreviewSize.height = aInitialConfig.mPreviewSize.mHeight;
+  config.mRecorderProfile = aInitialConfig.mRecorderProfile;
+
+  mCameraControl = ICameraControl::Create(aCameraId, &config);
+  mCurrentConfiguration = initialConfig.forget();
+
+  // Attach our DOM-facing media stream to our viewfinder stream.
+  mStream = mInput;
+  MOZ_ASSERT(mWindow, "Shouldn't be created with a null window!");
+  if (mWindow->GetExtantDoc()) {
+    CombineWithPrincipal(mWindow->GetExtantDoc()->NodePrincipal());
+  }
+
+  // Register a listener for camera events.
+  mListener = new DOMCameraControlListener(this, mInput);
+  mCameraControl->AddListener(mListener);
+}
 
 nsDOMCameraControl::~nsDOMCameraControl()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
 }
 
 JSObject*
 nsDOMCameraControl::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return CameraControlBinding::Wrap(aCx, aScope, this);
 }
 
-nsICameraCapabilities*
-nsDOMCameraControl::Capabilities()
+bool
+nsDOMCameraControl::IsWindowStillActive()
+{
+  return nsDOMCameraManager::IsWindowStillActive(mWindow->WindowID());
+}
+
+// JS-to-native helpers
+// Setter for weighted regions: { top, bottom, left, right, weight }
+nsresult
+nsDOMCameraControl::Set(JSContext* aCx, uint32_t aKey, const JS::Value& aValue, uint32_t aLimit)
 {
-  if (!mDOMCapabilities) {
-    mDOMCapabilities = new DOMCameraCapabilities(mCameraControl);
+  if (aLimit == 0) {
+    DOM_CAMERA_LOGI("%s:%d : aLimit = 0, nothing to do\n", __func__, __LINE__);
+    return NS_OK;
+  }
+
+  if (!aValue.isObject()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  uint32_t length = 0;
+
+  JS::Rooted<JSObject*> regions(aCx, &aValue.toObject());
+  if (!JS_GetArrayLength(aCx, regions, &length)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  DOM_CAMERA_LOGI("%s:%d : got %d regions (limited to %d)\n", __func__, __LINE__, length, aLimit);
+  if (length > aLimit) {
+    length = aLimit;
   }
 
-  return mDOMCapabilities;
+  nsTArray<ICameraControl::Region> regionArray;
+  regionArray.SetCapacity(length);
+
+  for (uint32_t i = 0; i < length; ++i) {
+    JS::Rooted<JS::Value> v(aCx);
+
+    if (!JS_GetElement(aCx, regions, i, &v)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    CameraRegion region;
+    if (!region.Init(aCx, v)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    ICameraControl::Region* r = regionArray.AppendElement();
+    r->top = region.mTop;
+    r->left = region.mLeft;
+    r->bottom = region.mBottom;
+    r->right = region.mRight;
+    r->weight = region.mWeight;
+
+    DOM_CAMERA_LOGI("region %d: top=%d, left=%d, bottom=%d, right=%d, weight=%u\n",
+      i,
+      r->top,
+      r->left,
+      r->bottom,
+      r->right,
+      r->weight
+    );
+  }
+  return mCameraControl->Set(aKey, regionArray);
+}
+
+// Getter for weighted regions: { top, bottom, left, right, weight }
+nsresult
+nsDOMCameraControl::Get(JSContext* aCx, uint32_t aKey, JS::Value* aValue)
+{
+  nsTArray<ICameraControl::Region> regionArray;
+
+  nsresult rv = mCameraControl->Get(aKey, regionArray);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, 0));
+  if (!array) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  uint32_t length = regionArray.Length();
+  DOM_CAMERA_LOGI("%s:%d : got %d regions\n", __func__, __LINE__, length);
+
+  for (uint32_t i = 0; i < length; ++i) {
+    ICameraControl::Region* r = &regionArray[i];
+    JS::Rooted<JS::Value> v(aCx);
+
+    JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
+    if (!o) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    DOM_CAMERA_LOGI("top=%d\n", r->top);
+    v = INT_TO_JSVAL(r->top);
+    if (!JS_SetProperty(aCx, o, "top", v)) {
+      return NS_ERROR_FAILURE;
+    }
+    DOM_CAMERA_LOGI("left=%d\n", r->left);
+    v = INT_TO_JSVAL(r->left);
+    if (!JS_SetProperty(aCx, o, "left", v)) {
+      return NS_ERROR_FAILURE;
+    }
+    DOM_CAMERA_LOGI("bottom=%d\n", r->bottom);
+    v = INT_TO_JSVAL(r->bottom);
+    if (!JS_SetProperty(aCx, o, "bottom", v)) {
+      return NS_ERROR_FAILURE;
+    }
+    DOM_CAMERA_LOGI("right=%d\n", r->right);
+    v = INT_TO_JSVAL(r->right);
+    if (!JS_SetProperty(aCx, o, "right", v)) {
+      return NS_ERROR_FAILURE;
+    }
+    DOM_CAMERA_LOGI("weight=%d\n", r->weight);
+    v = INT_TO_JSVAL(r->weight);
+    if (!JS_SetProperty(aCx, o, "weight", v)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    if (!JS_SetElement(aCx, array, i, o)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  *aValue = JS::ObjectValue(*array);
+  return NS_OK;
 }
 
 void
 nsDOMCameraControl::GetEffect(nsString& aEffect, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Get(CAMERA_PARAM_EFFECT, aEffect);
 }
 void
 nsDOMCameraControl::SetEffect(const nsAString& aEffect, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Set(CAMERA_PARAM_EFFECT, aEffect);
 }
 
 void
 nsDOMCameraControl::GetWhiteBalanceMode(nsString& aWhiteBalanceMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Get(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode);
 }
 void
 nsDOMCameraControl::SetWhiteBalanceMode(const nsAString& aWhiteBalanceMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Set(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode);
 }
 
 void
 nsDOMCameraControl::GetSceneMode(nsString& aSceneMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Get(CAMERA_PARAM_SCENEMODE, aSceneMode);
 }
 void
 nsDOMCameraControl::SetSceneMode(const nsAString& aSceneMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Set(CAMERA_PARAM_SCENEMODE, aSceneMode);
 }
 
 void
 nsDOMCameraControl::GetFlashMode(nsString& aFlashMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Get(CAMERA_PARAM_FLASHMODE, aFlashMode);
 }
 void
 nsDOMCameraControl::SetFlashMode(const nsAString& aFlashMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Set(CAMERA_PARAM_FLASHMODE, aFlashMode);
 }
 
 void
 nsDOMCameraControl::GetFocusMode(nsString& aFocusMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSMODE, aFocusMode);
 }
 void
 nsDOMCameraControl::SetFocusMode(const nsAString& aFocusMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Set(CAMERA_PARAM_FOCUSMODE, aFocusMode);
 }
 
 double
 nsDOMCameraControl::GetZoom(ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
+
   double zoom;
-  aRv = mCameraControl->Get(CAMERA_PARAM_ZOOM, &zoom);
+  aRv = mCameraControl->Get(CAMERA_PARAM_ZOOM, zoom);
   return zoom;
 }
 
 void
 nsDOMCameraControl::SetZoom(double aZoom, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Set(CAMERA_PARAM_ZOOM, aZoom);
 }
 
 /* attribute jsval meteringAreas; */
 JS::Value
 nsDOMCameraControl::GetMeteringAreas(JSContext* cx, ErrorResult& aRv)
 {
   JS::Rooted<JS::Value> areas(cx);
-  aRv = mCameraControl->Get(cx, CAMERA_PARAM_METERINGAREAS, areas.address());
+  aRv = Get(cx, CAMERA_PARAM_METERINGAREAS, areas.address());
   return areas;
 }
 
 void
 nsDOMCameraControl::SetMeteringAreas(JSContext* cx, JS::Handle<JS::Value> aMeteringAreas, ErrorResult& aRv)
 {
-  aRv = mCameraControl->SetMeteringAreas(cx, aMeteringAreas);
+  aRv = Set(cx, CAMERA_PARAM_METERINGAREAS, aMeteringAreas,
+            mCurrentConfiguration->mMaxMeteringAreas);
 }
 
 JS::Value
 nsDOMCameraControl::GetFocusAreas(JSContext* cx, ErrorResult& aRv)
 {
   JS::Rooted<JS::Value> value(cx);
-  aRv = mCameraControl->Get(cx, CAMERA_PARAM_FOCUSAREAS, value.address());
+  aRv = Get(cx, CAMERA_PARAM_FOCUSAREAS, value.address());
   return value;
 }
 void
 nsDOMCameraControl::SetFocusAreas(JSContext* cx, JS::Handle<JS::Value> aFocusAreas, ErrorResult& aRv)
 {
-  aRv = mCameraControl->SetFocusAreas(cx, aFocusAreas);
+  aRv = Set(cx, CAMERA_PARAM_FOCUSAREAS, aFocusAreas,
+            mCurrentConfiguration->mMaxFocusAreas);
 }
 
 static nsresult
-GetSize(JSContext* aCx, JS::Value* aValue, const CameraSize& aSize)
+GetSize(JSContext* aCx, JS::Value* aValue, const ICameraControl::Size& aSize)
 {
   JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
   if (!o) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   JS::Rooted<JS::Value> v(aCx);
 
@@ -181,448 +489,464 @@ GetSize(JSContext* aCx, JS::Value* aValu
   return NS_OK;
 }
 
 /* attribute any pictureSize */
 JS::Value
 nsDOMCameraControl::GetPictureSize(JSContext* cx, ErrorResult& aRv)
 {
   JS::Rooted<JS::Value> value(cx);
-  
-  CameraSize size;
+
+  ICameraControl::Size size;
   aRv = mCameraControl->Get(CAMERA_PARAM_PICTURESIZE, size);
   if (aRv.Failed()) {
     return value;
   }
 
   aRv = GetSize(cx, value.address(), size);
   return value;
 }
 void
-nsDOMCameraControl::SetPictureSize(JSContext* cx, JS::Handle<JS::Value> aSize, ErrorResult& aRv)
+nsDOMCameraControl::SetPictureSize(JSContext* aCx, JS::Handle<JS::Value> aSize, ErrorResult& aRv)
 {
   CameraSize size;
-  aRv = size.Init(cx, aSize.address());
-  if (aRv.Failed()) {
+  if (!size.Init(aCx, aSize)) {
+    aRv = NS_ERROR_FAILURE;
     return;
   }
 
-  aRv = mCameraControl->Set(CAMERA_PARAM_PICTURESIZE, size);
+  ICameraControl::Size s = { size.mWidth, size.mHeight };
+  aRv = mCameraControl->Set(CAMERA_PARAM_PICTURESIZE, s);
 }
 
 /* attribute any thumbnailSize */
 JS::Value
-nsDOMCameraControl::GetThumbnailSize(JSContext* cx, ErrorResult& aRv)
+nsDOMCameraControl::GetThumbnailSize(JSContext* aCx, ErrorResult& aRv)
 {
-  JS::Rooted<JS::Value> value(cx);
-  
-  CameraSize size;
+  JS::Rooted<JS::Value> value(aCx);
+
+  ICameraControl::Size size;
   aRv = mCameraControl->Get(CAMERA_PARAM_THUMBNAILSIZE, size);
   if (aRv.Failed()) {
     return value;
   }
 
-  aRv = GetSize(cx, value.address(), size);
+  aRv = GetSize(aCx, value.address(), size);
   return value;
 }
 void
-nsDOMCameraControl::SetThumbnailSize(JSContext* cx, JS::Handle<JS::Value> aSize, ErrorResult& aRv)
+nsDOMCameraControl::SetThumbnailSize(JSContext* aCx, JS::Handle<JS::Value> aSize, ErrorResult& aRv)
 {
   CameraSize size;
-  aRv = size.Init(cx, aSize.address());
-  if (aRv.Failed()) {
+  if (!size.Init(aCx, aSize)) {
+    aRv = NS_ERROR_FAILURE;
     return;
   }
 
-  aRv = mCameraControl->Set(CAMERA_PARAM_THUMBNAILSIZE, size);
+  ICameraControl::Size s = { size.mWidth, size.mHeight };
+  aRv = mCameraControl->Set(CAMERA_PARAM_THUMBNAILSIZE, s);
 }
 
 double
 nsDOMCameraControl::GetFocalLength(ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
+
   double focalLength;
-  aRv = mCameraControl->Get(CAMERA_PARAM_FOCALLENGTH, &focalLength);
+  aRv = mCameraControl->Get(CAMERA_PARAM_FOCALLENGTH, focalLength);
   return focalLength;
 }
 
 double
 nsDOMCameraControl::GetFocusDistanceNear(ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
+
   double distance;
-  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCENEAR, &distance);
+  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCENEAR, distance);
   return distance;
 }
 
 double
 nsDOMCameraControl::GetFocusDistanceOptimum(ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
+
   double distance;
-  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, &distance);
+  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, distance);
   return distance;
 }
 
 double
 nsDOMCameraControl::GetFocusDistanceFar(ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
+
   double distance;
-  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEFAR, &distance);
+  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEFAR, distance);
   return distance;
 }
 
 void
 nsDOMCameraControl::SetExposureCompensation(const Optional<double>& aCompensation, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
+
   if (!aCompensation.WasPassed()) {
     // use NaN to switch the camera back into auto mode
     aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, NAN);
+    return;
   }
 
   aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, aCompensation.Value());
 }
 
 double
 nsDOMCameraControl::GetExposureCompensation(ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
+
   double compensation;
-  aRv = mCameraControl->Get(CAMERA_PARAM_EXPOSURECOMPENSATION, &compensation);
+  aRv = mCameraControl->Get(CAMERA_PARAM_EXPOSURECOMPENSATION, compensation);
   return compensation;
 }
 
 int32_t
 nsDOMCameraControl::SensorAngle()
 {
+  MOZ_ASSERT(mCameraControl);
+
   int32_t angle;
-  mCameraControl->Get(CAMERA_PARAM_SENSORANGLE, &angle);
+  mCameraControl->Get(CAMERA_PARAM_SENSORANGLE, angle);
   return angle;
 }
 
-already_AddRefed<nsICameraShutterCallback>
-nsDOMCameraControl::GetOnShutter(ErrorResult& aRv)
+already_AddRefed<CameraShutterCallback>
+nsDOMCameraControl::GetOnShutter()
 {
-  nsCOMPtr<nsICameraShutterCallback> cb;
-  aRv = mCameraControl->Get(getter_AddRefs(cb));
+  nsCOMPtr<CameraShutterCallback> cb = mOnShutterCb;
   return cb.forget();
 }
 
 void
-nsDOMCameraControl::SetOnShutter(nsICameraShutterCallback* aOnShutter,
-                                 ErrorResult& aRv)
+nsDOMCameraControl::SetOnShutter(CameraShutterCallback* aCb)
 {
-  aRv = mCameraControl->Set(aOnShutter);
+  mOnShutterCb = aCb;
 }
 
-/* attribute nsICameraClosedCallback onClosed; */
-already_AddRefed<nsICameraClosedCallback>
-nsDOMCameraControl::GetOnClosed(ErrorResult& aRv)
+/* attribute CameraClosedCallback onClosed; */
+already_AddRefed<CameraClosedCallback>
+nsDOMCameraControl::GetOnClosed()
 {
-  nsCOMPtr<nsICameraClosedCallback> onClosed;
-  aRv = mCameraControl->Get(getter_AddRefs(onClosed));
+  nsCOMPtr<CameraClosedCallback> onClosed = mOnClosedCb;
   return onClosed.forget();
 }
 
 void
-nsDOMCameraControl::SetOnClosed(nsICameraClosedCallback* aOnClosed,
-                                ErrorResult& aRv)
+nsDOMCameraControl::SetOnClosed(CameraClosedCallback* aCb)
 {
-  aRv = mCameraControl->Set(aOnClosed);
+  mOnClosedCb = aCb;
 }
 
-already_AddRefed<nsICameraRecorderStateChange>
-nsDOMCameraControl::GetOnRecorderStateChange(ErrorResult& aRv)
+already_AddRefed<CameraRecorderStateChange>
+nsDOMCameraControl::GetOnRecorderStateChange()
 {
-  nsCOMPtr<nsICameraRecorderStateChange> cb;
-  aRv = mCameraControl->Get(getter_AddRefs(cb));
+  nsCOMPtr<CameraRecorderStateChange> cb = mOnRecorderStateChangeCb;
   return cb.forget();
 }
 
 void
-nsDOMCameraControl::SetOnRecorderStateChange(nsICameraRecorderStateChange* aOnRecorderStateChange,
-                                             ErrorResult& aRv)
+nsDOMCameraControl::SetOnRecorderStateChange(CameraRecorderStateChange* aCb)
 {
-  aRv = mCameraControl->Set(aOnRecorderStateChange);
+  mOnRecorderStateChangeCb = aCb;
+}
+
+/* attribute CameraPreviewStateChange onPreviewStateChange; */
+already_AddRefed<CameraPreviewStateChange>
+nsDOMCameraControl::GetOnPreviewStateChange()
+{
+  nsCOMPtr<CameraPreviewStateChange> cb = mOnPreviewStateChangeCb;
+  return cb.forget();
+}
+void
+nsDOMCameraControl::SetOnPreviewStateChange(CameraPreviewStateChange* aCb)
+{
+  mOnPreviewStateChangeCb = aCb;
 }
 
+already_AddRefed<dom::CameraCapabilities>
+nsDOMCameraControl::Capabilities()
+{
+  nsRefPtr<CameraCapabilities> caps = mCapabilities;
+
+  if (!caps) {
+    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();
+}
+
+// Methods.
 void
-nsDOMCameraControl::StartRecording(JSContext* aCx,
-                                   JS::Handle<JS::Value> aOptions,
-                                   nsDOMDeviceStorage& storageArea,
-                                   const nsAString& filename,
-                                   nsICameraStartRecordingCallback* onSuccess,
-                                   const Optional<nsICameraErrorCallback*>& onError,
+nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions,
+                                   nsDOMDeviceStorage& aStorageArea,
+                                   const nsAString& aFilename,
+                                   CameraStartRecordingCallback& aOnSuccess,
+                                   const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
                                    ErrorResult& aRv)
 {
-  MOZ_ASSERT(onSuccess, "no onSuccess handler passed");
+  MOZ_ASSERT(mCameraControl);
 
-  // Default values, until the dictionary parser can handle them.
-  mOptions.rotation = 0;
-  mOptions.maxFileSizeBytes = 0;
-  mOptions.maxVideoLengthMs = 0;
-  aRv = mOptions.Init(aCx, aOptions.address());
-  if (aRv.Failed()) {
-    return;
-  }
+  NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
 
-  aRv = NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
-
-  #ifdef MOZ_B2G
+#ifdef MOZ_B2G
   if (!mAudioChannelAgent) {
     mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
     if (mAudioChannelAgent) {
       // Camera app will stop recording when it falls to the background, so no callback is necessary.
       mAudioChannelAgent->Init(AUDIO_CHANNEL_CONTENT, nullptr);
       // Video recording doesn't output any sound, so it's not necessary to check canPlay.
       int32_t canPlay;
       mAudioChannelAgent->StartPlaying(&canPlay);
     }
   }
-  #endif
+#endif
 
   nsCOMPtr<nsIDOMDOMRequest> request;
   mDSFileDescriptor = new DeviceStorageFileDescriptor();
-  aRv = storageArea.CreateFileDescriptor(filename, mDSFileDescriptor.get(),
+  aRv = aStorageArea.CreateFileDescriptor(aFilename, mDSFileDescriptor.get(),
                                          getter_AddRefs(request));
   if (aRv.Failed()) {
     return;
   }
 
-  mOnSuccessCb = onSuccess;
-  mOnErrorCb = onError.WasPassed() ? onError.Value() : nullptr;
+  mOptions = aOptions;
+  mStartRecordingOnSuccessCb = &aOnSuccess;
+  mStartRecordingOnErrorCb = nullptr;
+  if (aOnError.WasPassed()) {
+    mStartRecordingOnErrorCb = &aOnError.Value();
+  }
 
-  request->AddEventListener(NS_LITERAL_STRING("success"), this, false);
-  request->AddEventListener(NS_LITERAL_STRING("error"), this, false);
+  nsCOMPtr<nsIDOMEventListener> listener = new StartRecordingHelper(this);
+  request->AddEventListener(NS_LITERAL_STRING("success"), listener, false);
+  request->AddEventListener(NS_LITERAL_STRING("error"), listener, false);
 }
 
-NS_IMETHODIMP
-nsDOMCameraControl::HandleEvent(nsIDOMEvent* aEvent)
+void
+nsDOMCameraControl::OnCreatedFileDescriptor(bool aSucceeded)
 {
-  nsString  eventType;
-  aEvent->GetType(eventType);
-  ErrorResult rv;
-
-  if ((eventType.EqualsLiteral("success")) &&
-      mDSFileDescriptor->mFileDescriptor.IsValid()) {
+  if (aSucceeded && mDSFileDescriptor->mFileDescriptor.IsValid()) {
+    ICameraControl::StartRecordingOptions o;
 
-    rv = mCameraControl->StartRecording(&mOptions,
-                                        mDSFileDescriptor.get(),
-                                        mOnSuccessCb.get(),
-                                        mOnErrorCb.get());
-    if (!rv.Failed()) {
-      return rv.ErrorCode();
+    o.rotation = mOptions.mRotation;
+    o.maxFileSizeBytes = mOptions.mMaxFileSizeBytes;
+    o.maxVideoLengthMs = mOptions.mMaxVideoLengthMs;
+    nsresult rv = mCameraControl->StartRecording(mDSFileDescriptor.get(), &o);
+    if (NS_SUCCEEDED(rv)) {
+      return;
     }
-
-    // An error happened. Fall through and call the error callback.
   }
 
-  // We're already be on the main thread, so go ahead and call the
-  // error callback directly.
-
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (mOnErrorCb &&
-      nsDOMCameraManager::IsWindowStillActive(mWindow->WindowID())) {
-    mOnErrorCb->HandleEvent(NS_LITERAL_STRING("FAILURE"));
-  }
-
-  return NS_OK;
+  OnError(CameraControlListener::kInStartRecording, NS_LITERAL_STRING("FAILURE"));
 }
 
 void
 nsDOMCameraControl::StopRecording(ErrorResult& aRv)
 {
-  aRv = NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
+  MOZ_ASSERT(mCameraControl);
 
-  #ifdef MOZ_B2G
+#ifdef MOZ_B2G
   if (mAudioChannelAgent) {
     mAudioChannelAgent->StopPlaying();
     mAudioChannelAgent = nullptr;
   }
-  #endif
+#endif
 
   aRv = mCameraControl->StopRecording();
 }
 
 void
-nsDOMCameraControl::GetPreviewStream(JSContext* aCx,
-                                     JS::Handle<JS::Value> aOptions,
-                                     nsICameraPreviewStreamCallback* onSuccess,
-                                     const Optional<nsICameraErrorCallback*>& onError,
-                                     ErrorResult& aRv)
-{
-  mozilla::idl::CameraSize size;
-  aRv = size.Init(aCx, aOptions.address());
-  if (aRv.Failed()) {
-    return;
-  }
-
-  aRv = mCameraControl->GetPreviewStream(size, onSuccess,
-                                         onError.WasPassed()
-                                         ? onError.Value() : nullptr);
-}
-
-void
 nsDOMCameraControl::ResumePreview(ErrorResult& aRv)
 {
-  aRv = mCameraControl->StartPreview(nullptr);
-}
-
-already_AddRefed<nsICameraPreviewStateChange>
-nsDOMCameraControl::GetOnPreviewStateChange() const
-{
-  nsCOMPtr<nsICameraPreviewStateChange> cb;
-  mCameraControl->Get(getter_AddRefs(cb));
-  return cb.forget();
-}
-
-void
-nsDOMCameraControl::SetOnPreviewStateChange(nsICameraPreviewStateChange* aCb)
-{
-  mCameraControl->Set(aCb);
-}
-
-void
-nsDOMCameraControl::AutoFocus(nsICameraAutoFocusCallback* onSuccess,
-                              const Optional<nsICameraErrorCallback*>& onError,
-                              ErrorResult& aRv)
-{
-  aRv = mCameraControl->AutoFocus(onSuccess,
-                                  onError.WasPassed() ? onError.Value() : nullptr);
+  MOZ_ASSERT(mCameraControl);
+  aRv = mCameraControl->StartPreview();
 }
 
 void
-nsDOMCameraControl::TakePicture(JSContext* aCx,
-                                const CameraPictureOptions& aOptions,
-                                nsICameraTakePictureCallback* onSuccess,
-                                const Optional<nsICameraErrorCallback*>& aOnError,
-                                ErrorResult& aRv)
+nsDOMCameraControl::SetConfiguration(const CameraConfiguration& aConfiguration,
+                                     const Optional<OwningNonNull<CameraSetConfigurationCallback> >& aOnSuccess,
+                                     const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
+                                     ErrorResult& aRv)
 {
-  mozilla::idl::CameraSize           size;
-  mozilla::idl::CameraPosition       pos;
-
-  aRv = size.Init(aCx, &aOptions.mPictureSize);
-  if (aRv.Failed()) {
-    return;
-  }
+  MOZ_ASSERT(mCameraControl);
 
-  /**
-   * Default values, until the dictionary parser can handle them.
-   * NaN indicates no value provided.
-   */
-  pos.latitude = NAN;
-  pos.longitude = NAN;
-  pos.altitude = NAN;
-  pos.timestamp = NAN;
-  aRv = pos.Init(aCx, &aOptions.mPosition);
-  if (aRv.Failed()) {
-    return;
-  }
-
-  nsICameraErrorCallback* onError =
-    aOnError.WasPassed() ? aOnError.Value() : nullptr;
-  aRv = mCameraControl->TakePicture(size, aOptions.mRotation,
-                                    aOptions.mFileFormat, pos,
-                                    aOptions.mDateTime, onSuccess, onError);
-}
-
-void
-nsDOMCameraControl::GetPreviewStreamVideoMode(JSContext* aCx,
-                                              JS::Handle<JS::Value> aOptions,
-                                              nsICameraPreviewStreamCallback* onSuccess,
-                                              const Optional<nsICameraErrorCallback*>& onError,
-                                              ErrorResult& aRv)
-{
-  mozilla::idl::CameraRecorderOptions options;
-  aRv = options.Init(aCx, aOptions.address());
-  if (aRv.Failed()) {
+  nsCOMPtr<CameraTakePictureCallback> cb = mTakePictureOnSuccessCb;
+  if (cb) {
+    // We're busy taking a picture, can't change modes right now.
+    if (aOnError.WasPassed()) {
+      ErrorResult ignored;
+      aOnError.Value().Call(NS_LITERAL_STRING("Busy"), ignored);
+    }
+    aRv = NS_ERROR_FAILURE;
     return;
   }
 
-  aRv = mCameraControl->GetPreviewStreamVideoMode(&options, onSuccess,
-                                                  onError.WasPassed()
-                                                  ? onError.Value() : nullptr);
+  ICameraControl::Configuration config;
+  config.mRecorderProfile = aConfiguration.mRecorderProfile;
+  config.mPreviewSize.width = aConfiguration.mPreviewSize.mWidth;
+  config.mPreviewSize.height = aConfiguration.mPreviewSize.mHeight;
+  config.mMode = ICameraControl::kPictureMode;
+  if (aConfiguration.mMode == CameraMode::Video) {
+    config.mMode = ICameraControl::kVideoMode;
+  }
+
+  mSetConfigurationOnSuccessCb = nullptr;
+  if (aOnSuccess.WasPassed()) {
+    mSetConfigurationOnSuccessCb = &aOnSuccess.Value();
+  }
+  mSetConfigurationOnErrorCb = nullptr;
+  if (aOnError.WasPassed()) {
+    mSetConfigurationOnErrorCb = &aOnError.Value();
+  }
+
+  aRv = mCameraControl->SetConfiguration(config);
+}
+
+void
+nsDOMCameraControl::AutoFocus(CameraAutoFocusCallback& aOnSuccess,
+                              const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
+                              ErrorResult& aRv)
+{
+  MOZ_ASSERT(mCameraControl);
+
+  nsCOMPtr<CameraAutoFocusCallback> cb = mAutoFocusOnSuccessCb.forget();
+  bool cancel = false;
+  if (cb) {
+    // we have a callback, which means we're already in the process of
+    // auto-focusing--cancel the old callback
+    nsCOMPtr<CameraErrorCallback> ecb = mAutoFocusOnErrorCb.forget();
+    ErrorResult ignored;
+    ecb->Call(NS_LITERAL_STRING("Interrupted"), ignored);
+    cancel = true;
+  }
+
+  mAutoFocusOnSuccessCb = &aOnSuccess;
+  mAutoFocusOnErrorCb = nullptr;
+  if (aOnError.WasPassed()) {
+    mAutoFocusOnErrorCb = &aOnError.Value();
+  }
+
+  aRv = mCameraControl->AutoFocus(cancel);
 }
 
 void
-nsDOMCameraControl::ReleaseHardware(const Optional<nsICameraReleaseCallback*>& onSuccess,
-                                    const Optional<nsICameraErrorCallback*>& onError,
-                                    ErrorResult& aRv)
+nsDOMCameraControl::TakePicture(const CameraPictureOptions& aOptions,
+                                CameraTakePictureCallback& aOnSuccess,
+                                const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
+                                ErrorResult& aRv)
 {
-  aRv =
-    mCameraControl->ReleaseHardware(
-        onSuccess.WasPassed() ? onSuccess.Value() : nullptr,
-        onError.WasPassed() ? onError.Value() : nullptr);
+  MOZ_ASSERT(mCameraControl);
+
+  nsCOMPtr<CameraTakePictureCallback> cb = mTakePictureOnSuccessCb;
+  if (cb) {
+    // There is already a call to TakePicture() in progress, abort this one and
+    //  invoke the error callback (if one was passed in).
+    if (aOnError.WasPassed()) {
+      ErrorResult ignored;
+      aOnError.Value().Call(NS_LITERAL_STRING("TakePictureAlreadyInProgress"), ignored);
+    }
+    aRv = NS_ERROR_FAILURE;
+    return;
+  }
+
+  {
+    ICameraControlParameterSetAutoEnter batch(mCameraControl);
+
+    // XXXmikeh - remove this: see bug 931155
+    ICameraControl::Size s;
+    s.width = aOptions.mPictureSize.mWidth;
+    s.height = aOptions.mPictureSize.mHeight;
+
+    ICameraControl::Position p;
+    p.latitude = aOptions.mPosition.mLatitude;
+    p.longitude = aOptions.mPosition.mLongitude;
+    p.altitude = aOptions.mPosition.mAltitude;
+    p.timestamp = aOptions.mPosition.mTimestamp;
+
+    if (s.width && s.height) {
+      mCameraControl->Set(CAMERA_PARAM_PICTURE_SIZE, s);
+    }
+    mCameraControl->Set(CAMERA_PARAM_PICTURE_ROTATION, aOptions.mRotation);
+    mCameraControl->Set(CAMERA_PARAM_PICTURE_FILEFORMAT, aOptions.mFileFormat);
+    mCameraControl->Set(CAMERA_PARAM_PICTURE_DATETIME, aOptions.mDateTime);
+    mCameraControl->SetLocation(p);
+  }
+
+  mTakePictureOnSuccessCb = &aOnSuccess;
+  mTakePictureOnErrorCb = nullptr;
+  if (aOnError.WasPassed()) {
+    mTakePictureOnErrorCb = &aOnError.Value();
+  }
+
+  aRv = mCameraControl->TakePicture();
 }
 
-class GetCameraResult : public nsRunnable
+void
+nsDOMCameraControl::ReleaseHardware(const Optional<OwningNonNull<CameraReleaseCallback> >& aOnSuccess,
+                                    const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
+                                    ErrorResult& aRv)
 {
-public:
-  GetCameraResult(nsDOMCameraControl* aDOMCameraControl,
-    nsresult aResult,
-    const nsMainThreadPtrHandle<nsICameraGetCameraCallback>& onSuccess,
-    const nsMainThreadPtrHandle<nsICameraErrorCallback>& onError,
-    uint64_t aWindowId)
-    : mDOMCameraControl(aDOMCameraControl)
-    , mResult(aResult)
-    , mOnSuccessCb(onSuccess)
-    , mOnErrorCb(onError)
-    , mWindowId(aWindowId)
-  { }
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mCameraControl);
 
-    if (nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      DOM_CAMERA_LOGT("%s : this=%p -- BEFORE CALLBACK\n", __func__, this);
-      if (NS_FAILED(mResult)) {
-        if (mOnErrorCb.get()) {
-          mOnErrorCb->HandleEvent(NS_LITERAL_STRING("FAILURE"));
-        }
-      } else {
-        if (mOnSuccessCb.get()) {
-          mOnSuccessCb->HandleEvent(mDOMCameraControl);
-        }
-      }
-      DOM_CAMERA_LOGT("%s : this=%p -- AFTER CALLBACK\n", __func__, this);
-    }
-
-    /**
-     * Finally, release the extra reference to the DOM-facing camera control.
-     * See the nsDOMCameraControl constructor for the corresponding call to
-     * NS_ADDREF_THIS().
-     */
-    NS_RELEASE(mDOMCameraControl);
-    return NS_OK;
+  mReleaseOnSuccessCb = nullptr;
+  if (aOnSuccess.WasPassed()) {
+    mReleaseOnSuccessCb = &aOnSuccess.Value();
+  }
+  mReleaseOnErrorCb = nullptr;
+  if (aOnError.WasPassed()) {
+    mReleaseOnErrorCb = &aOnError.Value();
   }
 
-protected:
-  /**
-   * 'mDOMCameraControl' is a raw pointer to a previously ADDREF()ed object,
-   * which is released in Run().
-   */
-  nsDOMCameraControl* mDOMCameraControl;
-  nsresult mResult;
-  nsMainThreadPtrHandle<nsICameraGetCameraCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-  uint64_t mWindowId;
-};
-
-nsresult
-nsDOMCameraControl::Result(nsresult aResult,
-                           const nsMainThreadPtrHandle<nsICameraGetCameraCallback>& onSuccess,
-                           const nsMainThreadPtrHandle<nsICameraErrorCallback>& onError,
-                           uint64_t aWindowId)
-{
-  nsCOMPtr<GetCameraResult> getCameraResult = new GetCameraResult(this, aResult, onSuccess, onError, aWindowId);
-  return NS_DispatchToMainThread(getCameraResult);
+  aRv = mCameraControl->ReleaseHardware();
 }
 
 void
 nsDOMCameraControl::Shutdown()
 {
   DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+  MOZ_ASSERT(mCameraControl);
+
+  // Remove any pending solicited event handlers; these
+  // reference our window object, which in turn references
+  // us. If we don't remove them, we can leak DOM objects.
+  mGetCameraOnSuccessCb = nullptr;
+  mGetCameraOnErrorCb = nullptr;
+  mAutoFocusOnSuccessCb = nullptr;
+  mAutoFocusOnErrorCb = nullptr;
+  mTakePictureOnSuccessCb = nullptr;
+  mTakePictureOnErrorCb = nullptr;
+  mStartRecordingOnSuccessCb = nullptr;
+  mStartRecordingOnErrorCb = nullptr;
+  mReleaseOnSuccessCb = nullptr;
+  mReleaseOnErrorCb = nullptr;
+  mSetConfigurationOnSuccessCb = nullptr;
+  mSetConfigurationOnErrorCb = nullptr;
+
+  // Remove all of the unsolicited event handlers too.
+  mOnShutterCb = nullptr;
+  mOnClosedCb = nullptr;
+  mOnRecorderStateChangeCb = nullptr;
+  mOnPreviewStateChangeCb = nullptr;
+
   mCameraControl->Shutdown();
 }
 
 nsRefPtr<ICameraControl>
 nsDOMCameraControl::GetNativeCameraControl()
 {
   return mCameraControl;
 }
@@ -633,8 +957,270 @@ nsDOMCameraControl::NotifyRecordingStatu
   NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE);
 
   return MediaManager::NotifyRecordingStatusChange(mWindow,
                                                    aMsg,
                                                    true /* aIsAudio */,
                                                    true /* aIsVideo */);
 }
 
+// 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:
+      // The hardware is open, so we can return a camera to JS, even if
+      // the preview hasn't started yet.
+      if (mGetCameraOnSuccessCb) {
+        nsCOMPtr<GetCameraCallback> cb = mGetCameraOnSuccessCb.forget();
+        ErrorResult ignored;
+        mGetCameraOnErrorCb = nullptr;
+        cb->Call(*this, *mCurrentConfiguration, ignored);
+      }
+      break;
+
+    case CameraControlListener::kHardwareClosed:
+      if (mReleaseOnSuccessCb) {
+        // If we have this event handler, this was a solicited hardware close.
+        nsCOMPtr<CameraReleaseCallback> cb = mReleaseOnSuccessCb.forget();
+        mReleaseOnErrorCb = nullptr;
+        cb->Call(ignored);
+      } else if(mOnClosedCb) {
+        // If not, something else closed the hardware.
+        nsCOMPtr<CameraClosedCallback> cb = mOnClosedCb;
+        cb->Call(ignored);
+      }
+      break;
+
+    default:
+      MOZ_ASSUME_UNREACHABLE("Unanticipated camera hardware state");
+  }
+}
+
+void
+nsDOMCameraControl::OnShutter()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  DOM_CAMERA_LOGI("DOM ** SNAP **\n");
+
+  nsCOMPtr<CameraShutterCallback> cb = mOnShutterCb;
+  if (cb) {
+    ErrorResult ignored;
+    cb->Call(ignored);
+  }
+}
+
+void
+nsDOMCameraControl::OnPreviewStateChange(CameraControlListener::PreviewState aState)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mOnPreviewStateChangeCb) {
+    return;
+  }
+
+  nsString state;
+  switch (aState) {
+    case CameraControlListener::kPreviewStarted:
+      state = NS_LITERAL_STRING("started");
+      break;
+
+    default:
+      state = NS_LITERAL_STRING("stopped");
+      break;
+  }
+
+  nsCOMPtr<CameraPreviewStateChange> cb = mOnPreviewStateChangeCb;
+  ErrorResult ignored;
+  cb->Call(state, ignored);
+}
+
+void
+nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState aState,
+                                          int32_t aArg, int32_t aTrackNum)
+{
+  // For now, we do nothing with 'aStatus' and 'aTrackNum'.
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ErrorResult ignored;
+  nsString state;
+
+  switch (aState) {
+    case CameraControlListener::kRecorderStarted:
+      if (mStartRecordingOnSuccessCb) {
+        nsCOMPtr<CameraStartRecordingCallback> cb = mStartRecordingOnSuccessCb.forget();
+        mStartRecordingOnErrorCb = nullptr;
+        cb->Call(ignored);
+      }
+      return;
+
+    case CameraControlListener::kRecorderStopped:
+      NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
+      return;
+
+#ifdef MOZ_B2G_CAMERA
+    case CameraControlListener::kFileSizeLimitReached:
+      state = NS_LITERAL_STRING("FileSizeLimitReached");
+      break;
+
+    case CameraControlListener::kVideoLengthLimitReached:
+      state = NS_LITERAL_STRING("VideoLengthLimitReached");
+      break;
+
+    case CameraControlListener::kTrackCompleted:
+      state = NS_LITERAL_STRING("TrackCompleted");
+      break;
+
+    case CameraControlListener::kTrackFailed:
+      state = NS_LITERAL_STRING("TrackFailed");
+      break;
+
+    case CameraControlListener::kMediaRecorderFailed:
+      state = NS_LITERAL_STRING("MediaRecorderFailed");
+      break;
+
+    case CameraControlListener::kMediaServerFailed:
+      state = NS_LITERAL_STRING("MediaServerFailed");
+      break;
+#endif
+
+    default:
+      MOZ_ASSUME_UNREACHABLE("Unanticipated video recorder error");
+      return;
+  }
+
+  nsCOMPtr<CameraRecorderStateChange> cb = mOnRecorderStateChangeCb;
+  if (cb) {
+    cb->Call(state, ignored);
+  }
+}
+
+void
+nsDOMCameraControl::OnConfigurationChange(DOMCameraConfiguration* aConfiguration)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Update our record of the current camera configuration
+  mCurrentConfiguration = aConfiguration;
+
+  DOM_CAMERA_LOGI("DOM OnConfigurationChange: this=%p\n", this);
+  DOM_CAMERA_LOGI("    mode                   : %s\n",
+    mCurrentConfiguration->mMode == CameraMode::Video ? "video" : "picture");
+  DOM_CAMERA_LOGI("    maximum focus areas    : %d\n",
+    mCurrentConfiguration->mMaxFocusAreas);
+  DOM_CAMERA_LOGI("    maximum metering areas : %d\n",
+    mCurrentConfiguration->mMaxMeteringAreas);
+  DOM_CAMERA_LOGI("    preview size (w x h)   : %d x %d\n",
+    mCurrentConfiguration->mPreviewSize.mWidth, mCurrentConfiguration->mPreviewSize.mHeight);
+  DOM_CAMERA_LOGI("    recorder profile       : %s\n",
+    NS_ConvertUTF16toUTF8(mCurrentConfiguration->mRecorderProfile).get());
+
+  nsCOMPtr<CameraSetConfigurationCallback> cb = mSetConfigurationOnSuccessCb.forget();
+  mSetConfigurationOnErrorCb = nullptr;
+  if (cb) {
+    ErrorResult ignored;
+    cb->Call(*mCurrentConfiguration, ignored);
+  }
+}
+
+void
+nsDOMCameraControl::OnAutoFocusComplete(bool aAutoFocusSucceeded)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ErrorResult ignored;
+
+  nsCOMPtr<CameraAutoFocusCallback> cb = mAutoFocusOnSuccessCb.forget();
+  mAutoFocusOnErrorCb = nullptr;
+  cb->Call(aAutoFocusSucceeded, ignored);
+}
+
+void
+nsDOMCameraControl::OnTakePictureComplete(nsIDOMBlob* aPicture)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ErrorResult ignored;
+
+  nsCOMPtr<CameraTakePictureCallback> cb = mTakePictureOnSuccessCb.forget();
+  mTakePictureOnErrorCb = nullptr;
+  cb->Call(aPicture, ignored);
+}
+
+void
+nsDOMCameraControl::OnError(CameraControlListener::CameraErrorContext aContext, const nsAString& aError)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<CameraErrorCallback>* errorCb;
+  switch (aContext) {
+    case CameraControlListener::kInGetCamera:
+      mGetCameraOnSuccessCb = nullptr;
+      errorCb = &mGetCameraOnErrorCb;
+      break;
+
+    case CameraControlListener::kInSetConfiguration:
+      mSetConfigurationOnSuccessCb = nullptr;
+      errorCb = &mSetConfigurationOnErrorCb;
+      break;
+
+    case CameraControlListener::kInAutoFocus:
+      mAutoFocusOnSuccessCb = nullptr;
+      errorCb = &mAutoFocusOnErrorCb;
+      break;
+
+    case CameraControlListener::kInTakePicture:
+      mTakePictureOnSuccessCb = nullptr;
+      errorCb = &mTakePictureOnErrorCb;
+      break;
+
+    case CameraControlListener::kInStartRecording:
+      mStartRecordingOnSuccessCb = nullptr;
+      errorCb = &mStartRecordingOnErrorCb;
+      break;
+
+    case CameraControlListener::kInReleaseHardware:
+      mReleaseOnSuccessCb = nullptr;
+      errorCb = &mReleaseOnErrorCb;
+      break;
+
+    case CameraControlListener::kInStopRecording:
+      NS_WARNING("Failed to stop recording (which shouldn't happen)!");
+      MOZ_CRASH();
+      break;
+
+    case CameraControlListener::kInStartPreview:
+      NS_WARNING("Failed to (re)start preview!");
+      MOZ_CRASH();
+      break;
+
+    case CameraControlListener::kInUnspecified:
+      if (aError.EqualsASCII("ErrorServiceFailed")) {
+        // If the camera service fails, we will get preview-stopped and
+        //  hardware-closed events, so nothing to do here.
+        return;
+      }
+      // fallthrough
+
+    default:
+      MOZ_ASSUME_UNREACHABLE("Error occurred in unanticipated camera state");
+      return;
+  }
+
+  MOZ_ASSERT(errorCb);
+
+  if (!*errorCb) {
+    DOM_CAMERA_LOGW("DOM No error handler for error '%s' at %d\n",
+      NS_LossyConvertUTF16toASCII(aError).get(), aContext);
+    return;
+  }
+
+  // kung-fu death grip
+  nsCOMPtr<CameraErrorCallback> cb = (*errorCb).forget();
+  ErrorResult ignored;
+  cb->Call(aError, ignored);
+}
+
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -3,61 +3,60 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DOM_CAMERA_DOMCAMERACONTROL_H
 #define DOM_CAMERA_DOMCAMERACONTROL_H
 
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsCycleCollectionParticipant.h"
-#include "nsIDOMEventListener.h"
-#include "DictionaryHelpers.h"
+#include "mozilla/dom/CameraControlBinding.h"
 #include "ICameraControl.h"
-#include "DOMCameraPreview.h"
-#include "nsIDOMCameraManager.h"
 #include "CameraCommon.h"
+#include "DOMMediaStream.h"
 #include "AudioChannelAgent.h"
 #include "nsProxyRelease.h"
 #include "nsHashPropertyBag.h"
 #include "DeviceStorage.h"
+#include "DOMCameraControlListener.h"
 
 class nsDOMDeviceStorage;
 class nsPIDOMWindow;
+class nsIDOMBlob;
 
 namespace mozilla {
+
 namespace dom {
-class CameraPictureOptions;
-template<typename T> class Optional;
+  class CameraCapabilities;
+  class CameraPictureOptions;
+  class CameraStartRecordingOptions;
+  template<typename T> class Optional;
 }
 class ErrorResult;
+class StartRecordingHelper;
 
 // Main camera control.
-class nsDOMCameraControl MOZ_FINAL : public nsIDOMEventListener,
-                                     public nsWrapperCache
+class nsDOMCameraControl MOZ_FINAL : public DOMMediaStream
 {
 public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_NSIDOMEVENTLISTENER
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMCameraControl)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsDOMCameraControl, DOMMediaStream)
+  NS_DECL_ISUPPORTS_INHERITED
 
-  nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread,
-                     nsICameraGetCameraCallback* onSuccess,
-                     nsICameraErrorCallback* onError, nsPIDOMWindow* aWindow);
-  nsresult Result(nsresult aResult,
-                  const nsMainThreadPtrHandle<nsICameraGetCameraCallback>& onSuccess,
-                  const nsMainThreadPtrHandle<nsICameraErrorCallback>& onError,
-                  uint64_t aWindowId);
+  nsDOMCameraControl(uint32_t aCameraId,
+                     const dom::CameraConfiguration& aInitialConfig,
+                     dom::GetCameraCallback* aOnSuccess,
+                     dom::CameraErrorCallback* aOnError,
+                     nsPIDOMWindow* aWindow);
   nsRefPtr<ICameraControl> GetNativeCameraControl();
 
   void Shutdown();
 
   nsPIDOMWindow* GetParentObject() const { return mWindow; }
 
-  // WebIDL
-  nsICameraCapabilities* Capabilities();
+  // Attributes.
   void GetEffect(nsString& aEffect, ErrorResult& aRv);
   void SetEffect(const nsAString& aEffect, ErrorResult& aRv);
   void GetWhiteBalanceMode(nsString& aMode, ErrorResult& aRv);
   void SetWhiteBalanceMode(const nsAString& aMode, ErrorResult& aRv);
   void GetSceneMode(nsString& aMode, ErrorResult& aRv);
   void SetSceneMode(const nsAString& aMode, ErrorResult& aRv);
   void GetFlashMode(nsString& aMode, ErrorResult& aRv);
   void SetFlashMode(const nsAString& aMode, ErrorResult& aRv);
@@ -75,55 +74,137 @@ public:
   void SetThumbnailSize(JSContext* aCx, JS::Handle<JS::Value> aSize, ErrorResult& aRv);
   double GetFocalLength(ErrorResult& aRv);
   double GetFocusDistanceNear(ErrorResult& aRv);
   double GetFocusDistanceOptimum(ErrorResult& aRv);
   double GetFocusDistanceFar(ErrorResult& aRv);
   void SetExposureCompensation(const dom::Optional<double>& aCompensation, ErrorResult& aRv);
   double GetExposureCompensation(ErrorResult& aRv);
   int32_t SensorAngle();
-  already_AddRefed<nsICameraShutterCallback> GetOnShutter(ErrorResult& aRv);
-  void SetOnShutter(nsICameraShutterCallback* aCb, ErrorResult& aRv);
-  already_AddRefed<nsICameraClosedCallback> GetOnClosed(ErrorResult& aRv);
-  void SetOnClosed(nsICameraClosedCallback* aCb, ErrorResult& aRv);
-  already_AddRefed<nsICameraRecorderStateChange> GetOnRecorderStateChange(ErrorResult& aRv);
-  void SetOnRecorderStateChange(nsICameraRecorderStateChange* aCb, ErrorResult& aRv);
-  void AutoFocus(nsICameraAutoFocusCallback* aOnSuccess, const dom::Optional<nsICameraErrorCallback*>& aOnErro, ErrorResult& aRvr);
-  void TakePicture(JSContext* aCx, const dom::CameraPictureOptions& aOptions,
-                   nsICameraTakePictureCallback* onSuccess,
-                   const dom::Optional<nsICameraErrorCallback* >& onError,
+  already_AddRefed<dom::CameraCapabilities> Capabilities();
+
+  // Unsolicited event handlers.
+  already_AddRefed<dom::CameraShutterCallback> GetOnShutter();
+  void SetOnShutter(dom::CameraShutterCallback* aCb);
+  already_AddRefed<dom::CameraClosedCallback> GetOnClosed();
+  void SetOnClosed(dom::CameraClosedCallback* aCb);
+  already_AddRefed<dom::CameraRecorderStateChange> GetOnRecorderStateChange();
+  void SetOnRecorderStateChange(dom::CameraRecorderStateChange* aCb);
+  already_AddRefed<dom::CameraPreviewStateChange> GetOnPreviewStateChange();
+  void SetOnPreviewStateChange(dom::CameraPreviewStateChange* aCb);
+
+  // Methods.
+  void SetConfiguration(const dom::CameraConfiguration& aConfiguration,
+                        const dom::Optional<dom::OwningNonNull<dom::CameraSetConfigurationCallback> >& aOnSuccess,
+                        const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
+                        ErrorResult& aRv);
+  void AutoFocus(dom::CameraAutoFocusCallback& aOnSuccess,
+                 const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
+                 ErrorResult& aRv);
+  void TakePicture(const dom::CameraPictureOptions& aOptions,
+                   dom::CameraTakePictureCallback& aOnSuccess,
+                   const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
                    ErrorResult& aRv);
-  already_AddRefed<nsICameraPreviewStateChange> GetOnPreviewStateChange() const;
-  void SetOnPreviewStateChange(nsICameraPreviewStateChange* aOnStateChange);
-  void GetPreviewStreamVideoMode(JSContext* cx, JS::Handle<JS::Value> aOptions, nsICameraPreviewStreamCallback* onSuccess, const dom::Optional<nsICameraErrorCallback* >& onError, ErrorResult& aRv);
-  void StartRecording(JSContext* cx, JS::Handle<JS::Value> aOptions, nsDOMDeviceStorage& storageArea, const nsAString& filename, nsICameraStartRecordingCallback* onSuccess, const dom::Optional<nsICameraErrorCallback* >& onError, ErrorResult& aRv);
+  void StartRecording(const dom::CameraStartRecordingOptions& aOptions,
+                      nsDOMDeviceStorage& storageArea,
+                      const nsAString& filename,
+                      dom::CameraStartRecordingCallback& aOnSuccess,
+                      const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
+                      ErrorResult& aRv);
   void StopRecording(ErrorResult& aRv);
-  void GetPreviewStream(JSContext* cx, JS::Handle<JS::Value> aOptions, nsICameraPreviewStreamCallback* onSuccess, const dom::Optional<nsICameraErrorCallback* >& onError, ErrorResult& aRv);
   void ResumePreview(ErrorResult& aRv);
-  void ReleaseHardware(const dom::Optional<nsICameraReleaseCallback* >& onSuccess, const dom::Optional<nsICameraErrorCallback* >& onError, ErrorResult& aRv);
+  void ReleaseHardware(const dom::Optional<dom::OwningNonNull<dom::CameraReleaseCallback> >& aOnSuccess,
+                       const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
+                       ErrorResult& aRv);
+
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
 protected:
   virtual ~nsDOMCameraControl();
 
+  class DOMCameraConfiguration : public dom::CameraConfiguration
+  {
+  public:
+    NS_INLINE_DECL_REFCOUNTING(DOMCameraConfiguration)
+
+    DOMCameraConfiguration();
+    DOMCameraConfiguration(const dom::CameraConfiguration& aConfiguration);
+
+    // Additional configuration options that aren't exposed to the DOM
+    uint32_t mMaxFocusAreas;
+    uint32_t mMaxMeteringAreas;
+
+  protected:
+    ~DOMCameraConfiguration();
+  };
+
+  friend class DOMCameraControlListener;
+  friend class mozilla::StartRecordingHelper;
+
+  void OnCreatedFileDescriptor(bool aSucceeded);
+
+  void OnAutoFocusComplete(bool aAutoFocusSucceeded);
+  void OnTakePictureComplete(nsIDOMBlob* aPicture);
+
+  void OnHardwareStateChange(DOMCameraControlListener::HardwareState aState);
+  void OnPreviewStateChange(DOMCameraControlListener::PreviewState aState);
+  void OnRecorderStateChange(CameraControlListener::RecorderState aState, int32_t aStatus, int32_t aTrackNum);
+  void OnConfigurationChange(DOMCameraConfiguration* aConfiguration);
+  void OnShutter();
+  void OnError(CameraControlListener::CameraErrorContext aContext, const nsAString& mError);
+
+  bool IsWindowStillActive();
+
+  nsresult NotifyRecordingStatusChange(const nsString& aMsg);
+
+  nsRefPtr<ICameraControl> mCameraControl; // non-DOM camera control
+
+  // An agent used to join audio channel service.
+  nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
+
+  nsresult Set(JSContext* aCx, uint32_t aKey, const JS::Value& aValue, uint32_t aLimit);
+  nsresult Get(JSContext* aCx, uint32_t aKey, JS::Value* aValue);
+
+  nsRefPtr<DOMCameraConfiguration>              mCurrentConfiguration;
+  nsRefPtr<dom::CameraCapabilities>             mCapabilities;
+
+  // solicited camera control event handlers
+  nsCOMPtr<dom::GetCameraCallback>              mGetCameraOnSuccessCb;
+  nsCOMPtr<dom::CameraErrorCallback>            mGetCameraOnErrorCb;
+  nsCOMPtr<dom::CameraAutoFocusCallback>        mAutoFocusOnSuccessCb;
+  nsCOMPtr<dom::CameraErrorCallback>            mAutoFocusOnErrorCb;
+  nsCOMPtr<dom::CameraTakePictureCallback>      mTakePictureOnSuccessCb;
+  nsCOMPtr<dom::CameraErrorCallback>            mTakePictureOnErrorCb;
+  nsCOMPtr<dom::CameraStartRecordingCallback>   mStartRecordingOnSuccessCb;
+  nsCOMPtr<dom::CameraErrorCallback>            mStartRecordingOnErrorCb;
+  nsCOMPtr<dom::CameraReleaseCallback>          mReleaseOnSuccessCb;
+  nsCOMPtr<dom::CameraErrorCallback>            mReleaseOnErrorCb;
+  nsCOMPtr<dom::CameraSetConfigurationCallback> mSetConfigurationOnSuccessCb;
+  nsCOMPtr<dom::CameraErrorCallback>            mSetConfigurationOnErrorCb;
+
+  // unsolicited event handlers
+  nsCOMPtr<dom::CameraShutterCallback>          mOnShutterCb;
+  nsCOMPtr<dom::CameraClosedCallback>           mOnClosedCb;
+  nsCOMPtr<dom::CameraRecorderStateChange>      mOnRecorderStateChangeCb;
+  nsCOMPtr<dom::CameraPreviewStateChange>       mOnPreviewStateChangeCb;
+
+  // Camera event listener; we only need this weak reference so that
+  //  we can remove the listener from the camera when we're done
+  //  with it.
+  DOMCameraControlListener* mListener;
+
+  // our viewfinder stream
+  CameraPreviewMediaStream* mInput;
+
+  // set once when this object is created
+  nsCOMPtr<nsPIDOMWindow>   mWindow;
+
+  dom::CameraStartRecordingOptions mOptions;
+  nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
+
 private:
   nsDOMCameraControl(const nsDOMCameraControl&) MOZ_DELETE;
   nsDOMCameraControl& operator=(const nsDOMCameraControl&) MOZ_DELETE;
-
-  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
-  nsresult NotifyRecordingStatusChange(const nsString& aMsg);
-
-  mozilla::idl::CameraStartRecordingOptions mOptions;
-  nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
-  nsCOMPtr<nsICameraStartRecordingCallback> mOnSuccessCb;
-  nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
-
-protected:
-  /* additional members */
-  nsRefPtr<ICameraControl>        mCameraControl; // non-DOM camera control
-  nsCOMPtr<nsICameraCapabilities> mDOMCapabilities;
-  // An agent used to join audio channel service.
-  nsCOMPtr<nsIAudioChannelAgent>  mAudioChannelAgent;
-  nsCOMPtr<nsPIDOMWindow> mWindow;
 };
 
 } // namespace mozilla
 
 #endif // DOM_CAMERA_DOMCAMERACONTROL_H
new file mode 100644
--- /dev/null
+++ b/dom/camera/DOMCameraControlListener.cpp
@@ -0,0 +1,329 @@
+/* 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 "DOMCameraControlListener.h"
+#include "nsThreadUtils.h"
+#include "nsDOMFile.h"
+#include "CameraCommon.h"
+#include "DOMCameraControl.h"
+#include "CameraPreviewMediaStream.h"
+#include "mozilla/dom/CameraManagerBinding.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// Boilerplate callback runnable
+class DOMCameraControlListener::DOMCallback : public nsRunnable
+{
+public:
+  DOMCallback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl)
+    : mDOMCameraControl(aDOMCameraControl)
+  { }
+  virtual ~DOMCallback() { }
+
+  virtual void RunCallback(nsDOMCameraControl* aDOMCameraControl) = 0;
+
+  NS_IMETHOD
+  Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsRefPtr<nsDOMCameraControl> camera = mDOMCameraControl.get();
+    if (camera) {
+      RunCallback(camera);
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsMainThreadPtrHandle<nsDOMCameraControl> mDOMCameraControl;
+};
+
+// Specific callback handlers
+void
+DOMCameraControlListener::OnHardwareStateChange(HardwareState aState)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
+             HardwareState aState)
+      : DOMCallback(aDOMCameraControl)
+      , mState(aState)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      aDOMCameraControl->OnHardwareStateChange(mState);
+    }
+
+  protected:
+    HardwareState mState;
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aState));
+}
+
+void
+DOMCameraControlListener::OnPreviewStateChange(PreviewState aState)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
+             PreviewState aState)
+      : DOMCallback(aDOMCameraControl)
+      , mState(aState)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      aDOMCameraControl->OnPreviewStateChange(mState);
+    }
+
+  protected:
+    PreviewState mState;
+  };
+
+  switch (aState) {
+    case kPreviewStopped:
+      // Clear the current frame right away, without dispatching a
+      //  runnable. This is an ugly coupling between the camera's
+      //  SurfaceTextureClient and the MediaStream/ImageContainer,
+      //  but without it, the preview can fail to start.
+      DOM_CAMERA_LOGI("Preview stopped, clearing current frame\n");
+      mStream->ClearCurrentFrame();
+      break;
+
+    case kPreviewPaused:
+      // In the paused state, we still want to reflect the change
+      //  in preview state, but we don't want to clear the current
+      //  frame as above, since doing so seems to cause genlock
+      //  problems when we restart the preview. See bug 957749.
+      DOM_CAMERA_LOGI("Preview paused\n");
+      break;
+
+    case kPreviewStarted:
+      DOM_CAMERA_LOGI("Preview started\n");
+      break;
+
+    default:
+      DOM_CAMERA_LOGE("Unknown preview state %d\n", aState);
+      MOZ_ASSUME_UNREACHABLE("Invalid preview state");
+      return;
+  }
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aState));
+}
+
+void
+DOMCameraControlListener::OnRecorderStateChange(RecorderState aState,
+                                                int32_t aStatus, int32_t aTrackNum)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
+             RecorderState aState,
+             int32_t aStatus,
+             int32_t aTrackNum)
+      : DOMCallback(aDOMCameraControl)
+      , mState(aState)
+      , mStatus(aStatus)
+      , mTrackNum(aTrackNum)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      aDOMCameraControl->OnRecorderStateChange(mState, mStatus, mTrackNum);
+    }
+
+  protected:
+    RecorderState mState;
+    int32_t mStatus;
+    int32_t mTrackNum;
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aState, aStatus, aTrackNum));
+}
+
+void
+DOMCameraControlListener::OnConfigurationChange(const CameraListenerConfiguration& aConfiguration)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
+             const CameraListenerConfiguration& aConfiguration)
+      : DOMCallback(aDOMCameraControl)
+      , mConfiguration(aConfiguration)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      nsRefPtr<nsDOMCameraControl::DOMCameraConfiguration> config =
+        new nsDOMCameraControl::DOMCameraConfiguration();
+
+      switch (mConfiguration.mMode) {
+        case ICameraControl::kVideoMode:
+          config->mMode = CameraMode::Video;
+          break;
+
+        case ICameraControl::kPictureMode:
+          config->mMode = CameraMode::Picture;
+          break;
+
+        default:
+          MOZ_ASSUME_UNREACHABLE("Unanticipated camera mode!");
+      }
+
+      // Map CameraControl parameters to their DOM-facing equivalents
+      config->mRecorderProfile = mConfiguration.mRecorderProfile;
+      config->mPreviewSize.mWidth = mConfiguration.mPreviewSize.width;
+      config->mPreviewSize.mHeight = mConfiguration.mPreviewSize.height;
+      config->mMaxMeteringAreas = mConfiguration.mMaxMeteringAreas;
+      config->mMaxFocusAreas = mConfiguration.mMaxFocusAreas;
+
+      aDOMCameraControl->OnConfigurationChange(config);
+    }
+
+  protected:
+    const CameraListenerConfiguration mConfiguration;
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aConfiguration));
+}
+
+void
+DOMCameraControlListener::OnShutter()
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl)
+      : DOMCallback(aDOMCameraControl)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      aDOMCameraControl->OnShutter();
+    }
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl));
+}
+
+bool
+DOMCameraControlListener::OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight)
+{
+  DOM_CAMERA_LOGI("OnNewPreviewFrame: got %d x %d frame\n", aWidth, aHeight);
+
+  mStream->SetCurrentFrame(gfxIntSize(aWidth, aHeight), aImage);
+  return true;
+}
+
+void
+DOMCameraControlListener::OnAutoFocusComplete(bool aAutoFocusSucceeded)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
+             bool aAutoFocusSucceeded)
+      : DOMCallback(aDOMCameraControl)
+      , mAutoFocusSucceeded(aAutoFocusSucceeded)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      aDOMCameraControl->OnAutoFocusComplete(mAutoFocusSucceeded);
+    }
+
+  protected:
+    bool mAutoFocusSucceeded;
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aAutoFocusSucceeded));
+}
+
+void
+DOMCameraControlListener::OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
+             uint8_t* aData, uint32_t aLength, const nsAString& aMimeType)
+      : DOMCallback(aDOMCameraControl)
+      , mData(aData)
+      , mLength(aLength)
+      , mMimeType(aMimeType)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      nsCOMPtr<nsIDOMBlob> picture = new nsDOMMemoryFile(static_cast<void*>(mData),
+                                                         static_cast<uint64_t>(mLength),
+                                                         mMimeType);
+      aDOMCameraControl->OnTakePictureComplete(picture);
+    }
+
+  protected:
+    uint8_t* mData;
+    uint32_t mLength;
+    nsString mMimeType;
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aData, aLength, aMimeType));
+}
+
+void
+DOMCameraControlListener::OnError(CameraErrorContext aContext, CameraError aError)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
+             CameraErrorContext aContext,
+             CameraError aError)
+      : DOMCallback(aDOMCameraControl)
+      , mContext(aContext)
+      , mError(aError)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      nsString error;
+
+      switch (mError) {
+        case kErrorServiceFailed:
+          error = NS_LITERAL_STRING("ErrorServiceFailed");
+          break;
+
+        case kErrorApiFailed:
+          // XXXmikeh legacy error placeholder
+          error = NS_LITERAL_STRING("FAILURE");
+          break;
+
+        default:
+          error = NS_LITERAL_STRING("ErrorUnknown");
+          break;
+      }
+      aDOMCameraControl->OnError(mContext, error);
+    }
+
+  protected:
+    CameraErrorContext mContext;
+    CameraError mError;
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aContext, aError));
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/DOMCameraControlListener.h
@@ -0,0 +1,48 @@
+/* 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_DOMCAMERACONTROLLISTENER_H
+#define DOM_CAMERA_DOMCAMERACONTROLLISTENER_H
+
+#include "nsProxyRelease.h"
+#include "CameraControlListener.h"
+
+namespace mozilla {
+
+class nsDOMCameraControl;
+class CameraPreviewMediaStream;
+
+class DOMCameraControlListener : public CameraControlListener
+{
+protected:
+  nsMainThreadPtrHandle<nsDOMCameraControl> mDOMCameraControl;
+  CameraPreviewMediaStream* mStream;
+
+  class DOMCallback;
+
+public:
+  DOMCameraControlListener(nsDOMCameraControl* aDOMCameraControl, CameraPreviewMediaStream* aStream)
+    : mDOMCameraControl(new nsMainThreadPtrHolder<nsDOMCameraControl>(aDOMCameraControl))
+    , mStream(aStream)
+  { }
+
+  void OnAutoFocusComplete(bool aAutoFocusSucceeded);
+  void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType);
+
+  void OnHardwareStateChange(HardwareState aState);
+  void OnPreviewStateChange(PreviewState aState);
+  void OnRecorderStateChange(RecorderState aState, int32_t aStatus, int32_t aTrackNum);
+  void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration);
+  void OnShutter();
+  bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);
+  void OnError(CameraErrorContext aContext, CameraError aError);
+
+private:
+  DOMCameraControlListener(const DOMCameraControlListener&) MOZ_DELETE;
+  DOMCameraControlListener& operator=(const DOMCameraControlListener&) MOZ_DELETE;
+};
+
+} // namespace mozilla
+
+#endif // DOM_CAMERA_DOMCAMERACONTROLLISTENER_H
--- a/dom/camera/DOMCameraManager.cpp
+++ b/dom/camera/DOMCameraManager.cpp
@@ -1,79 +1,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/. */
 
+#include "DOMCameraManager.h"
 #include "nsDebug.h"
+#include "jsapi.h"
 #include "nsPIDOMWindow.h"
 #include "mozilla/Services.h"
 #include "nsObserverService.h"
 #include "nsIPermissionManager.h"
 #include "DOMCameraControl.h"
-#include "DOMCameraManager.h"
 #include "nsDOMClassInfo.h"
 #include "DictionaryHelpers.h"
 #include "CameraCommon.h"
+#include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/CameraManagerBinding.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(nsDOMCameraManager, mWindow)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCameraManager)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCameraManager)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCameraManager)
 
 /**
  * Global camera logging object
  *
  * Set the NSPR_LOG_MODULES environment variable to enable logging
  * in a debug build, e.g. NSPR_LOG_MODULES=Camera:5
  */
 PRLogModuleInfo*
 GetCameraLog()
 {
   static PRLogModuleInfo *sLog;
-  if (!sLog)
+  if (!sLog) {
     sLog = PR_NewLogModule("Camera");
+  }
   return sLog;
 }
 
-/**
- * nsDOMCameraManager::GetListOfCameras
- * is implementation-specific, and can be found in (e.g.)
- * GonkCameraManager.cpp and FallbackCameraManager.cpp.
- */
-
 WindowTable* nsDOMCameraManager::sActiveWindows = nullptr;
 
 nsDOMCameraManager::nsDOMCameraManager(nsPIDOMWindow* aWindow)
   : mWindowId(aWindow->WindowID())
-  , mCameraThread(nullptr)
   , mWindow(aWindow)
 {
   /* member initializers and constructor code */
   DOM_CAMERA_LOGT("%s:%d : this=%p, windowId=%llx\n", __func__, __LINE__, this, mWindowId);
+  MOZ_COUNT_CTOR(nsDOMCameraManager);
   SetIsDOMBinding();
 }
 
 nsDOMCameraManager::~nsDOMCameraManager()
 {
   /* destructor code */
+  MOZ_COUNT_DTOR(nsDOMCameraManager);
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  if (obs) {
-    obs->RemoveObserver(this, "xpcom-shutdown");
-  }
+}
+
+void
+nsDOMCameraManager::GetListOfCameras(nsTArray<nsString>& aList, ErrorResult& aRv)
+{
+  aRv = ICameraControl::GetListOfCameras(aList);
 }
 
 bool
 nsDOMCameraManager::CheckPermission(nsPIDOMWindow* aWindow)
 {
   nsCOMPtr<nsIPermissionManager> permMgr =
     do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
   NS_ENSURE_TRUE(permMgr, false);
@@ -101,40 +102,38 @@ nsDOMCameraManager::CreateInstance(nsPID
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   obs->AddObserver(cameraManager, "xpcom-shutdown", true);
 
   return cameraManager.forget();
 }
 
 void
-nsDOMCameraManager::GetCamera(const CameraSelector& aOptions,
-                              nsICameraGetCameraCallback* onSuccess,
-                              const Optional<nsICameraErrorCallback*>& onError,
+nsDOMCameraManager::GetCamera(const nsAString& aCamera,
+                              const CameraConfiguration& aInitialConfig,
+                              GetCameraCallback& aOnSuccess,
+                              const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
                               ErrorResult& aRv)
 {
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+
   uint32_t cameraId = 0;  // back (or forward-facing) camera by default
-  if (aOptions.mCamera.EqualsLiteral("front")) {
+  if (aCamera.EqualsLiteral("front")) {
     cameraId = 1;
   }
 
-  // reuse the same camera thread to conserve resources
-  if (!mCameraThread) {
-    aRv = NS_NewThread(getter_AddRefs(mCameraThread));
-    if (aRv.Failed()) {
-      return;
-    }
+  nsCOMPtr<CameraErrorCallback> errorCallback = nullptr;
+  if (aOnError.WasPassed()) {
+    errorCallback = &aOnError.Value();
   }
 
-  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-
-  // Creating this object will trigger the onSuccess handler
+  // Creating this object will trigger the aOnSuccess callback
+  //  (or the aOnError one, if it fails).
   nsRefPtr<nsDOMCameraControl> cameraControl =
-    new nsDOMCameraControl(cameraId, mCameraThread,
-                           onSuccess, onError.WasPassed() ? onError.Value() : nullptr, mWindow);
+    new nsDOMCameraControl(cameraId, aInitialConfig, &aOnSuccess, errorCallback, mWindow);
 
   Register(cameraControl);
 }
 
 void
 nsDOMCameraManager::Register(nsDOMCameraControl* aDOMCameraControl)
 {
   DOM_CAMERA_LOGI(">>> Register( aDOMCameraControl = %p ) mWindowId = 0x%llx\n", aDOMCameraControl, mWindowId);
--- a/dom/camera/DOMCameraManager.h
+++ b/dom/camera/DOMCameraManager.h
@@ -5,34 +5,34 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DOM_CAMERA_DOMCAMERAMANAGER_H
 #define DOM_CAMERA_DOMCAMERAMANAGER_H
 
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
-#include "nsIThread.h"
 #include "nsIObserver.h"
-#include "nsThreadUtils.h"
 #include "nsHashKeys.h"
 #include "nsWrapperCache.h"
 #include "nsWeakReference.h"
 #include "nsClassHashtable.h"
-#include "nsIDOMCameraManager.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/Attributes.h"
-#include "nsPIDOMWindow.h"
+
+class nsPIDOMWindow;
 
 namespace mozilla {
   class ErrorResult;
-class nsDOMCameraControl;
-namespace dom {
-class CameraSelector;
-}
+  class nsDOMCameraControl;
+  namespace dom {
+    class CameraConfiguration;
+    class GetCameraCallback;
+    class CameraErrorCallback;
+  }
 }
 
 typedef nsTArray<nsRefPtr<mozilla::nsDOMCameraControl> > CameraControls;
 typedef nsClassHashtable<nsUint64HashKey, CameraControls> WindowTable;
 
 class nsDOMCameraManager MOZ_FINAL
   : public nsIObserver
   , public nsSupportsWeakReference
@@ -47,23 +47,21 @@ public:
   static bool CheckPermission(nsPIDOMWindow* aWindow);
   static already_AddRefed<nsDOMCameraManager>
     CreateInstance(nsPIDOMWindow* aWindow);
   static bool IsWindowStillActive(uint64_t aWindowId);
 
   void Register(mozilla::nsDOMCameraControl* aDOMCameraControl);
   void OnNavigation(uint64_t aWindowId);
 
-  nsresult GetNumberOfCameras(int32_t& aDeviceCount);
-  nsresult GetCameraName(uint32_t aDeviceNum, nsCString& aDeviceName);
-
   // WebIDL
-  void GetCamera(const mozilla::dom::CameraSelector& aOptions,
-                 nsICameraGetCameraCallback* aCallback,
-                 const mozilla::dom::Optional<nsICameraErrorCallback*>& ErrorCallback,
+  void GetCamera(const nsAString& aCamera,
+                 const mozilla::dom::CameraConfiguration& aOptions,
+                 mozilla::dom::GetCameraCallback& aOnSuccess,
+                 const mozilla::dom::Optional<mozilla::dom::OwningNonNull<mozilla::dom::CameraErrorCallback> >& aOnError,
                  mozilla::ErrorResult& aRv);
   void GetListOfCameras(nsTArray<nsString>& aList, mozilla::ErrorResult& aRv);
 
   nsPIDOMWindow* GetParentObject() const { return mWindow; }
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
     MOZ_OVERRIDE;
 
 protected:
@@ -74,37 +72,17 @@ protected:
 private:
   nsDOMCameraManager() MOZ_DELETE;
   nsDOMCameraManager(nsPIDOMWindow* aWindow);
   nsDOMCameraManager(const nsDOMCameraManager&) MOZ_DELETE;
   nsDOMCameraManager& operator=(const nsDOMCameraManager&) MOZ_DELETE;
 
 protected:
   uint64_t mWindowId;
-  nsCOMPtr<nsIThread> mCameraThread;
   nsCOMPtr<nsPIDOMWindow> mWindow;
   /**
-   * 'mActiveWindows' is only ever accessed while in the main thread,
+   * 'sActiveWindows' is only ever accessed while in the Main Thread,
    * so it is not otherwise protected.
    */
   static ::WindowTable* sActiveWindows;
 };
 
-class GetCameraTask : public nsRunnable
-{
-public:
-  GetCameraTask(uint32_t aCameraId, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, nsIThread* aCameraThread)
-    : mCameraId(aCameraId)
-    , mOnSuccessCb(onSuccess)
-    , mOnErrorCb(onError)
-    , mCameraThread(aCameraThread)
-  { }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE;
-
-protected:
-  uint32_t mCameraId;
-  nsCOMPtr<nsICameraGetCameraCallback> mOnSuccessCb;
-  nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
-  nsCOMPtr<nsIThread> mCameraThread;
-};
-
 #endif // DOM_CAMERA_DOMCAMERAMANAGER_H
deleted file mode 100644
--- a/dom/camera/DOMCameraPreview.cpp
+++ /dev/null
@@ -1,304 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "base/basictypes.h"
-#include "Layers.h"
-#include "VideoUtils.h"
-#include "DOMCameraPreview.h"
-#include "CameraCommon.h"
-#include "nsGlobalWindow.h"
-#include "nsIDocument.h"
-#include "nsPIDOMWindow.h"
-
-using namespace mozilla;
-using namespace mozilla::layers;
-
-/**
- * 'PreviewControl' is a helper class that dispatches preview control
- * events from the main thread.
- *
- * NS_NewRunnableMethod() can't be used because it AddRef()s the method's
- * object, which can't be done off the main thread for cycle collection
- * participants.
- *
- * Before using this class, 'aDOMPreview' must be appropriately AddRef()ed.
- */
-class PreviewControl : public nsRunnable
-{
-public:
-  enum {
-    START,
-    STOP,
-    STARTED,
-    STOPPED
-  };
-  PreviewControl(DOMCameraPreview* aDOMPreview, uint32_t aControl)
-    : mDOMPreview(aDOMPreview)
-    , mControl(aControl)
-  { }
-
-  NS_IMETHOD Run()
-  {
-    NS_ASSERTION(NS_IsMainThread(), "PreviewControl not run on main thread");
-
-    switch (mControl) {
-      case START:
-        mDOMPreview->Start();
-        break;
-
-      case STOP:
-        mDOMPreview->StopPreview();
-        break;
-
-      case STARTED:
-        mDOMPreview->SetStateStarted();
-        break;
-
-      case STOPPED:
-        mDOMPreview->SetStateStopped();
-        break;
-
-      default:
-        DOM_CAMERA_LOGE("PreviewControl: invalid control %d\n", mControl);
-        break;
-    }
-
-    return NS_OK;
-  }
-
-protected:
-  /**
-   * This must be a raw pointer because this class is not created on the
-   * main thread, and DOMCameraPreview is not threadsafe.  Prior to
-   * issuing a preview control event, the caller must ensure that
-   * mDOMPreview will not disappear.
-   */
-  DOMCameraPreview* mDOMPreview;
-  uint32_t mControl;
-};
-
-class DOMCameraPreviewListener : public MediaStreamListener
-{
-public:
-  DOMCameraPreviewListener(DOMCameraPreview* aDOMPreview) :
-    mDOMPreview(aDOMPreview)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  ~DOMCameraPreviewListener()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  void NotifyConsumptionChanged(MediaStreamGraph* aGraph, Consumption aConsuming)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-
-#ifdef PR_LOGGING
-    const char* state;
-
-    switch (aConsuming) {
-      case NOT_CONSUMED:
-        state = "not consuming";
-        break;
-
-      case CONSUMED:
-        state = "consuming";
-        break;
-
-      default:
-        state = "unknown";
-        break;
-    }
-
-    DOM_CAMERA_LOGA("camera viewfinder is %s\n", state);
-#endif
-    nsCOMPtr<nsIRunnable> previewControl;
-
-    switch (aConsuming) {
-      case NOT_CONSUMED:
-        previewControl = new PreviewControl(mDOMPreview, PreviewControl::STOP);
-        break;
-
-      case CONSUMED:
-        previewControl = new PreviewControl(mDOMPreview, PreviewControl::START);
-        break;
-
-      default:
-        return;
-    }
-
-    nsresult rv = NS_DispatchToMainThread(previewControl);
-    if (NS_FAILED(rv)) {
-      DOM_CAMERA_LOGE("Failed to dispatch preview control (%d)!\n", rv);
-    }
-  }
-
-protected:
-  // Raw pointer; if we exist, 'mDOMPreview' exists as well
-  DOMCameraPreview* mDOMPreview;
-};
-
-DOMCameraPreview::DOMCameraPreview(nsGlobalWindow* aWindow,
-                                   ICameraControl* aCameraControl,
-                                   uint32_t aWidth, uint32_t aHeight,
-                                   uint32_t aFrameRate)
-  : DOMMediaStream()
-  , mState(STOPPED)
-  , mWidth(aWidth)
-  , mHeight(aHeight)
-  , mFramesPerSecond(aFrameRate)
-  , mFrameCount(0)
-  , mCameraControl(aCameraControl)
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p : mWidth=%d, mHeight=%d, mFramesPerSecond=%d\n", __func__, __LINE__, this, mWidth, mHeight, mFramesPerSecond);
-
-  mImageContainer = LayerManager::CreateImageContainer();
-  mWindow = aWindow;
-  mInput = new CameraPreviewMediaStream(this);
-  mStream = mInput;
-
-  mListener = new DOMCameraPreviewListener(this);
-  mInput->AddListener(mListener);
-
-  if (aWindow->GetExtantDoc()) {
-    CombineWithPrincipal(aWindow->GetExtantDoc()->NodePrincipal());
-  }
-}
-
-DOMCameraPreview::~DOMCameraPreview()
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p, mListener=%p\n", __func__, __LINE__, this, mListener);
-  mInput->RemoveListener(mListener);
-}
-
-bool
-DOMCameraPreview::HaveEnoughBuffered()
-{
-  return true;
-}
-
-bool
-DOMCameraPreview::ReceiveFrame(void* aBuffer, ImageFormat aFormat, FrameBuilder aBuilder)
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  if (!aBuffer || !aBuilder) {
-    return false;
-  }
-  if (mState != STARTED) {
-    return false;
-  }
-
-  nsRefPtr<Image> image = mImageContainer->CreateImage(aFormat);
-  aBuilder(image, aBuffer, mWidth, mHeight);
-
-  mInput->SetCurrentFrame(gfxIntSize(mWidth, mHeight), image);
-  return true;
-}
-
-void
-DOMCameraPreview::Start()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Start() not called from main thread");
-  if (mState != STOPPED) {
-    return;
-  }
-
-  DOM_CAMERA_LOGI("Starting preview stream\n");
-
-  /**
-   * Add a reference to ourselves to make sure we stay alive while
-   * the preview is running, as the CameraControlImpl object holds a
-   * weak reference to us.
-   *
-   * This reference is removed in SetStateStopped().
-   */
-  NS_ADDREF_THIS();
-  DOM_CAMERA_SETSTATE(STARTING);
-  mCameraControl->StartPreview(this);
-}
-
-void
-DOMCameraPreview::SetStateStarted()
-{
-  NS_ASSERTION(NS_IsMainThread(), "SetStateStarted() not called from main thread");
-
-  DOM_CAMERA_SETSTATE(STARTED);
-  DOM_CAMERA_LOGI("Preview stream started\n");
-}
-
-void
-DOMCameraPreview::Started()
-{
-  if (mState != STARTING) {
-    return;
-  }
-
-  DOM_CAMERA_LOGI("Dispatching preview stream started\n");
-  nsCOMPtr<nsIRunnable> started = new PreviewControl(this, PreviewControl::STARTED);
-  nsresult rv = NS_DispatchToMainThread(started);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGE("failed to set statrted state (%d), POTENTIAL MEMORY LEAK!\n", rv);
-  }
-}
-
-void
-DOMCameraPreview::StopPreview()
-{
-  NS_ASSERTION(NS_IsMainThread(), "StopPreview() not called from main thread");
-  if (mState != STARTED) {
-    return;
-  }
-
-  DOM_CAMERA_LOGI("Stopping preview stream\n");
-  DOM_CAMERA_SETSTATE(STOPPING);
-  mCameraControl->StopPreview();
-  //mInput->EndTrack(TRACK_VIDEO);
-  //mInput->Finish();
-}
-
-void
-DOMCameraPreview::SetStateStopped()
-{
-  NS_ASSERTION(NS_IsMainThread(), "SetStateStopped() not called from main thread");
-
-  // see bug 809259 and bug 817367.
-  if (mState != STOPPING) {
-    //mInput->EndTrack(TRACK_VIDEO);
-    //mInput->Finish();
-  }
-  DOM_CAMERA_SETSTATE(STOPPED);
-  DOM_CAMERA_LOGI("Preview stream stopped\n");
-
-  /**
-   * Only remove the reference added in Start() once the preview
-   * has stopped completely.
-   */
-  NS_RELEASE_THIS();
-}
-
-void
-DOMCameraPreview::Stopped(bool aForced)
-{
-  if (mState != STOPPING && !aForced) {
-    return;
-  }
-
-  mInput->ClearCurrentFrame();
-
-  DOM_CAMERA_LOGI("Dispatching preview stream stopped\n");
-  nsCOMPtr<nsIRunnable> stopped = new PreviewControl(this, PreviewControl::STOPPED);
-  nsresult rv = NS_DispatchToMainThread(stopped);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGE("failed to decrement reference count (%d), MEMORY LEAK!\n", rv);
-  }
-}
-
-void
-DOMCameraPreview::Error()
-{
-  DOM_CAMERA_LOGE("Error occurred changing preview state!\n");
-  Stopped(true);
-}
deleted file mode 100644
--- a/dom/camera/DOMCameraPreview.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef DOM_CAMERA_DOMCAMERAPREVIEW_H
-#define DOM_CAMERA_DOMCAMERAPREVIEW_H
-
-#include "nsCycleCollectionParticipant.h"
-#include "MediaStreamGraph.h"
-#include "StreamBuffer.h"
-#include "ICameraControl.h"
-#include "DOMMediaStream.h"
-#include "CameraPreviewMediaStream.h"
-#include "CameraCommon.h"
-
-class nsGlobalWindow;
-
-namespace mozilla {
-
-typedef void (*FrameBuilder)(mozilla::layers::Image* aImage, void* aBuffer, uint32_t aWidth, uint32_t aHeight);
-
-/**
- * DOMCameraPreview is only exposed to the DOM as an nsDOMMediaStream,
- * which is a cycle-collection participant already, and we don't
- * add any traceable fields here, so we don't need to declare any
- * more cycle-collection goop.
- */
-class DOMCameraPreview : public DOMMediaStream
-{
-protected:
-  enum { TRACK_VIDEO = 1 };
-
-public:
-  DOMCameraPreview(nsGlobalWindow* aWindow, ICameraControl* aCameraControl,
-                   uint32_t aWidth, uint32_t aHeight, uint32_t aFramesPerSecond = 30);
-
-  bool ReceiveFrame(void* aBuffer, ImageFormat aFormat, mozilla::FrameBuilder aBuilder);
-  bool HaveEnoughBuffered();
-
-  void Start();   // called by the MediaStreamListener to start preview
-  void Started(); // called by the CameraControl when preview is started
-  void StopPreview(); // called by the MediaStreamListener to stop preview
-  void Stopped(bool aForced = false);
-                  // called by the CameraControl when preview is stopped
-  void Error();   // something went wrong, NS_RELEASE needed
-
-  void SetStateStarted();
-  void SetStateStopped();
-
-protected:
-  virtual ~DOMCameraPreview();
-
-  enum {
-    STOPPED,
-    STARTING,
-    STARTED,
-    STOPPING
-  };
-  uint32_t mState;
-
-  // Helper function, used in conjunction with the macro below, to make
-  //  it easy to track state changes, which must happen only on the main
-  //  thread.
-  void
-  SetState(uint32_t aNewState, const char* aFileOrFunc, int aLine)
-  {
-#ifdef PR_LOGGING
-    const char* states[] = { "stopped", "starting", "started", "stopping" };
-    MOZ_ASSERT(mState < sizeof(states) / sizeof(states[0]));
-    MOZ_ASSERT(aNewState < sizeof(states) / sizeof(states[0]));
-    DOM_CAMERA_LOGI("SetState: (this=%p) '%s' --> '%s' : %s:%d\n", this, states[mState], states[aNewState], aFileOrFunc, aLine);
-#endif
-
-    NS_ASSERTION(NS_IsMainThread(), "Preview state set OFF OF main thread!");
-    mState = aNewState;
-  }
-
-  uint32_t mWidth;
-  uint32_t mHeight;
-  uint32_t mFramesPerSecond;
-  CameraPreviewMediaStream* mInput;
-  nsRefPtr<mozilla::layers::ImageContainer> mImageContainer;
-  VideoSegment mVideoSegment;
-  uint32_t mFrameCount;
-  nsRefPtr<ICameraControl> mCameraControl;
-
-  // Raw pointer; AddListener() keeps the reference for us
-  MediaStreamListener* mListener;
-
-private:
-  DOMCameraPreview(const DOMCameraPreview&) MOZ_DELETE;
-  DOMCameraPreview& operator=(const DOMCameraPreview&) MOZ_DELETE;
-};
-
-} // namespace mozilla
-
-#define DOM_CAMERA_SETSTATE(newState)   SetState((newState), __func__, __LINE__)
-
-#endif // DOM_CAMERA_DOMCAMERAPREVIEW_H
--- a/dom/camera/FallbackCameraCapabilities.cpp
+++ b/dom/camera/FallbackCameraCapabilities.cpp
@@ -1,15 +1,14 @@
 /* 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 "nsDOMClassInfoID.h"
+#include "DOMCameraCapabilities.h"
 #include "DOMCameraControl.h"
-#include "DOMCameraCapabilities.h"
 #include "CameraCommon.h"
 
 using namespace mozilla;
 
 DOMCI_DATA(CameraCapabilities, nsICameraCapabilities)
 
 NS_INTERFACE_MAP_BEGIN(DOMCameraCapabilities)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
--- a/dom/camera/FallbackCameraControl.cpp
+++ b/dom/camera/FallbackCameraControl.cpp
@@ -1,216 +1,75 @@
 /* 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 "DOMCameraControl.h"
 #include "CameraControlImpl.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 namespace mozilla {
-class RecorderProfileManager;
-}
-
-/**
- * Fallback camera control subclass.  Can be used as a template for the
- * definition of new camera support classes.
- */
-class nsFallbackCameraControl : public CameraControlImpl
-{
-public:
-  nsFallbackCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId);
-
-  const char* GetParameter(const char* aKey);
-  const char* GetParameterConstChar(uint32_t aKey);
-  double GetParameterDouble(uint32_t aKey);
-  int32_t GetParameterInt32(uint32_t aKey);
-  void GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions);
-  void GetParameter(uint32_t aKey, idl::CameraSize& aSize);
-  void SetParameter(const char* aKey, const char* aValue);
-  void SetParameter(uint32_t aKey, const char* aValue);
-  void SetParameter(uint32_t aKey, double aValue);
-  void SetParameter(uint32_t aKey, const nsTArray<idl::CameraRegion>& aRegions);
-  void SetParameter(uint32_t aKey, const idl::CameraSize& aSize);
-  nsresult GetVideoSizes(nsTArray<idl::CameraSize>& aVideoSizes);
-  nsresult PushParameters();
-
-protected:
-  ~nsFallbackCameraControl();
+  class RecorderProfileManager;
 
-  nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream);
-  nsresult StartPreviewImpl(StartPreviewTask* aStartPreview);
-  nsresult StopPreviewImpl(StopPreviewTask* aStopPreview);
-  nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus);
-  nsresult TakePictureImpl(TakePictureTask* aTakePicture);
-  nsresult StartRecordingImpl(StartRecordingTask* aStartRecording);
-  nsresult StopRecordingImpl(StopRecordingTask* aStopRecording);
-  nsresult PushParametersImpl();
-  nsresult PullParametersImpl();
-  already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl();
-
-private:
-  nsFallbackCameraControl(const nsFallbackCameraControl&) MOZ_DELETE;
-  nsFallbackCameraControl& operator=(const nsFallbackCameraControl&) MOZ_DELETE;
-};
-
-/**
- * Stub implementation of the DOM-facing camera control constructor.
- *
- * This should never get called--it exists to keep the linker happy; if
- * implemented, it should construct (e.g.) nsFallbackCameraControl and
- * store a reference in the 'mCameraControl' member (which is why it is
- * defined here).
- */
-nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, nsPIDOMWindow* aWindow) :
-  mWindow(aWindow)
-{
-  MOZ_ASSERT(aWindow, "shouldn't be created with null window!");
-  SetIsDOMBinding();
+  namespace layers {
+    class GraphicBufferLocked;
+  }
 }
 
 /**
- * Stub implemetations of the fallback camera control.
- *
- * None of these should ever get called--they exist to keep the linker happy,
- * and may be used as templates for new camera support classes.
+ * Fallback camera control subclass. Can be used as a template for the
+ * definition of new camera support classes.
  */
-nsFallbackCameraControl::nsFallbackCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
-  : CameraControlImpl(aCameraId, aCameraThread, aWindowId)
-{
-}
-
-nsFallbackCameraControl::~nsFallbackCameraControl()
-{
-}
-
-const char*
-nsFallbackCameraControl::GetParameter(const char* aKey)
-{
-  return nullptr;
-}
-
-const char*
-nsFallbackCameraControl::GetParameterConstChar(uint32_t aKey)
-{
-  return nullptr;
-}
-
-double
-nsFallbackCameraControl::GetParameterDouble(uint32_t aKey)
-{
-  return NAN;
-}
-
-int32_t
-nsFallbackCameraControl::GetParameterInt32(uint32_t aKey)
-{
-  return 0;
-}
-
-void
-nsFallbackCameraControl::GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions)
-{
-}
-
-void
-nsFallbackCameraControl::GetParameter(uint32_t aKey, idl::CameraSize& aSize)
-{
-}
-
-void
-nsFallbackCameraControl::SetParameter(const char* aKey, const char* aValue)
-{
-}
-
-void
-nsFallbackCameraControl::SetParameter(uint32_t aKey, const char* aValue)
-{
-}
-
-void
-nsFallbackCameraControl::SetParameter(uint32_t aKey, double aValue)
-{
-}
-
-void
-nsFallbackCameraControl::SetParameter(uint32_t aKey, const nsTArray<idl::CameraRegion>& aRegions)
-{
-}
-
-void
-nsFallbackCameraControl::SetParameter(uint32_t aKey, const idl::CameraSize& aSize)
+class FallbackCameraControl : public CameraControlImpl
 {
-}
+public:
+  FallbackCameraControl(uint32_t aCameraId) : CameraControlImpl(aCameraId) { }
 
-nsresult
-nsFallbackCameraControl::PushParameters()
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-nsresult
-nsFallbackCameraControl::GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
+  void OnAutoFocusComplete(bool aSuccess);
+  void OnTakePictureComplete(uint8_t* aData, uint32_t aLength) { }
+  void OnTakePictureError() { }
+  void OnNewPreviewFrame(layers::GraphicBufferLocked* aBuffer) { }
+  void OnRecorderEvent(int msg, int ext1, int ext2) { }
+  void OnError(CameraControlListener::CameraErrorContext aWhere,
+               CameraControlListener::CameraError aError) { }
 
-nsresult
-nsFallbackCameraControl::StartPreviewImpl(StartPreviewTask* aGetPreviewStream)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
+  virtual nsresult Set(uint32_t aKey, const nsAString& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, nsAString& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Set(uint32_t aKey, double aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, double& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Set(uint32_t aKey, int32_t aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, int32_t& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Set(uint32_t aKey, int64_t aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, int64_t& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Set(uint32_t aKey, const Size& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, Size& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Set(uint32_t aKey, const nsTArray<Region>& aRegions) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, nsTArray<Region>& aRegions) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
 
-nsresult
-nsFallbackCameraControl::StopPreviewImpl(StopPreviewTask* aGetPreviewStream)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-nsresult
-nsFallbackCameraControl::AutoFocusImpl(AutoFocusTask* aAutoFocus)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
+  virtual nsresult SetLocation(const Position& aLocation) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
 
-nsresult
-nsFallbackCameraControl::TakePictureImpl(TakePictureTask* aTakePicture)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
+  virtual nsresult Get(uint32_t aKey, nsTArray<Size>& aSizes) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, nsTArray<nsString>& aValues) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, nsTArray<double>& aValues) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
 
-nsresult
-nsFallbackCameraControl::StartRecordingImpl(StartRecordingTask* aStartRecording)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
+  nsresult PushParameters() { return NS_ERROR_FAILURE; }
+  nsresult PullParameters() { return NS_ERROR_FAILURE; }
 
-nsresult
-nsFallbackCameraControl::StopRecordingImpl(StopRecordingTask* aStopRecording)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
+protected:
+  ~FallbackCameraControl();
 
-nsresult
-nsFallbackCameraControl::PushParametersImpl()
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-nsresult
-nsFallbackCameraControl::PullParametersImpl()
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
+  virtual nsresult StartPreviewImpl() { return NS_ERROR_FAILURE; }
+  virtual nsresult StopPreviewImpl() { return NS_ERROR_FAILURE; }
+  virtual nsresult AutoFocusImpl(bool aCancelExistingCall) { return NS_ERROR_FAILURE; }
+  virtual nsresult TakePictureImpl() { return NS_ERROR_FAILURE; }
+  virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
+                                      const StartRecordingOptions* aOptions = nullptr)
+                                        { return NS_ERROR_FAILURE; }
+  virtual nsresult StopRecordingImpl() { return NS_ERROR_FAILURE; }
+  virtual nsresult PushParametersImpl() { return NS_ERROR_FAILURE; }
+  virtual nsresult PullParametersImpl() { return NS_ERROR_FAILURE; }
+  virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl() { return nullptr; }
 
-nsresult
-nsFallbackCameraControl::GetVideoSizes(nsTArray<idl::CameraSize>& aVideoSizes)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-already_AddRefed<RecorderProfileManager> 
-nsFallbackCameraControl::GetRecorderProfileManagerImpl()
-{
-  return nullptr;
-}
+private:
+  FallbackCameraControl(const FallbackCameraControl&) MOZ_DELETE;
+  FallbackCameraControl& operator=(const FallbackCameraControl&) MOZ_DELETE;
+};
--- a/dom/camera/FallbackCameraManager.cpp
+++ b/dom/camera/FallbackCameraManager.cpp
@@ -1,28 +1,32 @@
 /* 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 "DOMCameraManager.h"
-
-#include "mozilla/ErrorResult.h"
+#include "ICameraControl.h"
 
 using namespace mozilla;
 
-// From nsDOMCameraManager.
+// From ICameraControl.
 nsresult
-nsDOMCameraManager::GetNumberOfCameras(int32_t& aDeviceCount)
+ICameraControl::GetNumberOfCameras(int32_t& aDeviceCount)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 };
 
 nsresult
-nsDOMCameraManager::GetCameraName(uint32_t aDeviceNum, nsCString& aDeviceName)
+ICameraControl::GetCameraName(uint32_t aDeviceNum, nsCString& aDeviceName)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
-void
-nsDOMCameraManager::GetListOfCameras(nsTArray<nsString>& aList, ErrorResult& aRv)
+nsresult
+ICameraControl::GetListOfCameras(nsTArray<nsString>& aList)
 {
-  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+  return NS_ERROR_NOT_IMPLEMENTED;
 }
+
+already_AddRefed<ICameraControl>
+ICameraControl::Create(uint32_t aCameraId, const Configuration* aInitialConfig)
+{
+  return nullptr;
+}
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -1,976 +1,755 @@
 /*
- * Copyright (C) 2012 Mozilla Foundation
+ * Copyright (C) 2012-2014 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 "GonkCameraControl.h"
 #include <time.h>
 #include <string.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <libgen.h>
 #include "base/basictypes.h"
 #include "camera/CameraParameters.h"
 #include "nsCOMPtr.h"
-#include "nsDOMClassInfo.h"
 #include "nsMemory.h"
 #include "nsThread.h"
 #include <media/MediaProfiles.h>
 #include "mozilla/FileUtils.h"
 #include "mozilla/Services.h"
+#include "mozilla/unused.h"
 #include "nsAlgorithm.h"
 #include <media/mediaplayer.h>
 #include "nsPrintfCString.h"
 #include "nsIObserverService.h"
 #include "nsIVolume.h"
 #include "nsIVolumeService.h"
-#include "DOMCameraManager.h"
+#include "AutoRwLock.h"
 #include "GonkCameraHwMgr.h"
-#include "DOMCameraCapabilities.h"
-#include "DOMCameraControl.h"
 #include "GonkRecorderProfiles.h"
-#include "GonkCameraControl.h"
 #include "CameraCommon.h"
+#include "GonkCameraParameters.h"
 #include "DeviceStorageFileDescriptor.h"
 
 using namespace mozilla;
-using namespace mozilla::dom;
 using namespace mozilla::layers;
+using namespace mozilla::gfx;
 using namespace android;
-using mozilla::gfx::IntSize;
-
-/**
- * See bug 783682.  Most camera implementations, despite claiming they
- * support 'yuv420p' as a preview format, actually ignore this setting
- * and return 'yuv420sp' data anyway.  We have come across a new implementation
- * that, while reporting that 'yuv420p' is supported *and* has been accepted,
- * still returns the frame data in 'yuv420sp' anyway.  So for now, since
- * everyone seems to return this format, we just force it.
- */
-#define FORCE_PREVIEW_FORMAT_YUV420SP   1
 
 #define RETURN_IF_NO_CAMERA_HW()                                          \
   do {                                                                    \
     if (!mCameraHw.get()) {                                               \
       DOM_CAMERA_LOGE("%s:%d : mCameraHw is null\n", __func__, __LINE__); \
       return NS_ERROR_NOT_AVAILABLE;                                      \
     }                                                                     \
   } while(0)
 
-static const char* getKeyText(uint32_t aKey)
-{
-  switch (aKey) {
-    case CAMERA_PARAM_EFFECT:
-      return CameraParameters::KEY_EFFECT;
-    case CAMERA_PARAM_WHITEBALANCE:
-      return CameraParameters::KEY_WHITE_BALANCE;
-    case CAMERA_PARAM_SCENEMODE:
-      return CameraParameters::KEY_SCENE_MODE;
-    case CAMERA_PARAM_FLASHMODE:
-      return CameraParameters::KEY_FLASH_MODE;
-    case CAMERA_PARAM_FOCUSMODE:
-      return CameraParameters::KEY_FOCUS_MODE;
-    case CAMERA_PARAM_ZOOM:
-      return CameraParameters::KEY_ZOOM;
-    case CAMERA_PARAM_METERINGAREAS:
-      return CameraParameters::KEY_METERING_AREAS;
-    case CAMERA_PARAM_FOCUSAREAS:
-      return CameraParameters::KEY_FOCUS_AREAS;
-    case CAMERA_PARAM_FOCALLENGTH:
-      return CameraParameters::KEY_FOCAL_LENGTH;
-    case CAMERA_PARAM_FOCUSDISTANCENEAR:
-      return CameraParameters::KEY_FOCUS_DISTANCES;
-    case CAMERA_PARAM_FOCUSDISTANCEOPTIMUM:
-      return CameraParameters::KEY_FOCUS_DISTANCES;
-    case CAMERA_PARAM_FOCUSDISTANCEFAR:
-      return CameraParameters::KEY_FOCUS_DISTANCES;
-    case CAMERA_PARAM_EXPOSURECOMPENSATION:
-      return CameraParameters::KEY_EXPOSURE_COMPENSATION;
-    case CAMERA_PARAM_PICTURESIZE:
-      return CameraParameters::KEY_PICTURE_SIZE;
-    case CAMERA_PARAM_THUMBNAILQUALITY:
-      return CameraParameters::KEY_JPEG_THUMBNAIL_QUALITY;
-
-    case CAMERA_PARAM_SUPPORTED_PREVIEWSIZES:
-      return CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES;
-    case CAMERA_PARAM_SUPPORTED_VIDEOSIZES:
-      return CameraParameters::KEY_SUPPORTED_VIDEO_SIZES;
-    case CAMERA_PARAM_SUPPORTED_PICTURESIZES:
-      return CameraParameters::KEY_SUPPORTED_PICTURE_SIZES;
-    case CAMERA_PARAM_SUPPORTED_PICTUREFORMATS:
-      return CameraParameters::KEY_SUPPORTED_PICTURE_FORMATS;
-    case CAMERA_PARAM_SUPPORTED_WHITEBALANCES:
-      return CameraParameters::KEY_SUPPORTED_WHITE_BALANCE;
-    case CAMERA_PARAM_SUPPORTED_SCENEMODES:
-      return CameraParameters::KEY_SUPPORTED_SCENE_MODES;
-    case CAMERA_PARAM_SUPPORTED_EFFECTS:
-      return CameraParameters::KEY_SUPPORTED_EFFECTS;
-    case CAMERA_PARAM_SUPPORTED_FLASHMODES:
-      return CameraParameters::KEY_SUPPORTED_FLASH_MODES;
-    case CAMERA_PARAM_SUPPORTED_FOCUSMODES:
-      return CameraParameters::KEY_SUPPORTED_FOCUS_MODES;
-    case CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS:
-      return CameraParameters::KEY_MAX_NUM_FOCUS_AREAS;
-    case CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS:
-      return CameraParameters::KEY_MAX_NUM_METERING_AREAS;
-    case CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION:
-      return CameraParameters::KEY_MIN_EXPOSURE_COMPENSATION;
-    case CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION:
-      return CameraParameters::KEY_MAX_EXPOSURE_COMPENSATION;
-    case CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP:
-      return CameraParameters::KEY_EXPOSURE_COMPENSATION_STEP;
-    case CAMERA_PARAM_SUPPORTED_ZOOM:
-      return CameraParameters::KEY_ZOOM_SUPPORTED;
-    case CAMERA_PARAM_SUPPORTED_ZOOMRATIOS:
-      return CameraParameters::KEY_ZOOM_RATIOS;
-    case CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES:
-      return CameraParameters::KEY_SUPPORTED_JPEG_THUMBNAIL_SIZES;
-    default:
-      return nullptr;
-  }
-}
-
-// nsDOMCameraControl implementation-specific constructor
-nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, nsPIDOMWindow* aWindow)
-  : mDOMCapabilities(nullptr), mWindow(aWindow)
-{
-  MOZ_ASSERT(aWindow, "shouldn't be created with null window!");
-  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  SetIsDOMBinding();
-
-  /**
-   * nsDOMCameraControl is a cycle-collection participant, which means it is
-   * not threadsafe--so we need to bump up its reference count here to make
-   * sure that it exists long enough to be initialized.
-   *
-   * Once it is initialized, the GetCameraResult main-thread runnable will
-   * decrement it again to make sure it can be cleaned up.
-   *
-   * nsGonkCameraControl MUST NOT hold a strong reference to this
-   * nsDOMCameraControl or memory will leak!
-   */
-  NS_ADDREF_THIS();
-  nsRefPtr<nsGonkCameraControl> control = new nsGonkCameraControl(aCameraId, aCameraThread, this, onSuccess, onError, aWindow->WindowID());
-  control->DispatchInit(this, onSuccess, onError, aWindow->WindowID());
-  mCameraControl = control;
-}
-
-// Gonk-specific CameraControl implementation.
-
-// Initialize nsGonkCameraControl instance--runs on camera thread.
-class InitGonkCameraControl : public nsRunnable
-{
-public:
-  InitGonkCameraControl(nsGonkCameraControl* aCameraControl, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
-    : mCameraControl(aCameraControl)
-    , mDOMCameraControl(aDOMCameraControl)
-    , mOnSuccessCb(new nsMainThreadPtrHolder<nsICameraGetCameraCallback>(onSuccess))
-    , mOnErrorCb(new nsMainThreadPtrHolder<nsICameraErrorCallback>(onError))
-    , mWindowId(aWindowId)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  ~InitGonkCameraControl()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run()
-  {
-    nsresult rv = mCameraControl->Init();
-    return mDOMCameraControl->Result(rv, mOnSuccessCb, mOnErrorCb, mWindowId);
-  }
-
-  nsRefPtr<nsGonkCameraControl> mCameraControl;
-  // Raw pointer to DOM-facing camera control--it must NS_ADDREF itself for us
-  nsDOMCameraControl* mDOMCameraControl;
-  nsMainThreadPtrHandle<nsICameraGetCameraCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-  uint64_t mWindowId;
-};
-
 // Construct nsGonkCameraControl on the main thread.
-nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
-  : CameraControlImpl(aCameraId, aCameraThread, aWindowId)
-  , mExposureCompensationMin(0.0)
-  , mExposureCompensationStep(0.0)
-  , mDeferConfigUpdate(false)
-  , mWidth(0)
-  , mHeight(0)
-  , mLastPictureWidth(0)
-  , mLastPictureHeight(0)
-  , mLastThumbnailWidth(0)
-  , mLastThumbnailHeight(0)
-#if !FORCE_PREVIEW_FORMAT_YUV420SP
-  , mFormat(PREVIEW_FORMAT_UNKNOWN)
-#else
-  , mFormat(PREVIEW_FORMAT_YUV420SP)
-#endif
-  , mFps(30)
-  , mDiscardedFrameCount(0)
+nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId)
+  : CameraControlImpl(aCameraId)
+  , mLastPictureSize({0, 0})
+  , mLastThumbnailSize({0, 0})
+  , mPreviewFps(30)
+  , mResumePreviewAfterTakingPicture(false) // XXXmikeh - see bug 950102
+  , mDeferConfigUpdate(0)
   , mMediaProfiles(nullptr)
   , mRecorder(nullptr)
   , mProfileManager(nullptr)
   , mRecorderProfile(nullptr)
   , mVideoFile(nullptr)
+  , mReentrantMonitor("GonkCameraControl::OnTakePictureMonitor")
 {
   // Constructor runs on the main thread...
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  mRwLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "GonkCameraControl.Parameters.Lock");
-}
-
-void nsGonkCameraControl::DispatchInit(nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
-{
-  // ...but initialization is carried out on the camera thread.
-  nsCOMPtr<nsIRunnable> init = new InitGonkCameraControl(this, aDOMCameraControl, onSuccess, onError, aWindowId);
-  mCameraThread->Dispatch(init, NS_DISPATCH_NORMAL);
+  mImageContainer = LayerManager::CreateImageContainer();
 }
 
 nsresult
-nsGonkCameraControl::Init()
+nsGonkCameraControl::Init(const Configuration* aInitialConfig)
 {
+  class InitGonkCameraControl : public nsRunnable
+  {
+  public:
+    InitGonkCameraControl(nsGonkCameraControl* aCameraControl,
+                          const Configuration* aConfig)
+      : mCameraControl(aCameraControl)
+      , mHaveInitialConfig(false)
+    {
+      DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+      if (aConfig) {
+        mConfig = *aConfig;
+        mHaveInitialConfig = true;
+      }
+    }
+
+    ~InitGonkCameraControl()
+    {
+      DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+    }
+
+    /**
+     * For initialization, we try to return the camera control to the upper
+     * upper layer (i.e. the DOM) as quickly as possible. To do this, the
+     * camera is initialized in the following stages:
+     *
+     *  0. InitImpl() initializes the hardware;
+     *  1. SetConfigurationInternal() does the minimal configuration
+     *     required so that we can start the preview -and- report a valid
+     *     configuration to the upper layer;
+     *  2. OnHardwareStateChange() reports that the hardware is ready,
+     *     which the upper layer can (and does) use to return the camera
+     *     control object;
+     *  3. StartPreviewImpl() starts the flow of preview frames from the
+     *     camera hardware.
+     *
+     * The intent of the above flow is to let the Main Thread do as much work
+     * up-front as possible without waiting for blocking Camera Thread calls
+     * to complete.
+     */
+    NS_IMETHODIMP
+    Run() MOZ_OVERRIDE
+    {
+      nsresult rv = mCameraControl->InitImpl();
+      if (NS_FAILED(rv)) {
+        mCameraControl->OnError(CameraControlListener::kInGetCamera,
+                                CameraControlListener::kErrorInitFailed);
+        // The hardware failed to initialize, so close it up
+        mCameraControl->ReleaseHardware();
+        return rv;
+      }
+
+      if (mHaveInitialConfig) {
+        rv = mCameraControl->SetConfigurationInternal(mConfig);
+        if (NS_FAILED(rv)) {
+          mCameraControl->OnError(CameraControlListener::kInGetCamera,
+                                  CameraControlListener::kErrorInvalidConfiguration);
+          // The initial configuration failed, close up the hardware
+          mCameraControl->ReleaseHardware();
+          return rv;
+        }
+      }
+
+      mCameraControl->OnHardwareStateChange(CameraControlListener::kHardwareOpen);
+      return mCameraControl->StartPreviewImpl();
+    }
+
+  protected:
+    nsRefPtr<nsGonkCameraControl> mCameraControl;
+    Configuration mConfig;
+    bool mHaveInitialConfig;
+  };
+
+  // Initialization is carried out on the camera thread.
+  return mCameraThread->Dispatch(
+    new InitGonkCameraControl(this, aInitialConfig), NS_DISPATCH_NORMAL);
+}
+
+nsresult
+nsGonkCameraControl::InitImpl()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
+
   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_FAILURE;
   }
 
   DOM_CAMERA_LOGI("Initializing camera %d (this=%p, mCameraHw=%p)\n", mCameraId, this, mCameraHw.get());
 
   // Initialize our camera configuration database.
   PullParametersImpl();
 
-  // Try to set preferred image format and frame rate
-#if !FORCE_PREVIEW_FORMAT_YUV420SP
-  DOM_CAMERA_LOGI("Camera preview formats: %s\n", mParams.get(mParams.KEY_SUPPORTED_PREVIEW_FORMATS));
-  const char* const PREVIEW_FORMAT = "yuv420p";
-  const char* const BAD_PREVIEW_FORMAT = "yuv420sp";
-  mParams.setPreviewFormat(PREVIEW_FORMAT);
-  mParams.setPreviewFrameRate(mFps);
-#else
-  mParams.setPreviewFormat("yuv420sp");
-  mParams.setPreviewFrameRate(mFps);
-#endif
+  // Set preferred preview frame format.
+  mParams.Set(CAMERA_PARAM_PREVIEWFORMAT, NS_LITERAL_STRING("yuv420sp"));
   PushParametersImpl();
 
-  // Check that our settings stuck
-  PullParametersImpl();
-#if !FORCE_PREVIEW_FORMAT_YUV420SP
-  const char* format = mParams.getPreviewFormat();
-  if (strcmp(format, PREVIEW_FORMAT) == 0) {
-    mFormat = PREVIEW_FORMAT_YUV420P;  /* \o/ */
-  } else if (strcmp(format, BAD_PREVIEW_FORMAT) == 0) {
-    mFormat = PREVIEW_FORMAT_YUV420SP;
-    DOM_CAMERA_LOGA("Camera ignored our request for '%s' preview, will have to convert (from %d)\n", PREVIEW_FORMAT, mFormat);
-  } else {
-    mFormat = PREVIEW_FORMAT_UNKNOWN;
-    DOM_CAMERA_LOGE("Camera ignored our request for '%s' preview, returned UNSUPPORTED format '%s'\n", PREVIEW_FORMAT, format);
-  }
-#endif
+  // Grab any other settings we'll need later.
+  mParams.Get(CAMERA_PARAM_PICTURE_FILEFORMAT, mFileFormat);
+  mParams.Get(CAMERA_PARAM_THUMBNAILSIZE, mLastThumbnailSize);
 
-  // Check the frame rate and log if the camera ignored our setting
-  uint32_t fps = mParams.getPreviewFrameRate();
-  if (fps != mFps) {
-    DOM_CAMERA_LOGA("We asked for %d fps but camera returned %d fps, using that", mFps, fps);
-    mFps = fps;
-  }
+  // The emulator's camera returns -1 for these values; bump them up to 0
+  int areas;
+  mParams.Get(CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS, areas);
+  mCurrentConfiguration.mMaxMeteringAreas = areas != -1 ? areas : 0;
+  mParams.Get(CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS, areas);
+  mCurrentConfiguration.mMaxFocusAreas = areas != -1 ? areas : 0;
 
-  // Grab any other settings we'll need later.
-  mExposureCompensationMin = mParams.getFloat(mParams.KEY_MIN_EXPOSURE_COMPENSATION);
-  mExposureCompensationStep = mParams.getFloat(mParams.KEY_EXPOSURE_COMPENSATION_STEP);
-  mMaxMeteringAreas = mParams.getInt(mParams.KEY_MAX_NUM_METERING_AREAS);
-  mMaxFocusAreas = mParams.getInt(mParams.KEY_MAX_NUM_FOCUS_AREAS);
-  mLastThumbnailWidth = mParams.getInt(mParams.KEY_JPEG_THUMBNAIL_WIDTH);
-  mLastThumbnailHeight = mParams.getInt(mParams.KEY_JPEG_THUMBNAIL_HEIGHT);
+  mParams.Get(CAMERA_PARAM_PICTURE_SIZE, mLastPictureSize);
+  mParams.Get(CAMERA_PARAM_PREVIEWSIZE, mCurrentConfiguration.mPreviewSize);
+  mParams.Get(CAMERA_PARAM_VIDEOSIZE, mLastRecorderSize);
 
-  int w;
-  int h;
-  mParams.getPictureSize(&w, &h);
-  MOZ_ASSERT(w > 0 && h > 0); // make sure the driver returns sane values
-  mLastPictureWidth = static_cast<uint32_t>(w);
-  mLastPictureHeight = static_cast<uint32_t>(h);
-
-  DOM_CAMERA_LOGI(" - minimum exposure compensation: %f\n", mExposureCompensationMin);
-  DOM_CAMERA_LOGI(" - exposure compensation step:    %f\n", mExposureCompensationStep);
-  DOM_CAMERA_LOGI(" - maximum metering areas:        %d\n", mMaxMeteringAreas);
-  DOM_CAMERA_LOGI(" - maximum focus areas:           %d\n", mMaxFocusAreas);
-  DOM_CAMERA_LOGI(" - default picture size:          %u x %u\n", mLastPictureWidth, mLastPictureHeight);
-  DOM_CAMERA_LOGI(" - default thumbnail size:        %u x %u\n", mLastThumbnailWidth, mLastThumbnailHeight);
+  DOM_CAMERA_LOGI(" - maximum metering areas:        %u\n", mCurrentConfiguration.mMaxMeteringAreas);
+  DOM_CAMERA_LOGI(" - maximum focus areas:           %u\n", mCurrentConfiguration.mMaxFocusAreas);
+  DOM_CAMERA_LOGI(" - default picture size:          %u x %u\n",
+    mLastPictureSize.width, mLastPictureSize.height);
+  DOM_CAMERA_LOGI(" - default thumbnail size:        %u x %u\n",
+    mLastThumbnailSize.width, mLastThumbnailSize.height);
+  DOM_CAMERA_LOGI(" - default preview size:          %u x %u\n",
+    mCurrentConfiguration.mPreviewSize.width, mCurrentConfiguration.mPreviewSize.height);
+  DOM_CAMERA_LOGI(" - default video recorder size:   %u x %u\n",
+    mLastRecorderSize.width, mLastRecorderSize.height);
+  DOM_CAMERA_LOGI(" - default picture file format:   %s\n",
+    NS_ConvertUTF16toUTF8(mFileFormat).get());
 
   return NS_OK;
 }
 
 nsGonkCameraControl::~nsGonkCameraControl()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p, mCameraHw = %p\n", __func__, __LINE__, this, mCameraHw.get());
 
-  ReleaseHardwareImpl(nullptr);
-  if (mRwLock) {
-    PRRWLock* lock = mRwLock;
-    mRwLock = nullptr;
-    PR_DestroyRWLock(lock);
-  }
-
+  ReleaseHardwareImpl();
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 }
 
-class RwAutoLockRead
-{
-public:
-  RwAutoLockRead(PRRWLock* aRwLock)
-    : mRwLock(aRwLock)
-  {
-    PR_RWLock_Rlock(mRwLock);
-  }
-
-  ~RwAutoLockRead()
-  {
-    PR_RWLock_Unlock(mRwLock);
-  }
-
-protected:
-  PRRWLock* mRwLock;
-};
-
-class RwAutoLockWrite
+nsresult
+nsGonkCameraControl::SetConfigurationInternal(const Configuration& aConfig)
 {
-public:
-  RwAutoLockWrite(PRRWLock* aRwLock)
-    : mRwLock(aRwLock)
-  {
-    PR_RWLock_Wlock(mRwLock);
-  }
-
-  ~RwAutoLockWrite()
-  {
-    PR_RWLock_Unlock(mRwLock);
-  }
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 
-protected:
-  PRRWLock* mRwLock;
-};
-
-const char*
-nsGonkCameraControl::GetParameter(const char* aKey)
-{
-  RwAutoLockRead lock(mRwLock);
-  return mParams.get(aKey);
-}
-
-const char*
-nsGonkCameraControl::GetParameterConstChar(uint32_t aKey)
-{
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    return nullptr;
-  }
+  nsresult rv;
 
-  RwAutoLockRead lock(mRwLock);
-  return mParams.get(key);
-}
-
-double
-nsGonkCameraControl::GetParameterDouble(uint32_t aKey)
-{
-  double val;
-  int index = 0;
-  double focusDistance[3];
-  const char* s;
-
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    // return 1x when zooming is not supported
-    return aKey == CAMERA_PARAM_ZOOM ? 1.0 : 0.0;
-  }
-
-  RwAutoLockRead lock(mRwLock);
-  switch (aKey) {
-    case CAMERA_PARAM_ZOOM:
-      val = mParams.getInt(key);
-      return val / 100;
+  switch (aConfig.mMode) {
+    case kPictureMode:
+      rv = SetPictureConfiguration(aConfig);
+      break;
 
-    /**
-     * The gonk camera parameters API only exposes one focus distance property
-     * that contains "Near,Optimum,Far" distances, in metres, where 'Far' may
-     * be 'Infinity'.
-     */
-    case CAMERA_PARAM_FOCUSDISTANCEFAR:
-      ++index;
-      // intentional fallthrough
-
-    case CAMERA_PARAM_FOCUSDISTANCEOPTIMUM:
-      ++index;
-      // intentional fallthrough
-
-    case CAMERA_PARAM_FOCUSDISTANCENEAR:
-      s = mParams.get(key);
-      if (sscanf(s, "%lf,%lf,%lf", &focusDistance[0], &focusDistance[1], &focusDistance[2]) == 3) {
-        return focusDistance[index];
-      }
-      return 0.0;
-
-    case CAMERA_PARAM_EXPOSURECOMPENSATION:
-      index = mParams.getInt(key);
-      if (!index) {
-        // NaN indicates automatic exposure compensation
-        return NAN;
-      }
-      val = (index - 1) * mExposureCompensationStep + mExposureCompensationMin;
-      DOM_CAMERA_LOGI("index = %d --> compensation = %f\n", index, val);
-      return val;
+    case kVideoMode:
+      rv = SetVideoConfiguration(aConfig);
+      break;
 
     default:
-      return mParams.getFloat(key);
-  }
-}
-
-int32_t
-nsGonkCameraControl::GetParameterInt32(uint32_t aKey)
-{
-  if (aKey == CAMERA_PARAM_SENSORANGLE) {
-    if (!mCameraHw.get()) {
-      return 0;
-    }
-    return mCameraHw->GetSensorOrientation();
+      MOZ_ASSUME_UNREACHABLE("Unanticipated camera mode in SetConfigurationInternal()");
   }
 
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    return 0;
-  }
-
-  RwAutoLockRead lock(mRwLock);
-  return mParams.getInt(key);
-}
-
-void
-nsGonkCameraControl::GetParameter(uint32_t aKey,
-                                  nsTArray<idl::CameraRegion>& aRegions)
-{
-  aRegions.Clear();
-
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    return;
-  }
-
-  RwAutoLockRead lock(mRwLock);
-
-  const char* value = mParams.get(key);
-  DOM_CAMERA_LOGI("key='%s' --> value='%s'\n", key, value);
-  if (!value) {
-    return;
-  }
-
-  const char* p = value;
-  uint32_t count = 1;
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  // count the number of regions in the string
-  while ((p = strstr(p, "),("))) {
-    ++count;
-    p += 3;
-  }
-
-  aRegions.SetCapacity(count);
-  idl::CameraRegion* r;
-
-  // parse all of the region sets
-  uint32_t i;
-  for (i = 0, p = value; p && i < count; ++i, p = strchr(p + 1, '(')) {
-    r = aRegions.AppendElement();
-    if (sscanf(p, "(%d,%d,%d,%d,%u)", &r->top, &r->left, &r->bottom, &r->right, &r->weight) != 5) {
-      DOM_CAMERA_LOGE("%s:%d : region tuple has bad format: '%s'\n", __func__, __LINE__, p);
-      aRegions.Clear();
-      return;
-    }
-  }
-
-  return;
-}
-
-void
-nsGonkCameraControl::GetParameter(uint32_t aKey,
-                                  nsTArray<idl::CameraSize>& aSizes)
-{
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    return;
+  mCurrentConfiguration.mMode = aConfig.mMode;
+  mCurrentConfiguration.mRecorderProfile = aConfig.mRecorderProfile;
+  if (aConfig.mMode == kVideoMode) {
+    mCurrentConfiguration.mPreviewSize = mLastRecorderSize;
   }
 
-  RwAutoLockRead lock(mRwLock);
-
-  const char* value = mParams.get(key);
-  DOM_CAMERA_LOGI("key='%s' --> value='%s'\n", key, value);
-  if (!value) {
-    return;
-  }
-
-  const char* p = value;
-  idl::CameraSize* s;
-
-  // The 'value' string is in the format "w1xh1,w2xh2,w3xh3,..."
-  while (p) {
-    s = aSizes.AppendElement();
-    if (sscanf(p, "%dx%d", &s->width, &s->height) != 2) {
-      DOM_CAMERA_LOGE("%s:%d : size tuple has bad format: '%s'\n", __func__, __LINE__, p);
-      aSizes.Clear();
-      return;
-    }
-    // Look for the next record...
-    p = strchr(p, ',');
-    if (p) {
-      // ...skip the comma too
-      ++p;
-    }
-  }
-
-  return;
-}
-
-void
-nsGonkCameraControl::GetParameter(uint32_t aKey, idl::CameraSize& aSize)
-{
-  if (aKey == CAMERA_PARAM_THUMBNAILSIZE) {
-    // This is a special case--for some reason the thumbnail size
-    // is accessed as two separate values instead of a tuple.
-    RwAutoLockRead lock(mRwLock);
-
-    aSize.width = mParams.getInt(CameraParameters::KEY_JPEG_THUMBNAIL_WIDTH);
-    aSize.height = mParams.getInt(CameraParameters::KEY_JPEG_THUMBNAIL_HEIGHT);
-    DOM_CAMERA_LOGI("thumbnail size --> value='%ux%u'\n", aSize.width, aSize.height);
-    return;
-  }
-
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    return;
-  }
-
-  RwAutoLockRead lock(mRwLock);
-
-  const char* value = mParams.get(key);
-  DOM_CAMERA_LOGI("key='%s' --> value='%s'\n", key, value);
-  if (!value) {
-    return;
-  }
-
-  if (sscanf(value, "%ux%u", &aSize.width, &aSize.height) != 2) {
-    DOM_CAMERA_LOGE("%s:%d : size tuple has bad format: '%s'\n", __func__, __LINE__, value);
-    aSize.width = 0;
-    aSize.height = 0;
-  }
+  OnConfigurationChange();
+  return NS_OK;
 }
 
 nsresult
+nsGonkCameraControl::SetConfigurationImpl(const Configuration& aConfig)
+{
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
+
+  // Stop any currently running preview
+  StopPreviewImpl();
+
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  nsresult rv = SetConfigurationInternal(aConfig);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Restart the preview
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  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);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mParams.Get(CAMERA_PARAM_PREVIEWFRAMERATE, mPreviewFps);
+
+  DOM_CAMERA_LOGI("picture mode preview: wanted %ux%u, got %ux%u (%u fps)\n",
+    aConfig.mPreviewSize.width, aConfig.mPreviewSize.height,
+    mCurrentConfiguration.mPreviewSize.width, mCurrentConfiguration.mPreviewSize.height,
+    mPreviewFps);
+
+  return NS_OK;
+}
+
+nsresult
+nsGonkCameraControl::SetVideoConfiguration(const Configuration& aConfig)
+{
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+
+  nsresult rv = SetupVideoMode(aConfig.mRecorderProfile);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  DOM_CAMERA_LOGI("video mode preview: profile '%s', got %ux%u (%u fps)\n",
+    NS_ConvertUTF16toUTF8(aConfig.mRecorderProfile).get(),
+    mLastRecorderSize.width, mLastRecorderSize.height,
+    mPreviewFps);
+
+  return rv;
+}
+
+// Parameter management.
+nsresult
 nsGonkCameraControl::PushParameters()
 {
-  if (mDeferConfigUpdate) {
-    DOM_CAMERA_LOGT("%s:%d - defering config update\n", __func__, __LINE__);
+  uint32_t dcu = mDeferConfigUpdate;
+  if (dcu > 0) {
+    DOM_CAMERA_LOGI("Defering config update (nest level %u)\n", dcu);
     return NS_OK;
   }
 
   /**
    * If we're already on the camera thread, call PushParametersImpl()
    * directly, so that it executes synchronously.  Some callers
    * require this so that changes take effect immediately before
    * we can proceed.
    */
-  if (NS_IsMainThread()) {
-    DOM_CAMERA_LOGT("%s:%d - dispatching to camera thread\n", __func__, __LINE__);
-    nsCOMPtr<nsIRunnable> pushParametersTask = NS_NewRunnableMethod(this, &nsGonkCameraControl::PushParametersImpl);
+  if (NS_GetCurrentThread() != mCameraThread) {
+    DOM_CAMERA_LOGT("%s:%d - dispatching to Camera Thread\n", __func__, __LINE__);
+    nsCOMPtr<nsIRunnable> pushParametersTask =
+      NS_NewRunnableMethod(this, &nsGonkCameraControl::PushParametersImpl);
     return mCameraThread->Dispatch(pushParametersTask, NS_DISPATCH_NORMAL);
   }
 
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
   return PushParametersImpl();
 }
 
 void
-nsGonkCameraControl::SetParameter(const char* aKey, const char* aValue)
+nsGonkCameraControl::BeginBatchParameterSet()
 {
-  {
-    RwAutoLockWrite lock(mRwLock);
-    mParams.set(aKey, aValue);
-  }
-  PushParameters();
-}
-
-void
-nsGonkCameraControl::SetParameter(uint32_t aKey, const char* aValue)
-{
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    return;
-  }
-
-  {
-    RwAutoLockWrite lock(mRwLock);
-    mParams.set(key, aValue);
+  uint32_t dcu = ++mDeferConfigUpdate;
+  if (dcu == 0) {
+    NS_WARNING("Overflow weirdness incrementing mDeferConfigUpdate!");
+    MOZ_CRASH();
   }
-  PushParameters();
-}
-
-void
-nsGonkCameraControl::SetParameter(uint32_t aKey, double aValue)
-{
-  uint32_t index;
-
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    return;
-  }
-
-  {
-    RwAutoLockWrite lock(mRwLock);
-    if (aKey == CAMERA_PARAM_EXPOSURECOMPENSATION) {
-      /**
-       * Convert from real value to a Gonk index, round
-       * to the nearest step; index is 1-based.
-       */
-      index = (aValue - mExposureCompensationMin + mExposureCompensationStep / 2) / mExposureCompensationStep + 1;
-      DOM_CAMERA_LOGI("compensation = %f --> index = %d\n", aValue, index);
-      mParams.set(key, index);
-    } else {
-      mParams.setFloat(key, aValue);
-    }
-  }
-  PushParameters();
+  DOM_CAMERA_LOGI("Begin deferring camera configuration updates (nest level %u)\n", dcu);
 }
 
 void
-nsGonkCameraControl::SetParameter(uint32_t aKey,
-                                  const nsTArray<idl::CameraRegion>& aRegions)
+nsGonkCameraControl::EndBatchParameterSet()
 {
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    return;
+  uint32_t dcu = mDeferConfigUpdate--;
+  if (dcu == 0) {
+    NS_WARNING("Underflow badness decrementing mDeferConfigUpdate!");
+    MOZ_CRASH();
   }
+  DOM_CAMERA_LOGI("End deferring camera configuration updates (nest level %u)\n", dcu);
 
-  uint32_t length = aRegions.Length();
+  if (dcu == 1) {
+    PushParameters();
+  }
+}
 
-  if (!length) {
-    // This tells the camera driver to revert to automatic regioning.
-    {
-      RwAutoLockWrite lock(mRwLock);
-      mParams.set(key, "(0,0,0,0,0)");
+template<class T> nsresult
+nsGonkCameraControl::SetAndPush(uint32_t aKey, const T& aValue)
+{
+  nsresult rv = mParams.Set(aKey, aValue);
+  if (NS_FAILED(rv)) {
+    DOM_CAMERA_LOGE("Camera parameter aKey=%d failed to set (0x%x)\n", aKey, rv);
+    return rv;
+  }
+  return PushParameters();
+}
+
+// Array-of-Size parameter accessor.
+nsresult
+nsGonkCameraControl::Get(uint32_t aKey, nsTArray<Size>& aSizes)
+{
+  if (aKey == CAMERA_PARAM_SUPPORTED_VIDEOSIZES) {
+    nsresult rv = mParams.Get(aKey, aSizes);
+    if (aSizes.Length() != 0) {
+      return rv;
     }
-    PushParameters();
-    return;
-  }
-
-  nsCString s;
-
-  for (uint32_t i = 0; i < length; ++i) {
-    const idl::CameraRegion* r = &aRegions[i];
-    s.AppendPrintf("(%d,%d,%d,%d,%d),", r->top, r->left, r->bottom, r->right, r->weight);
+    DOM_CAMERA_LOGI("Camera doesn't support video independent of the preview\n");
+    aKey = CAMERA_PARAM_SUPPORTED_PREVIEWSIZES;
   }
 
-  // remove the trailing comma
-  s.Trim(",", false, true, true);
+  return mParams.Get(aKey, aSizes);
+}
 
-  DOM_CAMERA_LOGI("camera region string '%s'\n", s.get());
+// Array-of-doubles parameter accessor.
+nsresult
+nsGonkCameraControl::Get(uint32_t aKey, nsTArray<double>& aValues)
+{
+  return mParams.Get(aKey, aValues);
+}
+
+// Array-of-nsString parameter accessor.
+nsresult
+nsGonkCameraControl::Get(uint32_t aKey, nsTArray<nsString>& aValues)
+{
+  return mParams.Get(aKey, aValues);
+}
 
-  {
-    RwAutoLockWrite lock(mRwLock);
-    mParams.set(key, s.get());
+// nsString-valued parameter accessors
+nsresult
+nsGonkCameraControl::Set(uint32_t aKey, const nsAString& aValue)
+{
+  nsresult rv = mParams.Set(aKey, aValue);
+  if (NS_FAILED(rv)) {
+    return rv;
   }
-  PushParameters();
+
+  if (aKey == CAMERA_PARAM_PICTURE_FILEFORMAT) {
+    // Picture format -- need to keep it for the TakePicture() callback.
+    mFileFormat = aValue;
+  }
+
+  return PushParameters();
+}
+
+nsresult
+nsGonkCameraControl::Get(uint32_t aKey, nsAString& aRet)
+{
+  return mParams.Get(aKey, aRet);
 }
 
-void
-nsGonkCameraControl::SetParameter(uint32_t aKey, int aValue)
+// Double-valued parameter accessors
+nsresult
+nsGonkCameraControl::Set(uint32_t aKey, double aValue)
 {
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    return;
-  }
-  {
-    RwAutoLockWrite lock(mRwLock);
-    mParams.set(key, aValue);
-  }
-  PushParameters();
+  return SetAndPush(aKey, aValue);
+}
+
+nsresult
+nsGonkCameraControl::Get(uint32_t aKey, double& aRet)
+{
+  return mParams.Get(aKey, aRet);
+}
+
+// Signed-64-bit parameter accessors.
+nsresult
+nsGonkCameraControl::Set(uint32_t aKey, int64_t aValue)
+{
+  return SetAndPush(aKey, aValue);
 }
 
-void
-nsGonkCameraControl::SetParameter(uint32_t aKey, const idl::CameraSize& aSize)
+nsresult
+nsGonkCameraControl::Get(uint32_t aKey, int64_t& aRet)
+{
+  return mParams.Get(aKey, aRet);
+}
+
+// Weighted-region parameter accessors.
+nsresult
+nsGonkCameraControl::Set(uint32_t aKey, const nsTArray<Region>& aRegions)
+{
+  return SetAndPush(aKey, aRegions);
+}
+
+nsresult
+nsGonkCameraControl::Get(uint32_t aKey, nsTArray<Region>& aRegions)
+{
+  return mParams.Get(aKey, aRegions);
+}
+
+// Singleton-size parameter accessors.
+nsresult
+nsGonkCameraControl::Set(uint32_t aKey, const Size& aSize)
 {
   switch (aKey) {
     case CAMERA_PARAM_PICTURESIZE:
       DOM_CAMERA_LOGI("setting picture size to %ux%u\n", aSize.width, aSize.height);
-      SetPictureSize(aSize.width, aSize.height);
-      break;
+      return SetPictureSize(aSize);
 
     case CAMERA_PARAM_THUMBNAILSIZE:
       DOM_CAMERA_LOGI("setting thumbnail size to %ux%u\n", aSize.width, aSize.height);
-      SetThumbnailSize(aSize.width, aSize.height);
-      break;
+      return SetThumbnailSize(aSize);
 
     default:
-      {
-        const char* key = getKeyText(aKey);
-        if (!key) {
-          return;
-        }
+      return SetAndPush(aKey, aSize);
+  }
+}
 
-        nsCString s;
-        s.AppendPrintf("%ux%u", aSize.width, aSize.height);
-        DOM_CAMERA_LOGI("setting '%s' to %s\n", key, s.get());
+nsresult
+nsGonkCameraControl::Get(uint32_t aKey, Size& aSize)
+{
+  return mParams.Get(aKey, aSize);
+}
 
-        RwAutoLockWrite lock(mRwLock);
-        mParams.set(key, s.get());
-      }
-      break;
+// Signed int parameter accessors.
+nsresult
+nsGonkCameraControl::Set(uint32_t aKey, int aValue)
+{
+  if (aKey == CAMERA_PARAM_PICTURE_ROTATION) {
+    aValue = RationalizeRotation(aValue + mCameraHw->GetSensorOrientation());
   }
-  PushParameters();
+  return SetAndPush(aKey, aValue);
 }
 
 nsresult
-nsGonkCameraControl::GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream)
+nsGonkCameraControl::Get(uint32_t aKey, int& aRet)
 {
-  // stop any currently running preview
-  StopPreviewInternal(true /* forced */);
+  if (aKey == CAMERA_PARAM_SENSORANGLE) {
+    if (!mCameraHw.get()) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+    aRet = mCameraHw->GetSensorOrientation();
+    return NS_OK;
+  }
 
-  // remove any existing recorder profile
-  mRecorderProfile = nullptr;
+  return mParams.Get(aKey, aRet);
+}
 
-  SetPreviewSize(aGetPreviewStream->mSize.width, aGetPreviewStream->mSize.height);
-  DOM_CAMERA_LOGI("picture preview: wanted %d x %d, got %d x %d (%d fps, format %d)\n", aGetPreviewStream->mSize.width, aGetPreviewStream->mSize.height, mWidth, mHeight, mFps, mFormat);
-
-  nsMainThreadPtrHandle<nsICameraPreviewStreamCallback> onSuccess = aGetPreviewStream->mOnSuccessCb;
-  nsCOMPtr<GetPreviewStreamResult> getPreviewStreamResult = new GetPreviewStreamResult(this, mWidth, mHeight, mFps, onSuccess, mWindowId);
-  return NS_DispatchToMainThread(getPreviewStreamResult);
+// GPS location parameter accessors.
+nsresult
+nsGonkCameraControl::SetLocation(const Position& aLocation)
+{
+  return SetAndPush(CAMERA_PARAM_PICTURE_LOCATION, aLocation);
 }
 
 nsresult
-nsGonkCameraControl::StartPreviewImpl(StartPreviewTask* aStartPreview)
+nsGonkCameraControl::StartPreviewImpl()
 {
-  /**
-   * If 'aStartPreview->mDOMPreview' is null, we are just restarting
-   * the preview after taking a picture.  No need to monkey with the
-   * currently set DOM-facing preview object.
-   */
-  if (aStartPreview->mDOMPreview) {
-    StopPreviewInternal(true /* forced */);
-    mDOMPreview = aStartPreview->mDOMPreview;
-  } else if (!mDOMPreview) {
-    return NS_ERROR_INVALID_ARG;
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
+  RETURN_IF_NO_CAMERA_HW();
+
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+  if (mPreviewState == CameraControlListener::kPreviewStarted) {
+    DOM_CAMERA_LOGW("Camera preview already started, nothing to do\n");
+    return NS_OK;
   }
 
-  DOM_CAMERA_LOGI("%s: starting preview (mDOMPreview=%p)\n", __func__, mDOMPreview);
+  DOM_CAMERA_LOGI("Starting preview (this=%p)\n", this);
 
-  RETURN_IF_NO_CAMERA_HW();
   if (mCameraHw->StartPreview() != OK) {
-    DOM_CAMERA_LOGE("%s: failed to start preview\n", __func__);
+    DOM_CAMERA_LOGE("Failed to start camera preview\n");
     return NS_ERROR_FAILURE;
   }
 
-  if (aStartPreview->mDOMPreview) {
-    mDOMPreview->Started();
-  }
-
-  OnPreviewStateChange(PREVIEW_STARTED);
+  OnPreviewStateChange(CameraControlListener::kPreviewStarted);
   return NS_OK;
 }
 
 nsresult
-nsGonkCameraControl::StopPreviewInternal(bool aForced)
+nsGonkCameraControl::StopPreviewImpl()
 {
-  DOM_CAMERA_LOGI("%s: stopping preview (mDOMPreview=%p)\n", __func__, mDOMPreview);
+  RETURN_IF_NO_CAMERA_HW();
 
-  // StopPreview() is a synchronous call--it doesn't return
-  // until the camera preview thread exits.
-  if (mDOMPreview) {
-    if (mCameraHw.get()) {
-      mCameraHw->StopPreview();
-    }
-    mDOMPreview->Stopped(aForced);
-    mDOMPreview = nullptr;
-  }
+  DOM_CAMERA_LOGI("Stopping preview (this=%p)\n", this);
 
-  OnPreviewStateChange(PREVIEW_STOPPED);
+  mCameraHw->StopPreview();
+
+  OnPreviewStateChange(CameraControlListener::kPreviewStopped);
   return NS_OK;
 }
 
 nsresult
-nsGonkCameraControl::StopPreviewImpl(StopPreviewTask* aStopPreview)
+nsGonkCameraControl::AutoFocusImpl(bool aCancelExistingCall)
 {
-  return StopPreviewInternal();
-}
-
-nsresult
-nsGonkCameraControl::AutoFocusImpl(AutoFocusTask* aAutoFocus)
-{
-  if (aAutoFocus->mCancel) {
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
+  RETURN_IF_NO_CAMERA_HW();
+  if (aCancelExistingCall) {
     if (mCameraHw.get()) {
       mCameraHw->CancelAutoFocus();
     }
   }
 
-  mAutoFocusOnSuccessCb = aAutoFocus->mOnSuccessCb;
-  mAutoFocusOnErrorCb = aAutoFocus->mOnErrorCb;
-
-  RETURN_IF_NO_CAMERA_HW();
   if (mCameraHw->AutoFocus() != OK) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
-void
-nsGonkCameraControl::SetThumbnailSize(uint32_t aWidth, uint32_t aHeight)
+nsresult
+nsGonkCameraControl::SetThumbnailSizeImpl(const Size& aSize)
 {
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
+
   /**
    * We keep a copy of the specified size so that if the picture size
    * changes, we can choose a new thumbnail size close to what was asked for
    * last time.
    */
-  mLastThumbnailWidth = aWidth;
-  mLastThumbnailHeight = aHeight;
+  mLastThumbnailSize = aSize;
 
   /**
    * If either of width or height is zero, set the other to zero as well.
    * This should disable inclusion of a thumbnail in the final picture.
    */
-  if (!aWidth || !aHeight) {
-    DOM_CAMERA_LOGW("Requested thumbnail size %ux%u, disabling thumbnail\n", aWidth, aHeight);
-    RwAutoLockWrite write(mRwLock);
-    mParams.set(CameraParameters::KEY_JPEG_THUMBNAIL_WIDTH, 0);
-    mParams.set(CameraParameters::KEY_JPEG_THUMBNAIL_HEIGHT, 0);
-    return;
+  if (!aSize.width || !aSize.height) {
+    DOM_CAMERA_LOGW("Requested thumbnail size %ux%u, disabling thumbnail\n",
+      aSize.width, aSize.height);
+    Size size = { 0, 0 };
+    return SetAndPush(CAMERA_PARAM_THUMBNAILSIZE, size);
   }
 
   /**
    * Choose the supported thumbnail size that is closest to the specified size.
    * Some drivers will fail to take a picture if the thumbnail does not have
    * the same aspect ratio as the set picture size, so we need to enforce that
    * too.
    */
   int smallestDelta = INT_MAX;
   uint32_t smallestDeltaIndex = UINT32_MAX;
-  int targetArea = aWidth * aHeight;
+  int targetArea = aSize.width * aSize.height;
 
-  nsAutoTArray<idl::CameraSize, 8> supportedSizes;
-  GetParameter(CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES, supportedSizes);
+  nsAutoTArray<Size, 8> supportedSizes;
+  Get(CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES, supportedSizes);
 
   for (uint32_t i = 0; i < supportedSizes.Length(); ++i) {
     int area = supportedSizes[i].width * supportedSizes[i].height;
     int delta = abs(area - targetArea);
 
     if (area != 0
       && delta < smallestDelta
-      && supportedSizes[i].width * mLastPictureHeight / supportedSizes[i].height == mLastPictureWidth
+      && supportedSizes[i].width * mLastPictureSize.height /
+         supportedSizes[i].height == mLastPictureSize.width
     ) {
       smallestDelta = delta;
       smallestDeltaIndex = i;
     }
   }
 
   if (smallestDeltaIndex == UINT32_MAX) {
-    DOM_CAMERA_LOGW("Unable to find a thumbnail size close to %ux%u\n", aWidth, aHeight);
-    return;
+    DOM_CAMERA_LOGW("Unable to find a thumbnail size close to %ux%u\n",
+      aSize.width, aSize.height);
+    return NS_ERROR_INVALID_ARG;
   }
 
-  uint32_t w = supportedSizes[smallestDeltaIndex].width;
-  uint32_t h = supportedSizes[smallestDeltaIndex].height;
-  DOM_CAMERA_LOGI("Requested thumbnail size %ux%u --> using supported size %ux%u\n", aWidth, aHeight, w, h);
-  if (w > INT32_MAX || h > INT32_MAX) {
+  Size size = supportedSizes[smallestDeltaIndex];
+  DOM_CAMERA_LOGI("camera-param set picture-size = %ux%u (requested %ux%u)\n",
+    size.width, size.height, aSize.width, aSize.height);
+  if (size.width > INT32_MAX || size.height > INT32_MAX) {
     DOM_CAMERA_LOGE("Supported thumbnail size is too big, no change\n");
-    return;
+    return NS_ERROR_FAILURE;
   }
 
-  RwAutoLockWrite write(mRwLock);
-  mParams.set(CameraParameters::KEY_JPEG_THUMBNAIL_WIDTH, static_cast<int>(w));
-  mParams.set(CameraParameters::KEY_JPEG_THUMBNAIL_HEIGHT, static_cast<int>(h));
+  return SetAndPush(CAMERA_PARAM_THUMBNAILSIZE, size);
 }
 
-void
+nsresult
+nsGonkCameraControl::SetThumbnailSize(const Size& aSize)
+{
+  class SetThumbnailSize : public nsRunnable
+  {
+  public:
+    SetThumbnailSize(nsGonkCameraControl* aCameraControl, const Size& aSize)
+      : mCameraControl(aCameraControl)
+      , mSize(aSize)
+    {
+      MOZ_COUNT_CTOR(SetThumbnailSize);
+    }
+    ~SetThumbnailSize() { MOZ_COUNT_DTOR(SetThumbnailSize); }
+
+    NS_IMETHODIMP
+    Run() MOZ_OVERRIDE
+    {
+      nsresult rv = mCameraControl->SetThumbnailSizeImpl(mSize);
+      if (NS_FAILED(rv)) {
+        mCameraControl->OnError(CameraControlListener::kInUnspecified,
+                                CameraControlListener::kErrorSetThumbnailSizeFailed);
+      }
+      return NS_OK;
+    }
+
+  protected:
+    nsRefPtr<nsGonkCameraControl> mCameraControl;
+    Size mSize;
+  };
+
+  if (NS_GetCurrentThread() == mCameraThread) {
+    return SetThumbnailSizeImpl(aSize);
+  }
+
+  return mCameraThread->Dispatch(new SetThumbnailSize(this, aSize), NS_DISPATCH_NORMAL);
+}
+
+nsresult
 nsGonkCameraControl::UpdateThumbnailSize()
 {
-  SetThumbnailSize(mLastThumbnailWidth, mLastThumbnailHeight);
+  return SetThumbnailSize(mLastThumbnailSize);
 }
 
-void
-nsGonkCameraControl::SetPictureSize(uint32_t aWidth, uint32_t aHeight)
+nsresult
+nsGonkCameraControl::SetPictureSizeImpl(const Size& aSize)
 {
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
+
   /**
    * Some drivers are less friendly about getting one of these set to zero,
    * so if either is not specified, ignore both and go with current or
    * default settings.
    */
-  if (!aWidth || !aHeight) {
-    DOM_CAMERA_LOGW("Ignoring requested picture size of %ux%u\n", aWidth, aHeight);
-    return;
+  if (!aSize.width || !aSize.height) {
+    DOM_CAMERA_LOGW("Ignoring requested picture size of %ux%u\n", aSize.width, aSize.height);
+    return NS_ERROR_INVALID_ARG;
   }
 
-  if (aWidth == mLastPictureWidth && aHeight == mLastPictureHeight) {
-    DOM_CAMERA_LOGI("Requested picture size %ux%u unchanged\n", aWidth, aHeight);
-    return;
+  if (aSize.width == mLastPictureSize.width && aSize.height == mLastPictureSize.height) {
+    DOM_CAMERA_LOGI("Requested picture size %ux%u unchanged\n", aSize.width, aSize.height);
+    return NS_OK;
   }
 
   /**
    * Choose the supported picture size that is closest in area to the
    * specified size. Some drivers will fail to take a picture if the
    * thumbnail size is not the same aspect ratio, so we update that
    * as well to a size closest to the last user-requested one.
    */
   int smallestDelta = INT_MAX;
   uint32_t smallestDeltaIndex = UINT32_MAX;
-  int targetArea = aWidth * aHeight;
-  
-  nsAutoTArray<idl::CameraSize, 8> supportedSizes;
-  GetParameter(CAMERA_PARAM_SUPPORTED_PICTURESIZES, supportedSizes);
+  int targetArea = aSize.width * aSize.height;
+
+  nsAutoTArray<Size, 8> supportedSizes;
+  Get(CAMERA_PARAM_SUPPORTED_PICTURESIZES, supportedSizes);
 
   for (uint32_t i = 0; i < supportedSizes.Length(); ++i) {
     int area = supportedSizes[i].width * supportedSizes[i].height;
     int delta = abs(area - targetArea);
 
     if (area != 0 && delta < smallestDelta) {
       smallestDelta = delta;
       smallestDeltaIndex = i;
     }
   }
 
   if (smallestDeltaIndex == UINT32_MAX) {
-    DOM_CAMERA_LOGW("Unable to find a picture size close to %ux%u\n", aWidth, aHeight);
-    return;
-  }
-
-  uint32_t w = supportedSizes[smallestDeltaIndex].width;
-  uint32_t h = supportedSizes[smallestDeltaIndex].height;
-  DOM_CAMERA_LOGI("Requested picture size %ux%u --> using supported size %ux%u\n", aWidth, aHeight, w, h);
-  if (w > INT32_MAX || h > INT32_MAX) {
-    DOM_CAMERA_LOGE("Supported picture size is too big, no change\n");
-    return;
+    DOM_CAMERA_LOGW("Unable to find a picture size close to %ux%u\n",
+      aSize.width, aSize.height);
+    return NS_ERROR_INVALID_ARG;
   }
 
-  mLastPictureWidth = w;
-  mLastPictureHeight = h;
-
-  {
-    // We must release the write-lock before updating the thumbnail size
-    RwAutoLockWrite write(mRwLock);
-    mParams.setPictureSize(static_cast<int>(w), static_cast<int>(h));
+  Size size = supportedSizes[smallestDeltaIndex];
+  DOM_CAMERA_LOGI("camera-param set picture-size = %ux%u (requested %ux%u)\n",
+    size.width, size.height, aSize.width, aSize.height);
+  if (size.width > INT32_MAX || size.height > INT32_MAX) {
+    DOM_CAMERA_LOGE("Supported picture size is too big, no change\n");
+    return NS_ERROR_FAILURE;
   }
 
-  // Finally, update the thumbnail size
-  UpdateThumbnailSize();
+  nsresult rv = mParams.Set(CAMERA_PARAM_PICTURESIZE, size);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  mLastPictureSize = size;
+
+  // Finally, update the thumbnail size in case the picture
+  // aspect ratio changed.
+  return UpdateThumbnailSize();
 }
 
 int32_t
 nsGonkCameraControl::RationalizeRotation(int32_t aRotation)
 {
   int32_t r = aRotation;
 
   // The result of this operation is an angle from 0..270 degrees,
@@ -988,410 +767,418 @@ nsGonkCameraControl::RationalizeRotation
   if (r < 0) {
     r += 360;
   }
 
   return r;
 }
 
 nsresult
-nsGonkCameraControl::TakePictureImpl(TakePictureTask* aTakePicture)
+nsGonkCameraControl::SetPictureSize(const Size& aSize)
 {
-  if (aTakePicture->mCancel) {
-    if (mCameraHw.get()) {
-      mCameraHw->CancelTakePicture();
+  class SetPictureSize : public nsRunnable
+  {
+  public:
+    SetPictureSize(nsGonkCameraControl* aCameraControl, const Size& aSize)
+      : mCameraControl(aCameraControl)
+      , mSize(aSize)
+    {
+      MOZ_COUNT_CTOR(SetPictureSize);
     }
+    ~SetPictureSize() { MOZ_COUNT_DTOR(SetPictureSize); }
+
+    NS_IMETHODIMP
+    Run() MOZ_OVERRIDE
+    {
+      nsresult rv = mCameraControl->SetPictureSizeImpl(mSize);
+      if (NS_FAILED(rv)) {
+        mCameraControl->OnError(CameraControlListener::kInUnspecified,
+                                CameraControlListener::kErrorSetPictureSizeFailed);
+      }
+      return NS_OK;
+    }
+
+  protected:
+    nsRefPtr<nsGonkCameraControl> mCameraControl;
+    Size mSize;
+  };
+
+  if (NS_GetCurrentThread() == mCameraThread) {
+    return SetPictureSizeImpl(aSize);
   }
 
-  mTakePictureOnSuccessCb = aTakePicture->mOnSuccessCb;
-  mTakePictureOnErrorCb = aTakePicture->mOnErrorCb;
-
-  RETURN_IF_NO_CAMERA_HW();
-
-  // batch-update camera configuration
-  mDeferConfigUpdate = true;
-
-  SetPictureSize(aTakePicture->mSize.width, aTakePicture->mSize.height);
-
-  // Picture format -- need to keep it for the callback.
-  mFileFormat = aTakePicture->mFileFormat;
-  SetParameter(CameraParameters::KEY_PICTURE_FORMAT, NS_ConvertUTF16toUTF8(mFileFormat).get());
-
-  // Round 'rotation' up to a positive value from 0..270 degrees, in steps of 90.
-  int32_t r = static_cast<uint32_t>(aTakePicture->mRotation);
-  r += mCameraHw->GetSensorOrientation(GonkCameraHardware::OFFSET_SENSOR_ORIENTATION);
-  r = RationalizeRotation(r);
-  DOM_CAMERA_LOGI("setting picture rotation to %d degrees (mapped from %d)\n", r, aTakePicture->mRotation);
-  SetParameter(CameraParameters::KEY_ROTATION, nsPrintfCString("%u", r).get());
+  return mCameraThread->Dispatch(new SetPictureSize(this, aSize), NS_DISPATCH_NORMAL);
+}
 
-  // Add any specified positional information -- don't care if these fail.
-  if (!isnan(aTakePicture->mPosition.latitude)) {
-    DOM_CAMERA_LOGI("setting picture latitude to %lf\n", aTakePicture->mPosition.latitude);
-    SetParameter(CameraParameters::KEY_GPS_LATITUDE, nsPrintfCString("%lf", aTakePicture->mPosition.latitude).get());
-  }
-  if (!isnan(aTakePicture->mPosition.longitude)) {
-    DOM_CAMERA_LOGI("setting picture longitude to %lf\n", aTakePicture->mPosition.longitude);
-    SetParameter(CameraParameters::KEY_GPS_LONGITUDE, nsPrintfCString("%lf", aTakePicture->mPosition.longitude).get());
-  }
-  if (!isnan(aTakePicture->mPosition.altitude)) {
-    DOM_CAMERA_LOGI("setting picture altitude to %lf\n", aTakePicture->mPosition.altitude);
-    SetParameter(CameraParameters::KEY_GPS_ALTITUDE, nsPrintfCString("%lf", aTakePicture->mPosition.altitude).get());
-  }
-  if (!isnan(aTakePicture->mPosition.timestamp)) {
-    DOM_CAMERA_LOGI("setting picture timestamp to %lf\n", aTakePicture->mPosition.timestamp);
-    SetParameter(CameraParameters::KEY_GPS_TIMESTAMP, nsPrintfCString("%lf", aTakePicture->mPosition.timestamp).get());
-  }
-
-  // Add the non-GPS timestamp.  The EXIF date/time field is formatted as
-  // "YYYY:MM:DD HH:MM:SS", without room for a time-zone; as such, the time
-  // is meant to be stored as a local time.  Since we are given seconds from
-  // Epoch GMT, we use localtime_r() to handle the conversion.
-  time_t time = aTakePicture->mDateTime;
-  if ((uint64_t)time != aTakePicture->mDateTime) {
-    DOM_CAMERA_LOGE("picture date/time '%llu' is too far in the future\n", aTakePicture->mDateTime);
-  } else {
-    struct tm t;
-    if (localtime_r(&time, &t)) {
-      char dateTime[20];
-      if (strftime(dateTime, sizeof(dateTime), "%Y:%m:%d %T", &t)) {
-        DOM_CAMERA_LOGI("setting picture date/time to %s\n", dateTime);
-        // Not every platform defines a CameraParameters::KEY_EXIF_DATETIME;
-        // for those who don't, we use the raw string key, and if the platform
-        // doesn't support it, it will be ignored.
-        //
-        // See bug 832494.
-        SetParameter("exif-datetime", dateTime);
-      } else {
-        DOM_CAMERA_LOGE("picture date/time couldn't be converted to string\n");
-      }
-    } else {
-      DOM_CAMERA_LOGE("picture date/time couldn't be converted to local time: (%d) %s\n", errno, strerror(errno));
-    }
-  }
-
-  mDeferConfigUpdate = false;
-  PushParameters();
+nsresult
+nsGonkCameraControl::TakePictureImpl()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
+  RETURN_IF_NO_CAMERA_HW();
 
   if (mCameraHw->TakePicture() != OK) {
     return NS_ERROR_FAILURE;
   }
-  
-  // In Gonk, taking a picture implicitly kills the preview stream,
+
+  // In Gonk, taking a picture implicitly stops the preview stream,
   // so we need to reflect that here.
-  OnPreviewStateChange(PREVIEW_STOPPED);
+  OnPreviewStateChange(CameraControlListener::kPreviewPaused);
   return NS_OK;
 }
 
 nsresult
 nsGonkCameraControl::PushParametersImpl()
 {
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
   DOM_CAMERA_LOGI("Pushing camera parameters\n");
   RETURN_IF_NO_CAMERA_HW();
 
-  RwAutoLockRead lock(mRwLock);
   if (mCameraHw->PushParameters(mParams) != OK) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 nsresult
 nsGonkCameraControl::PullParametersImpl()
 {
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
   DOM_CAMERA_LOGI("Pulling camera parameters\n");
   RETURN_IF_NO_CAMERA_HW();
 
-  RwAutoLockWrite lock(mRwLock);
-  mCameraHw->PullParameters(mParams);
-  return NS_OK;
+  return mCameraHw->PullParameters(mParams);
 }
 
 nsresult
-nsGonkCameraControl::StartRecordingImpl(StartRecordingTask* aStartRecording)
+nsGonkCameraControl::StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
+                                        const StartRecordingOptions* aOptions)
 {
   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.
    * See bug 795202.
    */
-  nsRefPtr<DeviceStorageFileDescriptor> dsfd = aStartRecording->mDSFileDescriptor;
-  NS_ENSURE_TRUE(dsfd, NS_ERROR_FAILURE);
+  NS_ENSURE_TRUE(aFileDescriptor, NS_ERROR_FAILURE);
   nsAutoString fullPath;
-  mVideoFile = dsfd->mDSFile;
+  mVideoFile = aFileDescriptor->mDSFile;
   mVideoFile->GetFullPath(fullPath);
   DOM_CAMERA_LOGI("Video filename is '%s'\n",
                   NS_LossyConvertUTF16toASCII(fullPath).get());
 
   if (!mVideoFile->IsSafePath()) {
     DOM_CAMERA_LOGE("Invalid video file name\n");
     return NS_ERROR_INVALID_ARG;
   }
 
   nsresult rv;
-  rv = SetupRecording(dsfd->mFileDescriptor.PlatformHandle(),
-                      aStartRecording->mOptions.rotation,
-                      aStartRecording->mOptions.maxFileSizeBytes,
-                      aStartRecording->mOptions.maxVideoLengthMs);
+  int fd = aFileDescriptor->mFileDescriptor.PlatformHandle();
+  if (aOptions) {
+    rv = SetupRecording(fd, aOptions->rotation, aOptions->maxFileSizeBytes,
+                        aOptions->maxVideoLengthMs);
+  } else {
+    rv = SetupRecording(fd, 0, 0, 0);
+  }
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (mRecorder->start() != OK) {
     DOM_CAMERA_LOGE("mRecorder->start() failed\n");
     // important: we MUST destroy the recorder if start() fails!
     mRecorder = nullptr;
     return NS_ERROR_FAILURE;
   }
 
+  OnRecorderStateChange(CameraControlListener::kRecorderStarted, -1, -1);
   return NS_OK;
 }
 
-class RecordingComplete : public nsRunnable
+nsresult
+nsGonkCameraControl::StopRecordingImpl()
 {
-public:
-  RecordingComplete(DeviceStorageFile* aFile)
-    : mFile(aFile)
-  { }
+  class RecordingComplete : public nsRunnable
+  {
+  public:
+    RecordingComplete(DeviceStorageFile* aFile)
+      : mFile(aFile)
+    { }
 
-  ~RecordingComplete() { }
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
+    ~RecordingComplete() { }
 
-    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-    obs->NotifyObservers(mFile, "file-watcher-notify", MOZ_UTF16("modified"));
-    return NS_OK;
-  }
+    NS_IMETHODIMP
+    Run()
+    {
+      MOZ_ASSERT(NS_IsMainThread());
 
-private:
-  nsRefPtr<DeviceStorageFile> mFile;
-};
+      nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+      obs->NotifyObservers(mFile, "file-watcher-notify", NS_LITERAL_STRING("modified").get());
+      return NS_OK;
+    }
 
-nsresult
-nsGonkCameraControl::StopRecordingImpl(StopRecordingTask* aStopRecording)
-{
+  private:
+    nsRefPtr<DeviceStorageFile> mFile;
+  };
+
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
+
   // nothing to do if we have no mRecorder
   NS_ENSURE_TRUE(mRecorder, NS_OK);
 
   mRecorder->stop();
   mRecorder = nullptr;
+  OnRecorderStateChange(CameraControlListener::kRecorderStopped, -1, -1);
 
   // notify DeviceStorage that the new video file is closed and ready
-  nsCOMPtr<nsIRunnable> recordingComplete = new RecordingComplete(mVideoFile);
-  return NS_DispatchToMainThread(recordingComplete, NS_DISPATCH_NORMAL);
+  return NS_DispatchToMainThread(new RecordingComplete(mVideoFile), NS_DISPATCH_NORMAL);
 }
 
 void
-nsGonkCameraControl::AutoFocusComplete(bool aSuccess)
+nsGonkCameraControl::OnAutoFocusComplete(bool aSuccess)
 {
-  /**
-   * Auto focusing can change some of the camera's parameters, so
-   * we need to pull a new set before sending the result to the
-   * main thread.
-   */
-  PullParametersImpl();
+  class AutoFocusComplete : public nsRunnable
+  {
+  public:
+    AutoFocusComplete(nsGonkCameraControl* aCameraControl, bool aSuccess)
+      : mCameraControl(aCameraControl)
+      , mSuccess(aSuccess)
+    { }
+
+    NS_IMETHODIMP
+    Run() MOZ_OVERRIDE
+    {
+      mCameraControl->OnAutoFocusComplete(mSuccess);
+      return NS_OK;
+    }
+
+  protected:
+    nsRefPtr<nsGonkCameraControl> mCameraControl;
+    bool mSuccess;
+  };
+
+  if (NS_GetCurrentThread() == mCameraThread) {
+    /**
+     * Auto focusing can change some of the camera's parameters, so
+     * we need to pull a new set before notifying any clients.
+     */
+    PullParametersImpl();
+    CameraControlImpl::OnAutoFocusComplete(aSuccess);
+    return;
+  }
 
   /**
-   * If we make it here, regardless of the value of 'aSuccess', we
-   * consider the autofocus _process_ to have succeeded.  It is up
-   * to the onSuccess callback to determine how to handle the case
-   * where the camera wasn't actually able to acquire focus.
+   * Because the callback needs to call PullParametersImpl(),
+   * we need to dispatch this callback through the Camera Thread.
    */
-  nsCOMPtr<nsIRunnable> autoFocusResult = new AutoFocusResult(aSuccess, mAutoFocusOnSuccessCb, mWindowId);
-  /**
-   * Remember to set these to null so that we don't hold any extra
-   * references to our document's window.
-   */
-  mAutoFocusOnSuccessCb = nullptr;
-  mAutoFocusOnErrorCb = nullptr;
-  nsresult rv = NS_DispatchToMainThread(autoFocusResult);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to dispatch autoFocus() onSuccess callback to main thread!");
-  }
+  mCameraThread->Dispatch(new AutoFocusComplete(this, aSuccess), NS_DISPATCH_NORMAL);
 }
 
 void
-nsGonkCameraControl::TakePictureComplete(uint8_t* aData, uint32_t aLength)
+nsGonkCameraControl::OnTakePictureComplete(uint8_t* aData, uint32_t aLength)
 {
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
   uint8_t* data = new uint8_t[aLength];
 
   memcpy(data, aData, aLength);
 
-  // TODO: see bug 779144.
-  nsCOMPtr<nsIRunnable> takePictureResult = new TakePictureResult(data, aLength, NS_LITERAL_STRING("image/jpeg"), mTakePictureOnSuccessCb, mWindowId);
-  /**
-   * Remember to set these to null so that we don't hold any extra
-   * references to our document's window.
-   */
-  mTakePictureOnSuccessCb = nullptr;
-  mTakePictureOnErrorCb = nullptr;
-  nsresult rv = NS_DispatchToMainThread(takePictureResult);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to dispatch takePicture() onSuccess callback to main thread!");
+  nsString s(NS_LITERAL_STRING("image/"));
+  s.Append(mFileFormat);
+  DOM_CAMERA_LOGI("Got picture, type '%s', %u bytes\n", NS_ConvertUTF16toUTF8(s).get(), aLength);
+  OnTakePictureComplete(data, aLength, s);
+
+  if (mResumePreviewAfterTakingPicture) {
+    nsresult rv = StartPreview();
+    if (NS_FAILED(rv)) {
+      DOM_CAMERA_LOGE("Failed to restart camera preview (%x)\n", rv);
+      OnPreviewStateChange(CameraControlListener::kPreviewStopped);
+    }
   }
-}
 
-void
-nsGonkCameraControl::TakePictureError()
-{
-  nsCOMPtr<nsIRunnable> takePictureError = new CameraErrorResult(mTakePictureOnErrorCb, NS_LITERAL_STRING("FAILURE"), mWindowId);
-  mTakePictureOnSuccessCb = nullptr;
-  mTakePictureOnErrorCb = nullptr;
-  nsresult rv = NS_DispatchToMainThread(takePictureError);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to dispatch takePicture() onError callback to main thread!");
-  }
+  DOM_CAMERA_LOGI("nsGonkCameraControl::OnTakePictureComplete() done\n");
 }
 
 void
-nsGonkCameraControl::SetPreviewSize(uint32_t aWidth, uint32_t aHeight)
+nsGonkCameraControl::OnTakePictureError()
 {
-  android::Vector<Size> previewSizes;
-  uint32_t bestWidth = aWidth;
-  uint32_t bestHeight = aHeight;
-  uint32_t minSizeDelta = UINT32_MAX;
-  uint32_t delta;
-  Size size;
+  CameraControlImpl::OnError(CameraControlListener::kInTakePicture,
+                             CameraControlListener::kErrorApiFailed);
+}
 
-  {
-    RwAutoLockRead lock(mRwLock);
-    mParams.getSupportedPreviewSizes(previewSizes);
+nsresult
+nsGonkCameraControl::SetPreviewSize(const Size& aSize)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
+
+  nsTArray<Size> previewSizes;
+  nsresult rv = mParams.Get(CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, previewSizes);
+  if (NS_FAILED(rv)) {
+    DOM_CAMERA_LOGE("Camera failed to return any preview sizes (0x%x)\n", rv);
+    return rv;
   }
 
-  if (!aWidth && !aHeight) {
+  Size best = aSize;
+  uint32_t minSizeDelta = UINT32_MAX;
+  uint32_t delta;
+
+  if (!aSize.width && !aSize.height) {
     // no size specified, take the first supported size
-    size = previewSizes[0];
-    bestWidth = size.width;
-    bestHeight = size.height;
-  } else if (aWidth && aHeight) {
+    best = previewSizes[0];
+  } else if (aSize.width && aSize.height) {
     // both height and width specified, find the supported size closest to requested size
-    for (uint32_t i = 0; i < previewSizes.size(); i++) {
+    uint32_t targetArea = aSize.width * aSize.height;
+    for (uint32_t i = 0; i < previewSizes.Length(); i++) {
       Size size = previewSizes[i];
-      uint32_t delta = abs((long int)(size.width * size.height - aWidth * aHeight));
+      uint32_t delta = abs((long int)(size.width * size.height - targetArea));
       if (delta < minSizeDelta) {
         minSizeDelta = delta;
-        bestWidth = size.width;
-        bestHeight = size.height;
+        best = size;
       }
     }
-  } else if (!aWidth) {
+  } else if (!aSize.width) {
     // width not specified, find closest height match
-    for (uint32_t i = 0; i < previewSizes.size(); i++) {
-      size = previewSizes[i];
-      delta = abs((long int)(size.height - aHeight));
+    for (uint32_t i = 0; i < previewSizes.Length(); i++) {
+      Size size = previewSizes[i];
+      delta = abs((long int)(size.height - aSize.height));
       if (delta < minSizeDelta) {
         minSizeDelta = delta;
-        bestWidth = size.width;
-        bestHeight = size.height;
+        best = size;
       }
     }
-  } else if (!aHeight) {
+  } else if (!aSize.height) {
     // height not specified, find closest width match
-    for (uint32_t i = 0; i < previewSizes.size(); i++) {
-      size = previewSizes[i];
-      delta = abs((long int)(size.width - aWidth));
+    for (uint32_t i = 0; i < previewSizes.Length(); i++) {
+      Size size = previewSizes[i];
+      delta = abs((long int)(size.width - aSize.width));
       if (delta < minSizeDelta) {
         minSizeDelta = delta;
-        bestWidth = size.width;
-        bestHeight = size.height;
+        best = size;
       }
     }
   }
 
-  mWidth = bestWidth;
-  mHeight = bestHeight;
   {
-    RwAutoLockWrite lock(mRwLock);
-    mParams.setPreviewSize(mWidth, mHeight);
+    ICameraControlParameterSetAutoEnter set(this);
+
+    // Some camera drivers will ignore our preview size if it's larger
+    // that the currently set video recording size, so we need to set
+    // both here just in case.
+    rv = SetAndPush(CAMERA_PARAM_PREVIEWSIZE, best);
+    if (NS_FAILED(rv)) {
+      DOM_CAMERA_LOGE("Failed to set picture mode preview size (0x%x)\n", rv);
+      return rv;
+    }
+
+    rv = SetAndPush(CAMERA_PARAM_VIDEOSIZE, best);
+    if (NS_FAILED(rv)) {
+      DOM_CAMERA_LOGE("Failed to bump up picture mode video size (0x%x)\n", rv);
+      return rv;
+    }
   }
-  PushParameters();
+
+  mCurrentConfiguration.mPreviewSize = best;
+  return NS_OK;
 }
 
 nsresult
 nsGonkCameraControl::SetupVideoMode(const nsAString& aProfile)
 {
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+
   // read preferences for camcorder
   mMediaProfiles = MediaProfiles::getInstance();
 
   nsAutoCString profile = NS_ConvertUTF16toUTF8(aProfile);
   mRecorderProfile = GetGonkRecorderProfileManager().get()->Get(profile.get());
   if (!mRecorderProfile) {
     DOM_CAMERA_LOGE("Recorder profile '%s' is not supported\n", profile.get());
     return NS_ERROR_INVALID_ARG;
   }
 
   const GonkRecorderVideoProfile* video = mRecorderProfile->GetGonkVideoProfile();
   int width = video->GetWidth();
   int height = video->GetHeight();
   int fps = video->GetFramerate();
-  if (fps == -1 || width == -1 || height == -1) {
-    DOM_CAMERA_LOGE("Can't configure preview with fps=%d, width=%d, height=%d\n", fps, width, height);
+  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();
 
-  // configure camera video recording parameters
-  const size_t SIZE = 256;
-  char buffer[SIZE];
+  Size size;
+  size.width = static_cast<uint32_t>(width);
+  size.height = static_cast<uint32_t>(height);
 
   {
-    RwAutoLockWrite lock(mRwLock);
-    mParams.setPreviewSize(width, height);
-    mParams.setPreviewFrameRate(fps);
+    ICameraControlParameterSetAutoEnter set(this);
+
+    // The camera interface allows for hardware to provide two video
+    //  streams, a low resolution preview and a potentially high resolution
+    //  stream for encoding. For now we don't use this and set preview and video
+    //  size to the same thing.
+    nsresult rv = SetAndPush(CAMERA_PARAM_PREVIEWSIZE, size);
+    if (NS_FAILED(rv)) {
+      DOM_CAMERA_LOGE("Failed to set video mode preview size (0x%x)\n", rv);
+      return rv;
+    }
 
-    /**
-     * "record-size" is probably deprecated in later ICS;
-     * might need to set "video-size" instead of "record-size".
-     * See bug 795332.
-     */
-    snprintf(buffer, SIZE, "%dx%d", width, height);
-    mParams.set("record-size", buffer);
+    rv = SetAndPush(CAMERA_PARAM_VIDEOSIZE, size);
+    if (NS_FAILED(rv)) {
+      DOM_CAMERA_LOGE("Failed to set video mode video size (0x%x)\n", rv);
+      return rv;
+    }
+
+    rv = SetAndPush(CAMERA_PARAM_PREVIEWFRAMERATE, fps);
+    if (NS_FAILED(rv)) {
+      DOM_CAMERA_LOGE("Failed to set video mode frame rate (0x%x)\n", rv);
+      return rv;
+    }
+    mPreviewFps = fps;
   }
 
-  // push the updated camera configuration immediately
-  PushParameters();
+  mLastRecorderSize = size;
   return NS_OK;
 }
 
 class GonkRecorderListener : public IMediaRecorderClient
 {
 public:
   GonkRecorderListener(nsGonkCameraControl* aCameraControl)
     : mCameraControl(aCameraControl)
   {
-    DOM_CAMERA_LOGT("%s:%d : this=%p, aCameraControl=%p\n", __func__, __LINE__, this, mCameraControl.get());
+    DOM_CAMERA_LOGT("%s:%d : this=%p, aCameraControl=%p\n",
+      __func__, __LINE__, this, mCameraControl.get());
   }
 
   void notify(int msg, int ext1, int ext2)
   {
     if (mCameraControl) {
-      mCameraControl->HandleRecorderEvent(msg, ext1, ext2);
+      mCameraControl->OnRecorderEvent(msg, ext1, ext2);
     }
   }
 
   IBinder* onAsBinder()
   {
     DOM_CAMERA_LOGE("onAsBinder() called, should NEVER get called!\n");
     return nullptr;
   }
 
 protected:
   ~GonkRecorderListener() { }
   nsRefPtr<nsGonkCameraControl> mCameraControl;
 };
 
 void
-nsGonkCameraControl::HandleRecorderEvent(int msg, int ext1, int ext2)
+nsGonkCameraControl::OnRecorderEvent(int msg, int ext1, int ext2)
 {
   /**
    * Refer to base/include/media/mediarecorder.h for a complete list
    * of error and info message codes.  There are duplicate values
    * within the status/error code space, as determined by code inspection:
    *
    *    +------- msg
    *    | +----- ext1
@@ -1429,79 +1216,79 @@ nsGonkCameraControl::HandleRecorderEvent
    * 3. Specific error codes are from utils/Errors.h and/or
    *    include/media/stagefright/MediaErrors.h.
    * 4. Only in frameworks/base/media/libmedia/mediaplayer.cpp.
    * 5. These are mostly informational and we can ignore them; note that
    *    although the MEDIA_RECORDER_INFO_MAX_DURATION_REACHED and
    *    MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED values are defined in this
    *    enum, they are used with different ext1 codes.  /o\
    */
-  int trackNum = -1;  // no track
+  int trackNum = CameraControlListener::kNoTrackNumber;
 
   switch (msg) {
     // Recorder-related events
     case MEDIA_RECORDER_EVENT_INFO:
       switch (ext1) {
         case MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED:
           DOM_CAMERA_LOGI("recorder-event : info: maximum file size reached\n");
-          OnRecorderStateChange(NS_LITERAL_STRING("FileSizeLimitReached"), ext2, trackNum);
+          OnRecorderStateChange(CameraControlListener::kFileSizeLimitReached, ext2, trackNum);
           return;
 
         case MEDIA_RECORDER_INFO_MAX_DURATION_REACHED:
           DOM_CAMERA_LOGI("recorder-event : info: maximum video duration reached\n");
-          OnRecorderStateChange(NS_LITERAL_STRING("VideoLengthLimitReached"), ext2, trackNum);
+          OnRecorderStateChange(CameraControlListener::kVideoLengthLimitReached, ext2, trackNum);
           return;
 
         case MEDIA_RECORDER_TRACK_INFO_COMPLETION_STATUS:
           DOM_CAMERA_LOGI("recorder-event : info: track completed\n");
-          OnRecorderStateChange(NS_LITERAL_STRING("TrackCompleted"), ext2, trackNum);
+          OnRecorderStateChange(CameraControlListener::kTrackCompleted, ext2, trackNum);
           return;
       }
       break;
 
     case MEDIA_RECORDER_EVENT_ERROR:
       switch (ext1) {
         case MEDIA_RECORDER_ERROR_UNKNOWN:
           DOM_CAMERA_LOGE("recorder-event : recorder-error: %d (0x%08x)\n", ext2, ext2);
-          OnRecorderStateChange(NS_LITERAL_STRING("MediaRecorderFailed"), ext2, trackNum);
+          OnRecorderStateChange(CameraControlListener::kMediaRecorderFailed, ext2, trackNum);
           return;
 
         case MEDIA_ERROR_SERVER_DIED:
           DOM_CAMERA_LOGE("recorder-event : recorder-error: server died\n");
-          OnRecorderStateChange(NS_LITERAL_STRING("MediaServerFailed"), ext2, trackNum);
+          OnRecorderStateChange(CameraControlListener::kMediaServerFailed, ext2, trackNum);
           return;
       }
       break;
 
     // Track-related events, see note 1(a) above.
     case MEDIA_RECORDER_TRACK_EVENT_INFO:
       trackNum = (ext1 & 0xF0000000) >> 28;
       ext1 &= 0xFFFF;
       switch (ext1) {
         case MEDIA_RECORDER_TRACK_INFO_COMPLETION_STATUS:
           if (ext2 == OK) {
             DOM_CAMERA_LOGI("recorder-event : track-complete: track %d, %d (0x%08x)\n", trackNum, ext2, ext2);
-            OnRecorderStateChange(NS_LITERAL_STRING("TrackCompleted"), ext2, trackNum);
+            OnRecorderStateChange(CameraControlListener::kTrackCompleted, ext2, trackNum);
             return;
           }
           DOM_CAMERA_LOGE("recorder-event : track-error: track %d, %d (0x%08x)\n", trackNum, ext2, ext2);
-          OnRecorderStateChange(NS_LITERAL_STRING("TrackFailed"), ext2, trackNum);
+          OnRecorderStateChange(CameraControlListener::kTrackFailed, ext2, trackNum);
           return;
 
         case MEDIA_RECORDER_TRACK_INFO_PROGRESS_IN_TIME:
           DOM_CAMERA_LOGI("recorder-event : track-info: progress in time: %d ms\n", ext2);
           return;
       }
       break;
 
     case MEDIA_RECORDER_TRACK_EVENT_ERROR:
       trackNum = (ext1 & 0xF0000000) >> 28;
       ext1 &= 0xFFFF;
       DOM_CAMERA_LOGE("recorder-event : track-error: track %d, %d (0x%08x)\n", trackNum, ext2, ext2);
-      OnRecorderStateChange(NS_LITERAL_STRING("TrackFailed"), ext2, trackNum);
+      OnRecorderStateChange(CameraControlListener::kTrackFailed, ext2, trackNum);
       return;
   }
 
   // All unhandled cases wind up here
   DOM_CAMERA_LOGW("recorder-event : unhandled: msg=%d, ext1=%d, ext2=%d\n", msg, ext1, ext2);
 }
 
 nsresult
@@ -1547,77 +1334,46 @@ nsGonkCameraControl::SetupRecording(int 
 
   // recording API needs file descriptor of output file
   CHECK_SETARG(mRecorder->setOutputFile(aFd, 0, 0));
   CHECK_SETARG(mRecorder->prepare());
   return NS_OK;
 }
 
 nsresult
-nsGonkCameraControl::GetPreviewStreamVideoModeImpl(GetPreviewStreamVideoModeTask* aGetPreviewStreamVideoMode)
-{
-  // stop any currently running preview
-  StopPreviewInternal(true /* forced */);
-
-  // setup the video mode
-  nsresult rv = SetupVideoMode(aGetPreviewStreamVideoMode->mOptions.profile);
-  NS_ENSURE_SUCCESS(rv, rv);
-  
-  const RecorderVideoProfile* video = mRecorderProfile->GetVideoProfile();
-  int width = video->GetWidth();
-  int height = video->GetHeight();
-  int fps = video->GetFramerate();
-  DOM_CAMERA_LOGI("recording preview format: %d x %d (%d fps)\n", width, height, fps);
-
-  // create and return new preview stream object
-  nsCOMPtr<GetPreviewStreamResult> getPreviewStreamResult = new GetPreviewStreamResult(this, width, height, fps, aGetPreviewStreamVideoMode->mOnSuccessCb, mWindowId);
-  rv = NS_DispatchToMainThread(getPreviewStreamResult);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to dispatch GetPreviewStreamVideoMode() onSuccess callback to main thread!");
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-nsresult
-nsGonkCameraControl::ReleaseHardwareImpl(ReleaseHardwareTask* aReleaseHardware)
+nsGonkCameraControl::ReleaseHardwareImpl()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
 
   // if we're recording, stop recording
   if (mRecorder) {
     DOM_CAMERA_LOGI("shutting down existing video recorder\n");
     mRecorder->stop();
     mRecorder = nullptr;
   }
 
   // stop the preview
-  StopPreviewInternal(true /* forced */);
+  StopPreviewImpl();
 
   // release the hardware handle
   if (mCameraHw.get()){
      mCameraHw->Close();
      mCameraHw.clear();
   }
 
-  if (aReleaseHardware) {
-    nsCOMPtr<nsIRunnable> releaseHardwareResult = new ReleaseHardwareResult(aReleaseHardware->mOnSuccessCb, mWindowId);
-    return NS_DispatchToMainThread(releaseHardwareResult);
-  }
-
+  OnHardwareStateChange(CameraControlListener::kHardwareClosed);
   return NS_OK;
 }
 
 already_AddRefed<GonkRecorderProfileManager>
 nsGonkCameraControl::GetGonkRecorderProfileManager()
 {
   if (!mProfileManager) {
-    nsTArray<idl::CameraSize> sizes;
-    nsresult rv = GetVideoSizes(sizes);
+    nsTArray<Size> sizes;
+    nsresult rv = Get(CAMERA_PARAM_SUPPORTED_VIDEOSIZES, sizes);
     NS_ENSURE_SUCCESS(rv, nullptr);
 
     mProfileManager = new GonkRecorderProfileManager(mCameraId);
     mProfileManager->SetSupportedResolutions(sizes);
   }
 
   nsRefPtr<GonkRecorderProfileManager> profileMgr = mProfileManager;
   return profileMgr.forget();
@@ -1625,92 +1381,90 @@ nsGonkCameraControl::GetGonkRecorderProf
 
 already_AddRefed<RecorderProfileManager>
 nsGonkCameraControl::GetRecorderProfileManagerImpl()
 {
   nsRefPtr<RecorderProfileManager> profileMgr = GetGonkRecorderProfileManager();
   return profileMgr.forget();
 }
 
-nsresult
-nsGonkCameraControl::GetVideoSizes(nsTArray<idl::CameraSize>& aVideoSizes)
+void
+nsGonkCameraControl::OnNewPreviewFrame(layers::GraphicBufferLocked* aBuffer)
 {
-  aVideoSizes.Clear();
+  nsRefPtr<Image> frame = mImageContainer->CreateImage(ImageFormat::GRALLOC_PLANAR_YCBCR);
+
+  GrallocImage* videoImage = static_cast<GrallocImage*>(frame.get());
 
-  android::Vector<Size> sizes;
-  {
-    RwAutoLockRead lock(mRwLock);
+  GrallocImage::GrallocData data;
+  data.mGraphicBuffer = static_cast<layers::GraphicBufferLocked*>(aBuffer);
+  data.mPicSize = IntSize(mCurrentConfiguration.mPreviewSize.width,
+                          mCurrentConfiguration.mPreviewSize.height);
+  videoImage->SetData(data);
 
-    mParams.getSupportedVideoSizes(sizes);
-    if (sizes.size() == 0) {
-      DOM_CAMERA_LOGI("Camera doesn't support video independent of the preview\n");
-      mParams.getSupportedPreviewSizes(sizes);
-    }
+  OnNewPreviewFrame(frame, mCurrentConfiguration.mPreviewSize.width,
+                    mCurrentConfiguration.mPreviewSize.height);
+}
+
+void
+nsGonkCameraControl::OnError(CameraControlListener::CameraErrorContext aWhere,
+                             CameraControlListener::CameraError aError)
+{
+  if (aError == CameraControlListener::kErrorServiceFailed) {
+    OnPreviewStateChange(CameraControlListener::kPreviewStopped);
+    OnHardwareStateChange(CameraControlListener::kHardwareClosed);
   }
 
-  if (sizes.size() == 0) {
-    DOM_CAMERA_LOGW("Camera doesn't report any supported video sizes at all\n");
-    return NS_OK;
-  }
-
-  for (size_t i = 0; i < sizes.size(); ++i) {
-    idl::CameraSize size;
-    size.width = sizes[i].width;
-    size.height = sizes[i].height;
-    aVideoSizes.AppendElement(size);
-  }
-  return NS_OK;
+  CameraControlImpl::OnError(aWhere, aError);
 }
 
 // Gonk callback handlers.
 namespace mozilla {
 
 void
-ReceiveImage(nsGonkCameraControl* gc, uint8_t* aData, uint32_t aLength)
+OnTakePictureComplete(nsGonkCameraControl* gc, uint8_t* aData, uint32_t aLength)
 {
-  gc->TakePictureComplete(aData, aLength);
-}
-
-void
-ReceiveImageError(nsGonkCameraControl* gc)
-{
-  gc->TakePictureError();
+  gc->OnTakePictureComplete(aData, aLength);
 }
 
 void
-AutoFocusComplete(nsGonkCameraControl* gc, bool aSuccess)
-{
-  gc->AutoFocusComplete(aSuccess);
-}
-
-static void
-GonkFrameBuilder(Image* aImage, void* aBuffer, uint32_t aWidth, uint32_t aHeight)
+OnTakePictureError(nsGonkCameraControl* gc)
 {
-  /**
-   * Cast the generic Image back to our platform-specific type and
-   * populate it.
-   */
-  GrallocImage* videoImage = static_cast<GrallocImage*>(aImage);
-  GrallocImage::GrallocData data;
-  data.mGraphicBuffer = static_cast<layers::GraphicBufferLocked*>(aBuffer);
-  data.mPicSize = IntSize(aWidth, aHeight);
-  videoImage->SetData(data);
+  gc->OnTakePictureError();
 }
 
 void
-ReceiveFrame(nsGonkCameraControl* gc, layers::GraphicBufferLocked* aBuffer)
+OnAutoFocusComplete(nsGonkCameraControl* gc, bool aSuccess)
 {
-  gc->ReceiveFrame(aBuffer, ImageFormat::GRALLOC_PLANAR_YCBCR, GonkFrameBuilder);
+  gc->OnAutoFocusComplete(aSuccess);
+}
+
+void
+OnNewPreviewFrame(nsGonkCameraControl* gc, layers::GraphicBufferLocked* aBuffer)
+{
+  gc->OnNewPreviewFrame(aBuffer);
 }
 
 void
 OnShutter(nsGonkCameraControl* gc)
 {
   gc->OnShutter();
 }
 
 void
 OnClosed(nsGonkCameraControl* gc)
 {
   gc->OnClosed();
 }
 
+void
+OnError(nsGonkCameraControl* gc, CameraControlListener::CameraError aError,
+        int32_t aArg1, int32_t aArg2)
+{
+#ifdef PR_LOGGING
+  DOM_CAMERA_LOGE("OnError : aError=%d, aArg1=%d, aArg2=%d\n", aError, aArg1, aArg2);
+#else
+  unused << aArg1;
+  unused << aArg2;
+#endif
+  gc->OnError(CameraControlListener::kInUnspecified, aError);
+}
+
 } // namespace mozilla
--- a/dom/camera/GonkCameraControl.h
+++ b/dom/camera/GonkCameraControl.h
@@ -1,10 +1,10 @@
 /*
- * Copyright (C) 2012 Mozilla Foundation
+ * Copyright (C) 2012-2013 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
@@ -13,136 +13,160 @@
  * 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 "prrwlock.h"
 #include <media/MediaProfiles.h>
+#include "mozilla/ReentrantMonitor.h"
 #include "DeviceStorage.h"
-#include "nsIDOMCameraManager.h"
-#include "DOMCameraControl.h"
 #include "CameraControlImpl.h"
 #include "CameraCommon.h"
 #include "GonkRecorder.h"
 #include "GonkCameraHwMgr.h"
+#include "GonkCameraParameters.h"
 
 namespace android {
-class GonkCameraHardware;
-class MediaProfiles;
-class GonkRecorder;
+  class GonkCameraHardware;
+  class MediaProfiles;
+  class GonkRecorder;
 }
 
 namespace mozilla {
 
 namespace layers {
-class GraphicBufferLocked;
+  class GraphicBufferLocked;
+  class ImageContainer;
 }
 
 class GonkRecorderProfile;
 class GonkRecorderProfileManager;
 
 class nsGonkCameraControl : public CameraControlImpl
 {
 public:
-  nsGonkCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId);
-  void DispatchInit(nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId);
-  nsresult Init();
+  nsGonkCameraControl(uint32_t aCameraId);
+  nsresult Init(const Configuration* aInitialConfig);
+  nsresult InitImpl();
+
+  void OnAutoFocusComplete(bool aSuccess);
+  void OnTakePictureComplete(uint8_t* aData, uint32_t aLength);
+  void OnTakePictureError();
+  void OnNewPreviewFrame(layers::GraphicBufferLocked* aBuffer);
+  void OnRecorderEvent(int msg, int ext1, int ext2);
+  void OnError(CameraControlListener::CameraErrorContext aWhere,
+               CameraControlListener::CameraError aError);
 
-  const char* GetParameter(const char* aKey);
-  const char* GetParameterConstChar(uint32_t aKey);
-  double GetParameterDouble(uint32_t aKey);
-  int32_t GetParameterInt32(uint32_t aKey);
-  void GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions);
-  void GetParameter(uint32_t aKey, nsTArray<idl::CameraSize>& aSizes);
-  void GetParameter(uint32_t aKey, idl::CameraSize& aSize);
-  void SetParameter(const char* aKey, const char* aValue);
-  void SetParameter(uint32_t aKey, const char* aValue);
-  void SetParameter(uint32_t aKey, double aValue);
-  void SetParameter(uint32_t aKey, const nsTArray<idl::CameraRegion>& aRegions);
-  void SetParameter(uint32_t aKey, int aValue);
-  void SetParameter(uint32_t aKey, const idl::CameraSize& aSize);
-  nsresult GetVideoSizes(nsTArray<idl::CameraSize>& aVideoSizes);
+  virtual nsresult Set(uint32_t aKey, const nsAString& aValue) MOZ_OVERRIDE;
+  virtual nsresult Get(uint32_t aKey, nsAString& aValue) MOZ_OVERRIDE;
+  virtual nsresult Set(uint32_t aKey, double aValue) MOZ_OVERRIDE;
+  virtual nsresult Get(uint32_t aKey, double& aValue) MOZ_OVERRIDE;
+  virtual nsresult Set(uint32_t aKey, int32_t aValue) MOZ_OVERRIDE;
+  virtual nsresult Get(uint32_t aKey, int32_t& aValue) MOZ_OVERRIDE;
+  virtual nsresult Set(uint32_t aKey, int64_t aValue) MOZ_OVERRIDE;
+  virtual nsresult Get(uint32_t aKey, int64_t& aValue) MOZ_OVERRIDE;
+  virtual nsresult Set(uint32_t aKey, const Size& aValue) MOZ_OVERRIDE;
+  virtual nsresult Get(uint32_t aKey, Size& aValue) MOZ_OVERRIDE;
+  virtual nsresult Set(uint32_t aKey, const nsTArray<Region>& aRegions) MOZ_OVERRIDE;
+  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;
+
   nsresult PushParameters();
-
-  void AutoFocusComplete(bool aSuccess);
-  void TakePictureComplete(uint8_t* aData, uint32_t aLength);
-  void TakePictureError();
-  void HandleRecorderEvent(int msg, int ext1, int ext2);
+  nsresult PullParameters();
 
 protected:
   ~nsGonkCameraControl();
 
-  nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream);
-  nsresult StartPreviewImpl(StartPreviewTask* aStartPreview);
-  nsresult StopPreviewImpl(StopPreviewTask* aStopPreview);
-  nsresult StopPreviewInternal(bool aForced = false);
-  nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus);
-  nsresult TakePictureImpl(TakePictureTask* aTakePicture);
-  nsresult StartRecordingImpl(StartRecordingTask* aStartRecording);
-  nsresult StopRecordingImpl(StopRecordingTask* aStopRecording);
-  nsresult PushParametersImpl();
-  nsresult PullParametersImpl();
-  nsresult GetPreviewStreamVideoModeImpl(GetPreviewStreamVideoModeTask* aGetPreviewStreamVideoMode);
-  nsresult ReleaseHardwareImpl(ReleaseHardwareTask* aReleaseHardware);
-  already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl();
+  using CameraControlImpl::OnNewPreviewFrame;
+  using CameraControlImpl::OnAutoFocusComplete;
+  using CameraControlImpl::OnTakePictureComplete;
+  using CameraControlImpl::OnConfigurationChange;
+  using CameraControlImpl::OnError;
+
+  virtual void BeginBatchParameterSet() MOZ_OVERRIDE;
+  virtual void EndBatchParameterSet() MOZ_OVERRIDE;
+
+  virtual nsresult SetConfigurationImpl(const Configuration& aConfig) MOZ_OVERRIDE;
+  nsresult SetConfigurationInternal(const Configuration& aConfig);
+  nsresult SetPictureConfiguration(const Configuration& aConfig);
+  nsresult SetVideoConfiguration(const Configuration& aConfig);
+
+  template<class T> nsresult SetAndPush(uint32_t aKey, const T& aValue);
+
+  virtual nsresult StartPreviewImpl() MOZ_OVERRIDE;
+  virtual nsresult StopPreviewImpl() MOZ_OVERRIDE;
+  virtual nsresult AutoFocusImpl(bool aCancelExistingCall) 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 PushParametersImpl() MOZ_OVERRIDE;
+  virtual nsresult PullParametersImpl() MOZ_OVERRIDE;
+  virtual nsresult ReleaseHardwareImpl() MOZ_OVERRIDE;
+  virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl() MOZ_OVERRIDE;
   already_AddRefed<GonkRecorderProfileManager> GetGonkRecorderProfileManager();
 
   nsresult SetupRecording(int aFd, int aRotation, int64_t aMaxFileSizeBytes, int64_t aMaxVideoLengthMs);
   nsresult SetupVideoMode(const nsAString& aProfile);
-  void SetPreviewSize(uint32_t aWidth, uint32_t aHeight);
-  void SetThumbnailSize(uint32_t aWidth, uint32_t aHeight);
-  void UpdateThumbnailSize();
-  void SetPictureSize(uint32_t aWidth, uint32_t aHeight);
+  nsresult SetPreviewSize(const Size& aSize);
+
+  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);
 
   int32_t RationalizeRotation(int32_t aRotation);
 
   android::sp<android::GonkCameraHardware> mCameraHw;
-  double                    mExposureCompensationMin;
-  double                    mExposureCompensationStep;
-  bool                      mDeferConfigUpdate;
-  PRRWLock*                 mRwLock;
-  android::CameraParameters mParams;
-  uint32_t                  mWidth;
-  uint32_t                  mHeight;
-  uint32_t                  mLastPictureWidth;
-  uint32_t                  mLastPictureHeight;
-  uint32_t                  mLastThumbnailWidth;
-  uint32_t                  mLastThumbnailHeight;
 
-  enum {
-    PREVIEW_FORMAT_UNKNOWN,
-    PREVIEW_FORMAT_YUV420P,
-    PREVIEW_FORMAT_YUV420SP
-  };
-  uint32_t                  mFormat;
+  Size                      mLastPictureSize;
+  Size                      mLastThumbnailSize;
+  Size                      mLastRecorderSize;
+  uint32_t                  mPreviewFps;
+  bool                      mResumePreviewAfterTakingPicture;
 
-  uint32_t                  mFps;
-  uint32_t                  mDiscardedFrameCount;
+  Atomic<uint32_t>          mDeferConfigUpdate;
+  GonkCameraParameters      mParams;
+
+  nsRefPtr<mozilla::layers::ImageContainer> mImageContainer;
 
   android::MediaProfiles*   mMediaProfiles;
   nsRefPtr<android::GonkRecorder> mRecorder;
 
-  // camcorder profile settings for the desired quality level
+  // 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:
   nsGonkCameraControl(const nsGonkCameraControl&) MOZ_DELETE;
   nsGonkCameraControl& operator=(const nsGonkCameraControl&) MOZ_DELETE;
 };
 
 // camera driver callbacks
-void ReceiveImage(nsGonkCameraControl* gc, uint8_t* aData, uint32_t aLength);
-void ReceiveImageError(nsGonkCameraControl* gc);
-void AutoFocusComplete(nsGonkCameraControl* gc, bool aSuccess);
-void ReceiveFrame(nsGonkCameraControl* gc, layers::GraphicBufferLocked* aBuffer);
+void OnTakePictureComplete(nsGonkCameraControl* gc, uint8_t* aData, uint32_t aLength);
+void OnTakePictureError(nsGonkCameraControl* gc);
+void OnAutoFocusComplete(nsGonkCameraControl* gc, bool aSuccess);
+void OnNewPreviewFrame(nsGonkCameraControl* gc, layers::GraphicBufferLocked* aBuffer);
 void OnShutter(nsGonkCameraControl* gc);
 void OnClosed(nsGonkCameraControl* gc);
+void OnError(nsGonkCameraControl* gc, CameraControlListener::CameraError aError,
+             int32_t aArg1, int32_t aArg2);
 
 } // namespace mozilla
 
 #endif // DOM_CAMERA_GONKCAMERACONTROL_H
--- a/dom/camera/GonkCameraHwMgr.cpp
+++ b/dom/camera/GonkCameraHwMgr.cpp
@@ -1,86 +1,64 @@
 /*
- * Copyright (C) 2012 Mozilla Foundation
+ * Copyright (C) 2012-2013 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 "GonkCameraHwMgr.h"
+
 #include <binder/IPCThreadState.h>
 #include <sys/system_properties.h>
 
 #include "base/basictypes.h"
 #include "nsDebug.h"
 #include "GonkCameraControl.h"
-#include "GonkCameraHwMgr.h"
 #include "GonkNativeWindow.h"
 #include "CameraCommon.h"
 
 using namespace mozilla;
 using namespace mozilla::layers;
 using namespace android;
 
-#if GIHM_TIMING_RECEIVEFRAME
-#define INCLUDE_TIME_H                  1
-#endif
-#if GIHM_TIMING_OVERALL
-#define INCLUDE_TIME_H                  1
-#endif
-
-#if INCLUDE_TIME_H
-#include <time.h>
-
-static __inline void timespecSubtract(struct timespec* a, struct timespec* b)
-{
-  // a = b - a
-  if (b->tv_nsec < a->tv_nsec) {
-    b->tv_nsec += 1000000000;
-    b->tv_sec -= 1;
-  }
-  a->tv_nsec = b->tv_nsec - a->tv_nsec;
-  a->tv_sec = b->tv_sec - a->tv_sec;
-}
-#endif
-
 GonkCameraHardware::GonkCameraHardware(mozilla::nsGonkCameraControl* aTarget, uint32_t aCameraId, const sp<Camera>& aCamera)
   : mCameraId(aCameraId)
   , mClosing(false)
-  , mMonitor("GonkCameraHardware.Monitor")
   , mNumFrames(0)
   , mCamera(aCamera)
   , mTarget(aTarget)
   , mInitialized(false)
   , mSensorOrientation(0)
 {
-  DOM_CAMERA_LOGT( "%s:%d : this=%p (aTarget=%p)\n", __func__, __LINE__, (void*)this, (void*)aTarget );
+  DOM_CAMERA_LOGT("%s:%d : this=%p (aTarget=%p)\n", __func__, __LINE__, (void*)this, (void*)aTarget);
   Init();
 }
 
 void
 GonkCameraHardware::OnNewFrame()
 {
   if (mClosing) {
     return;
   }
   nsRefPtr<GraphicBufferLocked> buffer = mNativeWindow->getCurrentBuffer();
   if (!buffer) {
     DOM_CAMERA_LOGW("received null frame");
     return;
   }
-  ReceiveFrame(mTarget, buffer);
+  OnNewPreviewFrame(mTarget, buffer);
 }
 
 // Android data callback
 void
 GonkCameraHardware::postData(int32_t aMsgType, const sp<IMemory>& aDataPtr, camera_frame_metadata_t* metadata)
 {
   if (mClosing) {
     return;
@@ -88,53 +66,49 @@ GonkCameraHardware::postData(int32_t aMs
 
   switch (aMsgType) {
     case CAMERA_MSG_PREVIEW_FRAME:
       // Do nothing
       break;
 
     case CAMERA_MSG_COMPRESSED_IMAGE:
       if (aDataPtr != nullptr) {
-        ReceiveImage(mTarget, (uint8_t*)aDataPtr->pointer(), aDataPtr->size());
+        OnTakePictureComplete(mTarget, static_cast<uint8_t*>(aDataPtr->pointer()), aDataPtr->size());
       } else {
-        ReceiveImageError(mTarget);
+        OnTakePictureError(mTarget);
       }
       break;
 
     default:
       DOM_CAMERA_LOGE("Unhandled data callback event %d\n", aMsgType);
       break;
   }
 }
 
 // Android notify callback
 void
 GonkCameraHardware::notify(int32_t aMsgType, int32_t ext1, int32_t ext2)
 {
-  bool bSuccess;
   if (mClosing) {
     return;
   }
 
   switch (aMsgType) {
     case CAMERA_MSG_FOCUS:
-      if (ext1) {
-        DOM_CAMERA_LOGI("Autofocus complete");
-        bSuccess = true;
-      } else {
-        DOM_CAMERA_LOGW("Autofocus failed");
-        bSuccess = false;
-      }
-      AutoFocusComplete(mTarget, bSuccess);
+      OnAutoFocusComplete(mTarget, !!ext1);
       break;
 
     case CAMERA_MSG_SHUTTER:
       OnShutter(mTarget);
       break;
 
+    case CAMERA_MSG_ERROR:
+      OnError(mTarget, CameraControlListener::kErrorServiceFailed, ext1, ext2);
+      break;
+
     default:
       DOM_CAMERA_LOGE("Unhandled notify callback event %d\n", aMsgType);
       break;
   }
 }
 
 void
 GonkCameraHardware::postDataTimestamp(nsecs_t aTimestamp, int32_t aMsgType, const sp<IMemory>& aDataPtr)
@@ -291,22 +265,36 @@ GonkCameraHardware::TakePicture()
 
 void
 GonkCameraHardware::CancelTakePicture()
 {
   DOM_CAMERA_LOGW("%s: android::Camera do not provide this capability\n", __func__);
 }
 
 int
+GonkCameraHardware::PushParameters(const GonkCameraParameters& aParams)
+{
+  const String8 s = aParams.Flatten();
+  return mCamera->setParameters(s);
+}
+
+int
 GonkCameraHardware::PushParameters(const CameraParameters& aParams)
 {
   String8 s = aParams.flatten();
   return mCamera->setParameters(s);
 }
 
+nsresult
+GonkCameraHardware::PullParameters(GonkCameraParameters& aParams)
+{
+  const String8 s = mCamera->getParameters();
+  return aParams.Unflatten(s);
+}
+
 void
 GonkCameraHardware::PullParameters(CameraParameters& aParams)
 {
   const String8 s = mCamera->getParameters();
   aParams.unflatten(s);
 }
 
 int
--- a/dom/camera/GonkCameraHwMgr.h
+++ b/dom/camera/GonkCameraHwMgr.h
@@ -22,25 +22,22 @@
 #include <camera/CameraParameters.h>
 #include <utils/threads.h>
 
 #include "GonkCameraControl.h"
 #include "CameraCommon.h"
 
 #include "GonkCameraListener.h"
 #include "GonkNativeWindow.h"
+#include "GonkCameraParameters.h"
 #include "mozilla/ReentrantMonitor.h"
 
-// config
-#define GIHM_TIMING_RECEIVEFRAME    0
-#define GIHM_TIMING_OVERALL         1
-
-
 namespace mozilla {
   class nsGonkCameraControl;
+  class GonkCameraParameters;
 }
 
 namespace android {
 
 class GonkCameraHardware : public GonkNativeWindowNewFrameCallback
                          , public CameraListener
 {
 protected:
@@ -81,37 +78,34 @@ public:
   int      GetSensorOrientation(uint32_t aType = RAW_SENSOR_ORIENTATION);
 
   int      AutoFocus();
   void     CancelAutoFocus();
   int      TakePicture();
   void     CancelTakePicture();
   int      StartPreview();
   void     StopPreview();
+  int      PushParameters(const mozilla::GonkCameraParameters& aParams);
   int      PushParameters(const CameraParameters& aParams);
+  nsresult PullParameters(mozilla::GonkCameraParameters& aParams);
   void     PullParameters(CameraParameters& aParams);
   int      StartRecording();
   int      StopRecording();
   int      SetListener(const sp<GonkCameraListener>& aListener);
   void     ReleaseRecordingFrame(const sp<IMemory>& aFrame);
   int      StoreMetaDataInBuffers(bool aEnabled);
 
 protected:
 
   uint32_t                      mCameraId;
   bool                          mClosing;
-  mozilla::ReentrantMonitor     mMonitor;
   uint32_t                      mNumFrames;
   sp<Camera>                    mCamera;
   mozilla::nsGonkCameraControl* mTarget;
   sp<GonkNativeWindow>          mNativeWindow;
-#if GIHM_TIMING_OVERALL
-  struct timespec               mStart;
-  struct timespec               mAutoFocusStart;
-#endif
   sp<GonkCameraListener>        mListener;
   bool                          mInitialized;
   int                           mRawSensorOrientation;
   int                           mSensorOrientation;
 
   bool IsInitialized()
   {
     return mInitialized;
--- a/dom/camera/GonkCameraManager.cpp
+++ b/dom/camera/GonkCameraManager.cpp
@@ -11,80 +11,81 @@
  * 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 <camera/Camera.h>
 
+#include "CameraCommon.h"
 #include "GonkCameraControl.h"
-#include "DOMCameraManager.h"
-#include "CameraCommon.h"
-#include "mozilla/ErrorResult.h"
+#include "ICameraControl.h"
 
 using namespace mozilla;
 
-// From nsDOMCameraManager, but gonk-specific!
+// From ICameraControl, gonk-specific management functions
 nsresult
-nsDOMCameraManager::GetNumberOfCameras(int32_t& aDeviceCount)
+ICameraControl::GetNumberOfCameras(int32_t& aDeviceCount)
 {
   aDeviceCount = android::Camera::getNumberOfCameras();
   return NS_OK;
 }
 
 nsresult
-nsDOMCameraManager::GetCameraName(uint32_t aDeviceNum, nsCString& aDeviceName)
+ICameraControl::GetCameraName(uint32_t aDeviceNum, nsCString& aDeviceName)
 {
   int32_t count = android::Camera::getNumberOfCameras();
+  int32_t deviceNum = static_cast<int32_t>(aDeviceNum);
+
   DOM_CAMERA_LOGI("GetCameraName : getNumberOfCameras() returned %d\n", count);
-  if (aDeviceNum > count) {
-    DOM_CAMERA_LOGE("GetCameraName : invalid device number");
+  if (deviceNum < 0 || deviceNum > count) {
+    DOM_CAMERA_LOGE("GetCameraName : invalid device number (%u)\n", aDeviceNum);
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   android::CameraInfo info;
-  int rv = android::Camera::getCameraInfo(aDeviceNum, &info);
+  int rv = android::Camera::getCameraInfo(deviceNum, &info);
   if (rv != 0) {
-    DOM_CAMERA_LOGE("GetCameraName : get_camera_info(%d) failed: %d\n", aDeviceNum, rv);
+    DOM_CAMERA_LOGE("GetCameraName : get_camera_info(%d) failed: %d\n", deviceNum, rv);
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   switch (info.facing) {
     case CAMERA_FACING_BACK:
       aDeviceName.Assign("back");
       break;
 
     case CAMERA_FACING_FRONT:
       aDeviceName.Assign("front");
       break;
 
     default:
       aDeviceName.Assign("extra-camera-");
-      aDeviceName.AppendInt(aDeviceNum);
+      aDeviceName.AppendInt(deviceNum);
       break;
   }
   return NS_OK;
 }
 
-void
-nsDOMCameraManager::GetListOfCameras(nsTArray<nsString>& aList, ErrorResult& aRv)
+nsresult
+ICameraControl::GetListOfCameras(nsTArray<nsString>& aList)
 {
   int32_t count = android::Camera::getNumberOfCameras();
+  DOM_CAMERA_LOGI("getListOfCameras : getNumberOfCameras() returned %d\n", count);
   if (count <= 0) {
-    return;
+    return NS_OK;
   }
 
-  DOM_CAMERA_LOGI("getListOfCameras : getNumberOfCameras() returned %d\n", count);
-
   // Allocate 2 extra slots to reserve space for 'front' and 'back' cameras
   // at the front of the array--we will collapse any empty slots below.
   aList.SetLength(2);
   uint32_t extraIdx = 2;
-  bool gotFront = false, gotBack = false;
+  bool gotFront = false;
+  bool gotBack = false;
   while (count--) {
     nsCString cameraName;
     nsresult result = GetCameraName(count, cameraName);
     if (result != NS_OK) {
       continue;
     }
 
     // The first camera we find named 'back' gets slot 0; and the first
@@ -99,13 +100,33 @@ nsDOMCameraManager::GetListOfCameras(nsT
       CopyUTF8toUTF16(cameraName, *aList.InsertElementAt(extraIdx));
       extraIdx++;
     }
   }
 
   if (!gotFront) {
     aList.RemoveElementAt(1);
   }
-  
+
   if (!gotBack) {
     aList.RemoveElementAt(0);
   }
+
+  return NS_OK;
 }
+
+// implementation-specific camera factory
+already_AddRefed<ICameraControl>
+ICameraControl::Create(uint32_t aCameraId, const Configuration* aInitialConfig)
+{
+  if (aInitialConfig) {
+    DOM_CAMERA_LOGI("Creating camera %d control, initial mode '%s'\n",
+      aCameraId, aInitialConfig->mMode == kVideoMode ? "video" : "picture");
+  } else {
+    DOM_CAMERA_LOGI("Creating camera %d control, no intial configuration\n", aCameraId);
+  }
+
+  nsRefPtr<nsGonkCameraControl> control = new nsGonkCameraControl(aCameraId);
+  nsresult rv = control->Init(aInitialConfig);
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
+  return control.forget();
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/GonkCameraParameters.cpp
@@ -0,0 +1,620 @@
+/*
+ * Copyright (C) 2013 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 "GonkCameraParameters.h"
+#include "camera/CameraParameters.h"
+#include "ICameraControl.h"
+#include "CameraCommon.h"
+
+using namespace mozilla;
+using namespace android;
+
+/* static */ const char*
+GonkCameraParameters::Parameters::GetTextKey(uint32_t aKey)
+{
+  switch (aKey) {
+    case CAMERA_PARAM_PREVIEWSIZE:
+      return KEY_PREVIEW_SIZE;
+    case CAMERA_PARAM_PREVIEWFORMAT:
+      return KEY_PREVIEW_FORMAT;
+    case CAMERA_PARAM_PREVIEWFRAMERATE:
+      return KEY_PREVIEW_FRAME_RATE;
+    case CAMERA_PARAM_EFFECT:
+      return KEY_EFFECT;
+    case CAMERA_PARAM_WHITEBALANCE:
+      return KEY_WHITE_BALANCE;
+    case CAMERA_PARAM_SCENEMODE:
+      return KEY_SCENE_MODE;
+    case CAMERA_PARAM_FLASHMODE:
+      return KEY_FLASH_MODE;
+    case CAMERA_PARAM_FOCUSMODE:
+      return KEY_FOCUS_MODE;
+    case CAMERA_PARAM_ZOOM:
+      return KEY_ZOOM;
+    case CAMERA_PARAM_METERINGAREAS:
+      return KEY_METERING_AREAS;
+    case CAMERA_PARAM_FOCUSAREAS:
+      return KEY_FOCUS_AREAS;
+    case CAMERA_PARAM_FOCALLENGTH:
+      return KEY_FOCAL_LENGTH;
+    case CAMERA_PARAM_FOCUSDISTANCENEAR:
+      return KEY_FOCUS_DISTANCES;
+    case CAMERA_PARAM_FOCUSDISTANCEOPTIMUM:
+      return KEY_FOCUS_DISTANCES;
+    case CAMERA_PARAM_FOCUSDISTANCEFAR:
+      return KEY_FOCUS_DISTANCES;
+    case CAMERA_PARAM_EXPOSURECOMPENSATION:
+      return KEY_EXPOSURE_COMPENSATION;
+    case CAMERA_PARAM_PICTURESIZE:
+      return KEY_PICTURE_SIZE;
+    case CAMERA_PARAM_THUMBNAILQUALITY:
+      return KEY_JPEG_THUMBNAIL_QUALITY;
+    case CAMERA_PARAM_PICTURE_SIZE:
+      return KEY_PICTURE_SIZE;
+    case CAMERA_PARAM_PICTURE_FILEFORMAT:
+      return KEY_PICTURE_FORMAT;
+    case CAMERA_PARAM_PICTURE_ROTATION:
+      return KEY_ROTATION;
+    case CAMERA_PARAM_PICTURE_DATETIME:
+      // Not every platform defines a KEY_EXIF_DATETIME;
+      // for those that don't, we use the raw string key, and if the platform
+      // doesn't support it, it will be ignored.
+      //
+      // See bug 832494.
+      return "exif-datetime";
+    case CAMERA_PARAM_VIDEOSIZE:
+      return KEY_VIDEO_SIZE;
+
+    case CAMERA_PARAM_SUPPORTED_PREVIEWSIZES:
+      return KEY_SUPPORTED_PREVIEW_SIZES;
+    case CAMERA_PARAM_SUPPORTED_PICTURESIZES:
+      return KEY_SUPPORTED_PICTURE_SIZES;
+    case CAMERA_PARAM_SUPPORTED_VIDEOSIZES:
+      return KEY_SUPPORTED_VIDEO_SIZES;
+    case CAMERA_PARAM_SUPPORTED_PICTUREFORMATS:
+      return KEY_SUPPORTED_PICTURE_FORMATS;
+    case CAMERA_PARAM_SUPPORTED_WHITEBALANCES:
+      return KEY_SUPPORTED_WHITE_BALANCE;
+    case CAMERA_PARAM_SUPPORTED_SCENEMODES:
+      return KEY_SUPPORTED_SCENE_MODES;
+    case CAMERA_PARAM_SUPPORTED_EFFECTS:
+      return KEY_SUPPORTED_EFFECTS;
+    case CAMERA_PARAM_SUPPORTED_FLASHMODES:
+      return KEY_SUPPORTED_FLASH_MODES;
+    case CAMERA_PARAM_SUPPORTED_FOCUSMODES:
+      return KEY_SUPPORTED_FOCUS_MODES;
+    case CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS:
+      return KEY_MAX_NUM_FOCUS_AREAS;
+    case CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS:
+      return KEY_MAX_NUM_METERING_AREAS;
+    case CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION:
+      return KEY_MIN_EXPOSURE_COMPENSATION;
+    case CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION:
+      return KEY_MAX_EXPOSURE_COMPENSATION;
+    case CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP:
+      return KEY_EXPOSURE_COMPENSATION_STEP;
+    case CAMERA_PARAM_SUPPORTED_ZOOM:
+      return KEY_ZOOM_SUPPORTED;
+    case CAMERA_PARAM_SUPPORTED_ZOOMRATIOS:
+      return KEY_ZOOM_RATIOS;
+    case CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES:
+      return KEY_SUPPORTED_JPEG_THUMBNAIL_SIZES;
+    default:
+      DOM_CAMERA_LOGE("Unhandled camera parameter value %u\n", aKey);
+      return nullptr;
+  }
+}
+
+GonkCameraParameters::GonkCameraParameters()
+  : mLock(PR_NewRWLock(PR_RWLOCK_RANK_NONE, "GonkCameraParameters.Lock"))
+  , mDirty(false)
+  , mInitialized(false)
+{
+  MOZ_COUNT_CTOR(GonkCameraParameters);
+  if (!mLock) {
+    MOZ_CRASH("OOM getting new PRRWLock");
+  }
+}
+
+GonkCameraParameters::~GonkCameraParameters()
+{
+  MOZ_COUNT_DTOR(GonkCameraParameters);
+  if (mLock) {
+    PR_DestroyRWLock(mLock);
+    mLock = nullptr;
+  }
+}
+
+// Any members that need to be initialized on the first parameter pull
+// need to get handled in here.
+nsresult
+GonkCameraParameters::Initialize()
+{
+  nsresult rv;
+
+  rv = GetImpl(CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION, mExposureCompensationMin);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = GetImpl(CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP, mExposureCompensationStep);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  mInitialized = true;
+  return NS_OK;
+}
+
+// Handle nsAStrings
+nsresult
+GonkCameraParameters::SetTranslated(uint32_t aKey, const nsAString& aValue)
+{
+  return SetImpl(aKey, NS_ConvertUTF16toUTF8(aValue).get());
+}
+
+nsresult
+GonkCameraParameters::GetTranslated(uint32_t aKey, nsAString& aValue)
+{
+  const char* val;
+  nsresult rv = GetImpl(aKey, val);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  aValue.AssignASCII(val);
+  return NS_OK;
+}
+
+// Handle ICameraControl::Sizes
+nsresult
+GonkCameraParameters::SetTranslated(uint32_t aKey, const ICameraControl::Size& aSize)
+{
+  if (aSize.width > INT_MAX || aSize.height > INT_MAX) {
+    // AOSP can only handle signed ints.
+    DOM_CAMERA_LOGE("Camera parameter aKey=%d out of bounds (width=%u, height=%u)\n",
+      aSize.width, aSize.height);
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsresult rv;
+
+  switch (aKey) {
+    case CAMERA_PARAM_THUMBNAILSIZE:
+      // This is a special case--for some reason the thumbnail size
+      // is accessed as two separate values instead of a tuple.
+      // XXXmikeh - make this restore the original values on error
+      rv = SetImpl(Parameters::KEY_JPEG_THUMBNAIL_WIDTH, static_cast<int>(aSize.width));
+      if (NS_SUCCEEDED(rv)) {
+        rv = SetImpl(Parameters::KEY_JPEG_THUMBNAIL_HEIGHT, static_cast<int>(aSize.height));
+      }
+      break;
+
+    case CAMERA_PARAM_VIDEOSIZE:
+      // "record-size" is probably deprecated in later ICS;
+      // might need to set "video-size" instead of "record-size";
+      // for the time being, set both. See bug 795332.
+      rv = SetImpl("record-size", nsPrintfCString("%ux%u", aSize.width, aSize.height).get());
+      if (NS_FAILED(rv)) {
+        break;
+      }
+      // intentional fallthrough
+
+    default:
+      rv = SetImpl(aKey, nsPrintfCString("%ux%u", aSize.width, aSize.height).get());
+      break;
+  }
+
+  if (NS_FAILED(rv)) {
+    DOM_CAMERA_LOGE("Camera parameter aKey=%d failed to set (0x%x)\n", aKey, rv);
+  }
+  return rv;
+}
+
+nsresult
+GonkCameraParameters::GetTranslated(uint32_t aKey, ICameraControl::Size& aSize)
+{