Bug 985496 - rationalize camera error reporting, r=dhylands
☠☠ backed out by 5d6a3571f1ab ☠ ☠
authorMike Habicher <mikeh@mozilla.com>
Fri, 25 Apr 2014 16:28:15 -0400
changeset 180832 a73dece39b0129e53040f29eed4f377ddf387dee
parent 180831 b23bdc96b607fba01f409d6239579692931fa746
child 180833 27b41f71ccd4475f9fac798e9604b4b5271d5ce8
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersdhylands
bugs985496
milestone31.0a1
Bug 985496 - rationalize camera error reporting, r=dhylands
content/media/webrtc/MediaEngineWebRTC.h
content/media/webrtc/MediaEngineWebRTCVideo.cpp
dom/camera/CameraControlImpl.cpp
dom/camera/CameraControlImpl.h
dom/camera/CameraControlListener.h
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/FallbackCameraControl.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/GonkRecorderProfiles.cpp
dom/camera/GonkRecorderProfiles.h
dom/camera/ICameraControl.h
dom/camera/TestGonkCameraControl.cpp
dom/camera/TestGonkCameraControl.h
dom/camera/TestGonkCameraHardware.cpp
dom/camera/moz.build
dom/camera/test/test_bug975472.html
--- a/content/media/webrtc/MediaEngineWebRTC.h
+++ b/content/media/webrtc/MediaEngineWebRTC.h
@@ -165,17 +165,17 @@ public:
   NS_DECL_THREADSAFE_ISUPPORTS
 #else
   // We are subclassed from CameraControlListener, which implements a
   // threadsafe reference-count for us.
   NS_DECL_ISUPPORTS_INHERITED
 
   void OnHardwareStateChange(HardwareState aState);
   bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);
-  void OnError(CameraErrorContext aContext, CameraError aError);
+  void OnUserError(UserContext aContext, nsresult 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();
   void RotateImage(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);
--- a/content/media/webrtc/MediaEngineWebRTCVideo.cpp
+++ b/content/media/webrtc/MediaEngineWebRTCVideo.cpp
@@ -630,17 +630,17 @@ MediaEngineWebRTCVideoSource::OnHardware
     LOG(("*** Initial orientation: %d (Camera %d Back %d MountAngle: %d)",
          mRotation, mCaptureIndex, mBackCamera, mCameraAngle));
     mState = kStarted;
     mCallbackMonitor.Notify();
   }
 }
 
 void
-MediaEngineWebRTCVideoSource::OnError(CameraErrorContext aContext, CameraError aError)
+MediaEngineWebRTCVideoSource::OnUserError(UserContext aContext, nsresult aError)
 {
   ReentrantMonitorAutoEnter sync(mCallbackMonitor);
   mCallbackMonitor.Notify();
 }
 
 void
 MediaEngineWebRTCVideoSource::OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType)
 {
--- a/dom/camera/CameraControlImpl.cpp
+++ b/dom/camera/CameraControlImpl.cpp
@@ -1,64 +1,69 @@
 /* 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 "mozilla/unused.h"
+#include "nsPrintfCString.h"
 #include "nsIWeakReferenceUtils.h"
 #include "CameraRecorderProfiles.h"
 #include "CameraCommon.h"
 #include "nsGlobalWindow.h"
 #include "DeviceStorageFileDescriptor.h"
 #include "CameraControlListener.h"
 
 using namespace mozilla;
 
 nsWeakPtr CameraControlImpl::sCameraThread;
 
 CameraControlImpl::CameraControlImpl(uint32_t aCameraId)
-  : mCameraId(aCameraId)
+  : mListenerLock(PR_NewRWLock(PR_RWLOCK_RANK_NONE, "CameraControlImpl.Listeners.Lock"))
+  , mCameraId(aCameraId)
   , 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));
+    if (NS_FAILED(rv)) {
+      MOZ_CRASH("Failed to create new Camera Thread");
+    }
 
     // keep a weak reference to the new thread
     sCameraThread = do_GetWeakReference(mCameraThread);
   }
 
   // Care must be taken with the mListenerLock read-write lock to prevent
   // deadlocks. Currently this is handled by ensuring that any attempts to
   // acquire the lock for writing (as in Add/RemoveListener()) happen in a
   // runnable dispatched to the Camera Thread--even if the method is being
   // called from that thread. This ensures that if a registered listener
   // (which is invoked with a read-lock) tries to call Add/RemoveListener(),
   // the lock-for-writing attempt won't happen until the listener has
   // completed.
   //
   // Multiple parallel listeners being invoked are not a problem because
   // the read-write lock allows multiple simultaneous read-locks.
-  mListenerLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "CameraControlImpl.Listeners.Lock");
+  if (!mListenerLock) {
+    MOZ_CRASH("Out of memory getting new PRRWLock");
+  }
 }
 
 CameraControlImpl::~CameraControlImpl()
 {
+  MOZ_ASSERT(mListenerLock, "mListenerLock missing in ~CameraControlImpl()");
   if (mListenerLock) {
     PR_DestroyRWLock(mListenerLock);
     mListenerLock = nullptr;
   }
 }
 
 already_AddRefed<RecorderProfileManager>
 CameraControlImpl::GetRecorderProfileManager()
@@ -258,114 +263,141 @@ CameraControlImpl::OnNewPreviewFrame(lay
   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)
+CameraControlImpl::OnUserError(CameraControlListener::UserContext aContext,
+                               nsresult 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[] = {
-    "api-failed",
-    "init-failed",
-    "invalid-configuration",
-    "service-failed",
-    "set-picture-size-failed",
-    "set-thumbnail-size-failed",
-    "unknown"
-  };
   const char* context[] = {
     "StartCamera",
     "StopCamera",
     "AutoFocus",
     "StartFaceDetection",
     "StopFaceDetection",
     "TakePicture",
     "StartRecording",
     "StopRecording",
     "SetConfiguration",
     "StartPreview",
     "StopPreview",
+    "SetPictureSize",
+    "SetThumbnailSize",
     "ResumeContinuousFocus",
     "Unspecified"
   };
-  if (static_cast<unsigned int>(aError) < sizeof(error) / sizeof(error[0]) &&
-    static_cast<unsigned int>(aContext) < sizeof(context) / sizeof(context[0])) {
-    DOM_CAMERA_LOGW("CameraControlImpl::OnError : aContext='%s' (%u), aError='%s' (%u)\n",
-      context[aContext], aContext, error[aError], aError);
+  if (static_cast<size_t>(aContext) < sizeof(context) / sizeof(context[0])) {
+    DOM_CAMERA_LOGW("CameraControlImpl::OnUserError : aContext='%s' (%d), aError=0x%x\n",
+      context[aContext], aContext, aError);
   } else {
-    DOM_CAMERA_LOGE("CameraControlImpl::OnError : aContext=%u, aError=%d\n",
+    DOM_CAMERA_LOGE("CameraControlImpl::OnUserError : aContext=%d, aError=0x%x\n",
       aContext, aError);
   }
 #endif
 
   for (uint32_t i = 0; i < mListeners.Length(); ++i) {
     CameraControlListener* l = mListeners[i];
-    l->OnError(aContext, aError);
+    l->OnUserError(aContext, aError);
+  }
+}
+
+void
+CameraControlImpl::OnSystemError(CameraControlListener::SystemContext aContext,
+                                 nsresult aError)
+{
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+#ifdef PR_LOGGING
+  const char* context[] = {
+    "Camera Service"
+  };
+  if (static_cast<size_t>(aContext) < sizeof(context) / sizeof(context[0])) {
+    DOM_CAMERA_LOGW("CameraControlImpl::OnSystemError : aContext='%s' (%d), aError=0x%x\n",
+      context[aContext], aContext, aError);
+  } else {
+    DOM_CAMERA_LOGE("CameraControlImpl::OnSystemError : aContext=%d, aError=0x%x\n",
+      aContext, aError);
+  }
+#endif
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnSystemError(aContext, aError);
   }
 }
 
 // Camera control asynchronous message; these are dispatched from
 //  the Main Thread to the Camera Thread, where they are consumed.
 
 class CameraControlImpl::ControlMessage : public nsRunnable
 {
 public:
   ControlMessage(CameraControlImpl* aCameraControl,
-                 CameraControlListener::CameraErrorContext aContext)
+                 CameraControlListener::UserContext 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);
+      nsPrintfCString msg("Camera control API(%d) failed with 0x%x", mContext, rv);
+      NS_WARNING(msg.get());
+      mCameraControl->OnUserError(mContext, rv);
     }
 
     return NS_OK;
   }
 
 protected:
+  virtual ~ControlMessage() { }
+
   nsRefPtr<CameraControlImpl> mCameraControl;
-  CameraControlListener::CameraErrorContext mContext;
+  CameraControlListener::UserContext mContext;
 };
 
 nsresult
+CameraControlImpl::Dispatch(ControlMessage* aMessage)
+{
+  nsresult rv = mCameraThread->Dispatch(aMessage, NS_DISPATCH_NORMAL);
+  if (NS_SUCCEEDED(rv)) {
+    return NS_OK;
+  }
+
+  nsPrintfCString msg("Failed to dispatch camera control message (0x%x)", rv);
+  NS_WARNING(msg.get());
+  return NS_ERROR_FAILURE;
+}
+
+nsresult
 CameraControlImpl::Start(const Configuration* aConfig)
 {
   class Message : public ControlMessage
   {
   public:
     Message(CameraControlImpl* aCameraControl,
-            CameraControlListener::CameraErrorContext aContext,
+            CameraControlListener::UserContext aContext,
             const Configuration* aConfig)
       : ControlMessage(aCameraControl, aContext)
       , mHaveInitialConfig(false)
     {
       if (aConfig) {
         mConfig = *aConfig;
         mHaveInitialConfig = true;
       }
@@ -380,144 +412,138 @@ CameraControlImpl::Start(const Configura
       return mCameraControl->StartImpl();
     }
 
   protected:
     bool mHaveInitialConfig;
     Configuration mConfig;
   };
 
-  return mCameraThread->Dispatch(
-    new Message(this, CameraControlListener::kInStartCamera, aConfig), NS_DISPATCH_NORMAL);
+  return Dispatch(new Message(this, CameraControlListener::kInStartCamera, aConfig));
 }
 
 nsresult
 CameraControlImpl::SetConfiguration(const Configuration& aConfig)
 {
   class Message : public ControlMessage
   {
   public:
     Message(CameraControlImpl* aCameraControl,
-            CameraControlListener::CameraErrorContext aContext,
+            CameraControlListener::UserContext 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);
+  return Dispatch(new Message(this, CameraControlListener::kInSetConfiguration, aConfig));
 }
 
 nsresult
 CameraControlImpl::AutoFocus()
 {
   class Message : public ControlMessage
   {
   public:
     Message(CameraControlImpl* aCameraControl,
-            CameraControlListener::CameraErrorContext aContext)
+            CameraControlListener::UserContext aContext)
       : ControlMessage(aCameraControl, aContext)
     { }
 
     nsresult
     RunImpl() MOZ_OVERRIDE
     {
       return mCameraControl->AutoFocusImpl();
     }
   };
 
-  return mCameraThread->Dispatch(
-    new Message(this, CameraControlListener::kInAutoFocus), NS_DISPATCH_NORMAL);
+  return Dispatch(new Message(this, CameraControlListener::kInAutoFocus));
 }
 
 nsresult
 CameraControlImpl::StartFaceDetection()
 {
   class Message : public ControlMessage
   {
   public:
     Message(CameraControlImpl* aCameraControl,
-            CameraControlListener::CameraErrorContext aContext)
+            CameraControlListener::UserContext aContext)
       : ControlMessage(aCameraControl, aContext)
     { }
 
     nsresult
     RunImpl() MOZ_OVERRIDE
     {
       return mCameraControl->StartFaceDetectionImpl();
     }
   };
 
-  return mCameraThread->Dispatch(
-    new Message(this, CameraControlListener::kInStartFaceDetection), NS_DISPATCH_NORMAL);
+  return Dispatch(new Message(this, CameraControlListener::kInStartFaceDetection));
 }
 
 nsresult
 CameraControlImpl::StopFaceDetection()
 {
   class Message : public ControlMessage
   {
   public:
     Message(CameraControlImpl* aCameraControl,
-            CameraControlListener::CameraErrorContext aContext)
+            CameraControlListener::UserContext aContext)
       : ControlMessage(aCameraControl, aContext)
     { }
 
     nsresult
     RunImpl() MOZ_OVERRIDE
     {
       return mCameraControl->StopFaceDetectionImpl();
     }
   };
 
-  return mCameraThread->Dispatch(
-    new Message(this, CameraControlListener::kInStopFaceDetection), NS_DISPATCH_NORMAL);
+  return Dispatch(new Message(this, CameraControlListener::kInStopFaceDetection));
 }
 
 nsresult
 CameraControlImpl::TakePicture()
 {
   class Message : public ControlMessage
   {
   public:
     Message(CameraControlImpl* aCameraControl,
-            CameraControlListener::CameraErrorContext aContext)
+            CameraControlListener::UserContext aContext)
       : ControlMessage(aCameraControl, aContext)
     { }
 
     nsresult
     RunImpl() MOZ_OVERRIDE
     {
       return mCameraControl->TakePictureImpl();
     }
   };
 
-  return mCameraThread->Dispatch(
-    new Message(this, CameraControlListener::kInTakePicture), NS_DISPATCH_NORMAL);
+  return Dispatch(new Message(this, CameraControlListener::kInTakePicture));
 }
 
 nsresult
 CameraControlImpl::StartRecording(DeviceStorageFileDescriptor* aFileDescriptor,
                                   const StartRecordingOptions* aOptions)
 {
   class Message : public ControlMessage
   {
   public:
     Message(CameraControlImpl* aCameraControl,
-            CameraControlListener::CameraErrorContext aContext,
+            CameraControlListener::UserContext aContext,
             const StartRecordingOptions* aOptions,
             DeviceStorageFileDescriptor* aFileDescriptor)
       : ControlMessage(aCameraControl, aContext)
       , mOptionsPassed(false)
       , mFileDescriptor(aFileDescriptor)
     {
       if (aOptions) {
         mOptions = *aOptions;
@@ -533,129 +559,126 @@ CameraControlImpl::StartRecording(Device
     }
 
   protected:
     StartRecordingOptions mOptions;
     bool mOptionsPassed;
     nsRefPtr<DeviceStorageFileDescriptor> mFileDescriptor;
   };
 
-
-  return mCameraThread->Dispatch(new Message(this, CameraControlListener::kInStartRecording,
-    aOptions, aFileDescriptor), NS_DISPATCH_NORMAL);
+  if (!aFileDescriptor) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  return Dispatch(new Message(this, CameraControlListener::kInStartRecording,
+    aOptions, aFileDescriptor));
 }
 
 nsresult
 CameraControlImpl::StopRecording()
 {
   class Message : public ControlMessage
   {
   public:
     Message(CameraControlImpl* aCameraControl,
-            CameraControlListener::CameraErrorContext aContext)
+            CameraControlListener::UserContext aContext)
       : ControlMessage(aCameraControl, aContext)
     { }
 
     nsresult
     RunImpl() MOZ_OVERRIDE
     {
       return mCameraControl->StopRecordingImpl();
     }
   };
 
-  return mCameraThread->Dispatch(
-    new Message(this, CameraControlListener::kInStopRecording), NS_DISPATCH_NORMAL);
+  return Dispatch(new Message(this, CameraControlListener::kInStopRecording));
 }
 
 nsresult
 CameraControlImpl::StartPreview()
 {
   class Message : public ControlMessage
   {
   public:
     Message(CameraControlImpl* aCameraControl,
-            CameraControlListener::CameraErrorContext aContext)
+            CameraControlListener::UserContext aContext)
       : ControlMessage(aCameraControl, aContext)
     { }
 
     nsresult
     RunImpl() MOZ_OVERRIDE
     {
       return mCameraControl->StartPreviewImpl();
     }
   };
 
-  return mCameraThread->Dispatch(
-    new Message(this, CameraControlListener::kInStartPreview), NS_DISPATCH_NORMAL);
+  return Dispatch(new Message(this, CameraControlListener::kInStartPreview));
 }
 
 nsresult
 CameraControlImpl::StopPreview()
 {
   class Message : public ControlMessage
   {
   public:
     Message(CameraControlImpl* aCameraControl,
-            CameraControlListener::CameraErrorContext aContext)
+            CameraControlListener::UserContext aContext)
       : ControlMessage(aCameraControl, aContext)
     { }
 
     nsresult
     RunImpl() MOZ_OVERRIDE
     {
       return mCameraControl->StopPreviewImpl();
     }
   };
 
-  return mCameraThread->Dispatch(
-    new Message(this, CameraControlListener::kInStopPreview), NS_DISPATCH_NORMAL);
+  return Dispatch(new Message(this, CameraControlListener::kInStopPreview));
 }
 
 nsresult
 CameraControlImpl::ResumeContinuousFocus()
 {
   class Message : public ControlMessage
   {
   public:
     Message(CameraControlImpl* aCameraControl,
-            CameraControlListener::CameraErrorContext aContext)
+            CameraControlListener::UserContext aContext)
       : ControlMessage(aCameraControl, aContext)
     { }
 
     nsresult
     RunImpl() MOZ_OVERRIDE
     {
       return mCameraControl->ResumeContinuousFocusImpl();
     }
   };
 
-  return mCameraThread->Dispatch(
-    new Message(this, CameraControlListener::kInResumeContinuousFocus), NS_DISPATCH_NORMAL);
+  return Dispatch(new Message(this, CameraControlListener::kInResumeContinuousFocus));
 }
 
 nsresult
 CameraControlImpl::Stop()
 {
   class Message : public ControlMessage
   {
   public:
     Message(CameraControlImpl* aCameraControl,
-            CameraControlListener::CameraErrorContext aContext)
+            CameraControlListener::UserContext aContext)
       : ControlMessage(aCameraControl, aContext)
     { }
 
     nsresult
     RunImpl() MOZ_OVERRIDE
     {
       return mCameraControl->StopImpl();
     }
   };
 
-  return mCameraThread->Dispatch(
-    new Message(this, CameraControlListener::kInStopCamera), NS_DISPATCH_NORMAL);
+  return Dispatch(new Message(this, CameraControlListener::kInStopCamera));
 }
 
 class CameraControlImpl::ListenerMessage : public CameraControlImpl::ControlMessage
 {
 public:
   ListenerMessage(CameraControlImpl* aCameraControl,
                   CameraControlListener* aListener)
     : ControlMessage(aCameraControl, CameraControlListener::kInUnspecified)
@@ -694,17 +717,19 @@ CameraControlImpl::AddListener(CameraCon
     nsresult
     RunImpl() MOZ_OVERRIDE
     {
       mCameraControl->AddListenerImpl(mListener.forget());
       return NS_OK;
     }
   };
 
-  mCameraThread->Dispatch(new Message(this, aListener), NS_DISPATCH_NORMAL);
+  if (aListener) {
+    Dispatch(new Message(this, aListener));
+  }
 }
 
 void
 CameraControlImpl::RemoveListenerImpl(CameraControlListener* aListener)
 {
   RwLockAutoEnterWrite lock(mListenerLock);
 
   nsRefPtr<CameraControlListener> l(aListener);
@@ -726,10 +751,12 @@ CameraControlImpl::RemoveListener(Camera
     nsresult
     RunImpl() MOZ_OVERRIDE
     {
       mCameraControl->RemoveListenerImpl(mListener);
       return NS_OK;
     }
   };
 
-  mCameraThread->Dispatch(new Message(this, aListener), NS_DISPATCH_NORMAL);
+  if (aListener) {
+    Dispatch(new Message(this, aListener));
+  }
 }
--- a/dom/camera/CameraControlImpl.h
+++ b/dom/camera/CameraControlImpl.h
@@ -29,19 +29,19 @@ class RecorderProfileManager;
 
 class CameraControlImpl : public ICameraControl
 {
 public:
   CameraControlImpl(uint32_t aCameraId);
   virtual void AddListener(CameraControlListener* aListener) MOZ_OVERRIDE;
   virtual void RemoveListener(CameraControlListener* aListener) MOZ_OVERRIDE;
 
+  // See ICameraControl.h for these methods' return values.
   virtual nsresult Start(const Configuration* aConfig = nullptr) MOZ_OVERRIDE;
   virtual nsresult Stop() MOZ_OVERRIDE;
-
   virtual nsresult SetConfiguration(const Configuration& aConfig) MOZ_OVERRIDE;
   virtual nsresult StartPreview() MOZ_OVERRIDE;
   virtual nsresult StopPreview() MOZ_OVERRIDE;
   virtual nsresult AutoFocus() MOZ_OVERRIDE;
   virtual nsresult StartFaceDetection() MOZ_OVERRIDE;
   virtual nsresult StopFaceDetection() MOZ_OVERRIDE;
   virtual nsresult TakePicture() MOZ_OVERRIDE;
   virtual nsresult StartRecording(DeviceStorageFileDescriptor* aFileDescriptor,
@@ -52,18 +52,18 @@ public:
   already_AddRefed<RecorderProfileManager> GetRecorderProfileManager();
   uint32_t GetCameraId() { return mCameraId; }
 
   virtual void Shutdown() MOZ_OVERRIDE;
 
   // Event handlers called directly from outside this class.
   void OnShutter();
   void OnClosed();
-  void OnError(CameraControlListener::CameraErrorContext aContext,
-               CameraControlListener::CameraError aError);
+  void OnUserError(CameraControlListener::UserContext aContext, nsresult aError);
+  void OnSystemError(CameraControlListener::SystemContext aContext, nsresult aError);
   void OnAutoFocusMoving(bool aIsMoving);
 
 protected:
   // Event handlers.
   void OnAutoFocusComplete(bool aAutoFocusSucceeded);
   void OnFacesDetected(const nsTArray<Face>& aFaces);
   void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType);
 
@@ -91,31 +91,47 @@ protected:
   void AddListenerImpl(already_AddRefed<CameraControlListener> aListener);
   void RemoveListenerImpl(CameraControlListener* aListener);
   nsTArray<nsRefPtr<CameraControlListener> > mListeners;
   PRRWLock* mListenerLock;
 
   class ControlMessage;
   class ListenerMessage;
 
+  nsresult Dispatch(ControlMessage* aMessage);
+
+  // Asynchronous method implementations, invoked on the Camera Thread.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if one or more arguments is invalid;
+  //  - NS_ERROR_NOT_INITIALIZED if the underlying hardware is not initialized,
+  //      failed to initialize (in the case of StartImpl()), or has been stopped;
+  //      for StartRecordingImpl(), this indicates that no recorder has been
+  //      configured (either by calling StartImpl() or SetConfigurationImpl());
+  //  - NS_ERROR_ALREADY_INITIALIZED if the underlying hardware is already
+  //      initialized;
+  //  - NS_ERROR_NOT_IMPLEMENTED if the method is not implemented;
+  //  - NS_ERROR_FAILURE on general failures.
   virtual nsresult StartImpl(const Configuration* aConfig = nullptr) = 0;
   virtual nsresult StopImpl() = 0;
   virtual nsresult SetConfigurationImpl(const Configuration& aConfig) = 0;
   virtual nsresult StartPreviewImpl() = 0;
   virtual nsresult StopPreviewImpl() = 0;
   virtual nsresult AutoFocusImpl() = 0;
   virtual nsresult StartFaceDetectionImpl() = 0;
   virtual nsresult StopFaceDetectionImpl() = 0;
   virtual nsresult TakePictureImpl() = 0;
   virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
                                       const StartRecordingOptions* aOptions) = 0;
   virtual nsresult StopRecordingImpl() = 0;
   virtual nsresult ResumeContinuousFocusImpl() = 0;
   virtual nsresult PushParametersImpl() = 0;
   virtual nsresult PullParametersImpl() = 0;
+
   virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl() = 0;
 
   void OnShutterInternal();
   void OnClosedInternal();
 
   uint32_t mCameraId;
 
   CameraControlListener::CameraListenerConfiguration mCurrentConfiguration;
--- a/dom/camera/CameraControlListener.h
+++ b/dom/camera/CameraControlListener.h
@@ -77,40 +77,41 @@ public:
   };
   virtual void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration) { }
 
   virtual void OnAutoFocusComplete(bool aAutoFocusSucceeded) { }
   virtual void OnAutoFocusMoving(bool aIsMoving) { }
   virtual void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType) { }
   virtual void OnFacesDetected(const nsTArray<ICameraControl::Face>& aFaces) { }
 
-  enum CameraErrorContext
+  enum UserContext
   {
     kInStartCamera,
     kInStopCamera,
     kInAutoFocus,
     kInStartFaceDetection,
     kInStopFaceDetection,
     kInTakePicture,
     kInStartRecording,
     kInStopRecording,
     kInSetConfiguration,
     kInStartPreview,
     kInStopPreview,
+    kInSetPictureSize,
+    kInSetThumbnailSize,
     kInResumeContinuousFocus,
     kInUnspecified
   };
-  enum CameraError
+  // Error handler for problems arising due to user-initiated actions.
+  virtual void OnUserError(UserContext aContext, nsresult aError) { }
+
+  enum SystemContext
   {
-    kErrorApiFailed,
-    kErrorInitFailed,
-    kErrorInvalidConfiguration,
-    kErrorServiceFailed,
-    kErrorSetPictureSizeFailed,
-    kErrorSetThumbnailSizeFailed,
-    kErrorUnknown
+    kSystemService
   };
-  virtual void OnError(CameraErrorContext aContext, CameraError aError) { }
+  // Error handler for problems arising due to system failures, not triggered
+  // by something the CameraControl API user did.
+  virtual void OnSystemError(SystemContext aContext, nsresult aError) { }
 };
 
 } // namespace mozilla
 
 #endif // DOM_CAMERA_CAMERACONTROLLISTENER_H
--- a/dom/camera/CameraRecorderProfiles.h
+++ b/dom/camera/CameraRecorderProfiles.h
@@ -39,16 +39,24 @@ public:
     switch (mCodec) {
       case H263:    return "h263";
       case H264:    return "h264";
       case MPEG4SP: return "mpeg4sp";
       default:      return nullptr;
     }
   }
 
+  // Get a representation of this video profile that can be returned
+  // to JS, possibly as a child member of another object.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aObject' is null;
+  //  - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
+  //  - NS_ERROR_FAILURE if construction of the JS object fails.
   nsresult GetJsObject(JSContext* aCx, JSObject** aObject);
 
 protected:
   uint32_t mCameraId;
   uint32_t mQualityIndex;
   Codec mCodec;
   int mBitrate;
   int mFramerate;
@@ -68,28 +76,35 @@ public:
 
   enum Codec {
     AMRNB,
     AMRWB,
     AAC,
     UNKNOWN
   };
 
-public:
   Codec GetCodec() const    { return mCodec; }
   const char* GetCodecName() const
   {
     switch (mCodec) {
       case AMRNB: return "amrnb";
       case AMRWB: return "amrwb";
       case AAC:   return "aac";
       default:    return nullptr;
     }
   }
 
+  // Get a representation of this audio profile that can be returned
+  // to JS, possibly as a child member of another object.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aObject' is null;
+  //  - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
+  //  - NS_ERROR_FAILURE if construction of the JS object fails.
   nsresult GetJsObject(JSContext* aCx, JSObject** aObject);
 
 protected:
   uint32_t mCameraId;
   uint32_t mQualityIndex;
   Codec mCodec;
   int mBitrate;
   int mSamplerate;
@@ -125,16 +140,24 @@ public:
   {
     switch (mFileFormat) {
       case THREE_GPP: return VIDEO_3GPP;
       case MPEG4:     return VIDEO_MP4;
       default:        return nullptr;
     }
   }
 
+  // Get a representation of this recorder profile that can be returned
+  // to JS, possibly as a child member of another object.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aObject' is null;
+  //  - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
+  //  - NS_ERROR_FAILURE if construction of the JS object fails.
   virtual nsresult GetJsObject(JSContext* aCx, JSObject** aObject) = 0;
 
 protected:
   virtual ~RecorderProfile();
 
   uint32_t mCameraId;
   uint32_t mQualityIndex;
   const char* mName;
@@ -156,24 +179,34 @@ public:
   virtual ~RecorderProfileBase()
   {
     DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   }
 
   const RecorderVideoProfile* GetVideoProfile() const { return &mVideo; }
   const RecorderAudioProfile* GetAudioProfile() const { return &mAudio; }
 
-  nsresult GetJsObject(JSContext* aCx, JSObject** aObject)
+  // Get a representation of this recorder profile that can be returned
+  // to JS, possibly as a child member of another object.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aObject' is null;
+  //  - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
+  //  - NS_ERROR_NOT_AVAILABLE if the profile has no file format name;
+  //  - NS_ERROR_FAILURE if construction of the JS object fails.
+  nsresult
+  GetJsObject(JSContext* aCx, JSObject** aObject)
   {
     NS_ENSURE_TRUE(aObject, NS_ERROR_INVALID_ARG);
 
     const char* format = GetFileFormatName();
     if (!format) {
       // the profile must have a file format
-      return NS_ERROR_FAILURE;
+      return NS_ERROR_NOT_AVAILABLE;
     }
 
     JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
     if (!o) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     JS::Rooted<JSString*> s(aCx, JS_NewStringCopyZ(aCx, format));
@@ -211,16 +244,26 @@ class RecorderProfileManager
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RecorderProfileManager)
 
   virtual bool IsSupported(uint32_t aQualityIndex) const { return true; }
   virtual already_AddRefed<RecorderProfile> Get(uint32_t aQualityIndex) const = 0;
 
   uint32_t GetMaxQualityIndex() const { return mMaxQualityIndex; }
+
+  // Get a representation of all supported recorder profiles that can be
+  // returned to JS.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aObject' is null;
+  //  - NS_ERROR_OUT_OF_MEMORY if a new object could not be allocated;
+  //  - NS_ERROR_NOT_AVAILABLE if the profile has no file format name;
+  //  - NS_ERROR_FAILURE if construction of the JS object fails.
   nsresult GetJsObject(JSContext* aCx, JSObject** aObject) const;
 
 protected:
   RecorderProfileManager(uint32_t aCameraId);
   virtual ~RecorderProfileManager();
 
   uint32_t mCameraId;
   uint32_t mMaxQualityIndex;
--- a/dom/camera/DOMCameraCapabilities.cpp
+++ b/dom/camera/DOMCameraCapabilities.cpp
@@ -175,21 +175,20 @@ CameraCapabilities::Populate(ICameraCont
   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;
+      DOM_CAMERA_LOGE("Failed to JS-objectify profile manager (0x%x)\n", rv);
+    } else {
+      mRecorderProfiles = JS::ObjectValue(*o);
     }
-
-    mRecorderProfiles = JS::ObjectValue(*o);
   }
 
   // For now, always return success, since the presence or absence of capabilities
   // indicates whether or not they are supported.
   return NS_OK;
 }
 
 void
--- a/dom/camera/DOMCameraCapabilities.h
+++ b/dom/camera/DOMCameraCapabilities.h
@@ -36,18 +36,23 @@ public:
   // Because this header's filename doesn't match its C++ or DOM-facing
   // classname, we can't rely on the [Func="..."] WebIDL tag to implicitly
   // include the right header for us; instead we must explicitly include a
   // HasSupport() method in each header. We can get rid of these with the
   // Great Renaming proposed in bug 983177.
   static bool HasSupport(JSContext* aCx, JSObject* aGlobal);
 
   CameraCapabilities(nsPIDOMWindow* aWindow);
-  ~CameraCapabilities();
 
+  // Populate the camera capabilities interface from the specific
+  // camera control object.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aCameraControl' is null.
   nsresult Populate(ICameraControl* aCameraControl);
 
   nsPIDOMWindow* GetParentObject() const { return mWindow; }
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   void GetPreviewSizes(nsTArray<CameraSize>& aRetVal) const;
   void GetPictureSizes(nsTArray<CameraSize>& aRetVal) const;
@@ -65,16 +70,18 @@ public:
   uint32_t MaxDetectedFaces() const;
   double MinExposureCompensation() const;
   double MaxExposureCompensation() const;
   double ExposureCompensationStep() const;
   JS::Value RecorderProfiles(JSContext* cx) const;
   void GetIsoModes(nsTArray<nsString>& aRetVal) const;
 
 protected:
+  ~CameraCapabilities();
+
   nsresult TranslateToDictionary(ICameraControl* aCameraControl,
                                  uint32_t aKey, nsTArray<CameraSize>& aSizes);
 
   nsTArray<CameraSize> mPreviewSizes;
   nsTArray<CameraSize> mPictureSizes;
   nsTArray<CameraSize> mThumbnailSizes;
   nsTArray<CameraSize> mVideoSizes;
 
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -27,16 +27,17 @@
 #include "CameraCommon.h"
 #include "nsGlobalWindow.h"
 #include "CameraPreviewMediaStream.h"
 #include "mozilla/dom/CameraControlBinding.h"
 #include "mozilla/dom/CameraManagerBinding.h"
 #include "mozilla/dom/CameraCapabilitiesBinding.h"
 #include "DOMCameraDetectedFace.h"
 #include "mozilla/dom/BindingUtils.h"
+#include "nsPrintfCString.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMCameraControl)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMediaStream)
@@ -197,18 +198,17 @@ nsDOMCameraControl::nsDOMCameraControl(u
 
   // Register a listener for camera events.
   mListener = new DOMCameraControlListener(this, mInput);
   mCameraControl->AddListener(mListener);
 
   // Start the camera...
   nsresult rv = mCameraControl->Start(&config);
   if (NS_FAILED(rv)) {
-    mListener->OnError(DOMCameraControlListener::kInStartCamera,
-                       DOMCameraControlListener::kErrorApiFailed);
+    mListener->OnUserError(DOMCameraControlListener::kInStartCamera, rv);
   }
 }
 
 nsDOMCameraControl::~nsDOMCameraControl()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
 }
 
@@ -733,17 +733,17 @@ nsDOMCameraControl::StartRecording(const
       mAudioChannelAgent->StartPlaying(&canPlay);
     }
   }
 #endif
 
   nsCOMPtr<nsIDOMDOMRequest> request;
   mDSFileDescriptor = new DeviceStorageFileDescriptor();
   aRv = aStorageArea.CreateFileDescriptor(aFilename, mDSFileDescriptor.get(),
-                                         getter_AddRefs(request));
+                                          getter_AddRefs(request));
   if (aRv.Failed()) {
     return;
   }
 
   mOptions = aOptions;
   mStartRecordingOnSuccessCb = &aOnSuccess;
   mStartRecordingOnErrorCb = nullptr;
   if (aOnError.WasPassed()) {
@@ -753,29 +753,32 @@ nsDOMCameraControl::StartRecording(const
   nsCOMPtr<nsIDOMEventListener> listener = new StartRecordingHelper(this);
   request->AddEventListener(NS_LITERAL_STRING("success"), listener, false);
   request->AddEventListener(NS_LITERAL_STRING("error"), listener, false);
 }
 
 void
 nsDOMCameraControl::OnCreatedFileDescriptor(bool aSucceeded)
 {
+  nsresult rv = NS_ERROR_FAILURE;
+
   if (aSucceeded && mDSFileDescriptor->mFileDescriptor.IsValid()) {
     ICameraControl::StartRecordingOptions o;
 
     o.rotation = mOptions.mRotation;
     o.maxFileSizeBytes = mOptions.mMaxFileSizeBytes;
     o.maxVideoLengthMs = mOptions.mMaxVideoLengthMs;
     o.autoEnableLowLightTorch = mOptions.mAutoEnableLowLightTorch;
-    nsresult rv = mCameraControl->StartRecording(mDSFileDescriptor.get(), &o);
+    rv = mCameraControl->StartRecording(mDSFileDescriptor.get(), &o);
     if (NS_SUCCEEDED(rv)) {
       return;
     }
   }
-  OnError(CameraControlListener::kInStartRecording, NS_LITERAL_STRING("FAILURE"));
+
+  OnUserError(CameraControlListener::kInStartRecording, rv);
 
   if (mDSFileDescriptor->mFileDescriptor.IsValid()) {
     // An error occured. We need to manually close the file associated with the
     // FileDescriptor, and we shouldn't do this on the main thread, so we
     // use a little helper.
     nsRefPtr<CloseFileRunnable> closer =
       new CloseFileRunnable(mDSFileDescriptor->mFileDescriptor);
     closer->Dispatch();
@@ -799,32 +802,58 @@ nsDOMCameraControl::StopRecording(ErrorR
 
 void
 nsDOMCameraControl::ResumePreview(ErrorResult& aRv)
 {
   MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->StartPreview();
 }
 
+class ImmediateErrorCallback : public nsRunnable
+{
+public:
+  ImmediateErrorCallback(CameraErrorCallback* aCallback, const nsAString& aMessage)
+    : mCallback(aCallback)
+    , mMessage(aMessage)
+  { }
+  
+  NS_IMETHODIMP
+  Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    ErrorResult ignored;
+    mCallback->Call(mMessage, ignored);
+    return NS_OK;
+  }
+
+protected:
+  nsRefPtr<CameraErrorCallback> mCallback;
+  nsString mMessage;
+};
+
 void
 nsDOMCameraControl::SetConfiguration(const CameraConfiguration& aConfiguration,
                                      const Optional<OwningNonNull<CameraSetConfigurationCallback> >& aOnSuccess,
                                      const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
                                      ErrorResult& aRv)
 {
   MOZ_ASSERT(mCameraControl);
 
   nsRefPtr<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);
+      // There is already a call to TakePicture() in progress, abort this
+      // call and invoke the error callback (if one was passed in).
+      NS_DispatchToMainThread(new ImmediateErrorCallback(&aOnError.Value(),
+                              NS_LITERAL_STRING("TakePictureInProgress")));
+    } else {
+      // Only throw if no error callback was passed in.
+      aRv = NS_ERROR_FAILURE;
     }
-    aRv = NS_ERROR_FAILURE;
     return;
   }
 
   ICameraControl::Configuration config;
   config.mRecorderProfile = aConfiguration.mRecorderProfile;
   config.mPreviewSize.width = aConfiguration.mPreviewSize.mWidth;
   config.mPreviewSize.height = aConfiguration.mPreviewSize.mHeight;
   config.mMode = ICameraControl::kPictureMode;
@@ -839,55 +868,34 @@ nsDOMCameraControl::SetConfiguration(con
   mSetConfigurationOnErrorCb = nullptr;
   if (aOnError.WasPassed()) {
     mSetConfigurationOnErrorCb = &aOnError.Value();
   }
 
   aRv = mCameraControl->SetConfiguration(config);
 }
 
-class ImmediateErrorCallback : public nsRunnable
-{
-public:
-  ImmediateErrorCallback(CameraErrorCallback* aCallback, const nsAString& aMessage)
-    : mCallback(aCallback)
-    , mMessage(aMessage)
-  { }
-  
-  NS_IMETHODIMP
-  Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    ErrorResult ignored;
-    mCallback->Call(mMessage, ignored);
-    return NS_OK;
-  }
-
-protected:
-  nsRefPtr<CameraErrorCallback> mCallback;
-  nsString mMessage;
-};
-
-
 void
 nsDOMCameraControl::AutoFocus(CameraAutoFocusCallback& aOnSuccess,
                               const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
                               ErrorResult& aRv)
 {
   MOZ_ASSERT(mCameraControl);
 
   nsRefPtr<CameraAutoFocusCallback> cb = mAutoFocusOnSuccessCb;
   if (cb) {
     if (aOnError.WasPassed()) {
       // There is already a call to AutoFocus() in progress, abort this new one
       // and invoke the error callback (if one was passed in).
       NS_DispatchToMainThread(new ImmediateErrorCallback(&aOnError.Value(),
                               NS_LITERAL_STRING("AutoFocusAlreadyInProgress")));
+    } else {
+      // Only throw if no error callback was passed in.
+      aRv = NS_ERROR_FAILURE;
     }
-    aRv = NS_ERROR_FAILURE;
     return;
   }
 
   mAutoFocusOnSuccessCb = &aOnSuccess;
   mAutoFocusOnErrorCb = nullptr;
   if (aOnError.WasPassed()) {
     mAutoFocusOnErrorCb = &aOnError.Value();
   }
@@ -919,18 +927,20 @@ nsDOMCameraControl::TakePicture(const Ca
 
   nsRefPtr<CameraTakePictureCallback> cb = mTakePictureOnSuccessCb;
   if (cb) {
     if (aOnError.WasPassed()) {
       // There is already a call to TakePicture() in progress, abort this new
       // one and invoke the error callback (if one was passed in).
       NS_DispatchToMainThread(new ImmediateErrorCallback(&aOnError.Value(),
                               NS_LITERAL_STRING("TakePictureAlreadyInProgress")));
+    } else {
+      // Only throw if no error callback was passed in.
+      aRv = NS_ERROR_FAILURE;
     }
-    aRv = NS_ERROR_FAILURE;
     return;
   }
 
   {
     ICameraControlParameterSetAutoEnter batch(mCameraControl);
 
     // XXXmikeh - remove this: see bug 931155
     ICameraControl::Size s;
@@ -1265,20 +1275,18 @@ nsDOMCameraControl::OnTakePictureComplet
     return;
   }
 
   ErrorResult ignored;
   cb->Call(aPicture, ignored);
 }
 
 void
-nsDOMCameraControl::OnError(CameraControlListener::CameraErrorContext aContext, const nsAString& aError)
+nsDOMCameraControl::OnUserError(CameraControlListener::UserContext aContext, nsresult aError)
 {
-  DOM_CAMERA_LOGI("DOM OnError context=%d, error='%s'\n", aContext,
-    NS_LossyConvertUTF16toASCII(aError).get());
   MOZ_ASSERT(NS_IsMainThread());
 
   nsRefPtr<CameraErrorCallback> errorCb;
 
   switch (aContext) {
     case CameraControlListener::kInStartCamera:
       mGetCameraOnSuccessCb = nullptr;
       errorCb = mGetCameraOnErrorCb.forget();
@@ -1316,41 +1324,84 @@ nsDOMCameraControl::OnError(CameraContro
       return;
 
     case CameraControlListener::kInStartPreview:
       // This method doesn't have any callbacks, so all we can do is log the
       // failure. This only happens after the hardware has been released.
       NS_WARNING("Failed to (re)start preview");
       return;
 
-    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.
-        NS_WARNING("Camera service failed");
-        return;
-      }
-      if (aError.EqualsASCII("ErrorSetPictureSizeFailed") ||
-          aError.EqualsASCII("ErrorSetThumbnailSizeFailed")) {
-        // We currently don't handle attribute setter failure. Practically,
-        // this only ever happens if a setter is called after the hardware
-        // has gone away before an asynchronous set gets to happen, so we
-        // swallow these.
-        NS_WARNING("Failed to set either picture or thumbnail size");
-        return;
-      }
-      // fallthrough
+    case CameraControlListener::kInStopPreview:
+      // This method doesn't have any callbacks, so all we can do is log the
+      // failure. This only happens after the hardware has been released.
+      NS_WARNING("Failed to stop preview");
+      return;
+
+    case CameraControlListener::kInSetPictureSize:
+      // This method doesn't have any callbacks, so all we can do is log the
+      // failure. This only happens after the hardware has been released.
+      NS_WARNING("Failed to set picture size");
+      return;
+
+    case CameraControlListener::kInSetThumbnailSize:
+      // This method doesn't have any callbacks, so all we can do is log the
+      // failure. This only happens after the hardware has been released.
+      NS_WARNING("Failed to set thumbnail size");
+      return;
 
     default:
-      MOZ_ASSUME_UNREACHABLE("Error occurred in unanticipated camera state");
+      {
+        nsPrintfCString msg("Unhandled aContext=%u, aError=0x%x\n", aContext, aError);
+        NS_WARNING(msg.get());
+      }
+      MOZ_ASSUME_UNREACHABLE("Unhandled user error");
       return;
   }
 
   if (!errorCb) {
-    DOM_CAMERA_LOGW("DOM No error handler for error '%s' in context=%d\n",
-      NS_LossyConvertUTF16toASCII(aError).get(), aContext);
+    DOM_CAMERA_LOGW("DOM No error handler for aError=0x%x in aContext=%u\n",
+      aError, aContext);
     return;
   }
 
+  nsString error;
+  switch (aError) {
+    case NS_ERROR_INVALID_ARG:
+      error = NS_LITERAL_STRING("InvalidArgument");
+      break;
+
+    case NS_ERROR_NOT_AVAILABLE:
+      error = NS_LITERAL_STRING("NotAvailable");
+      break;
+
+    case NS_ERROR_NOT_IMPLEMENTED:
+      error = NS_LITERAL_STRING("NotImplemented");
+      break;
+
+    case NS_ERROR_NOT_INITIALIZED:
+      error = NS_LITERAL_STRING("HardwareClosed");
+      break;
+
+    case NS_ERROR_ALREADY_INITIALIZED:
+      error = NS_LITERAL_STRING("HardwareAlreadyOpen");
+      break;
+
+    case NS_ERROR_OUT_OF_MEMORY:
+      error = NS_LITERAL_STRING("OutOfMemory");
+      break;
+
+    default:
+      {
+        nsPrintfCString msg("Reporting aError=0x%x as generic\n", aError);
+        NS_WARNING(msg.get());
+      }
+      // fallthrough
+
+    case NS_ERROR_FAILURE:
+      error = NS_LITERAL_STRING("GeneralFailure");
+      break;
+  }
+
+  DOM_CAMERA_LOGI("DOM OnUserError aContext=%u, error='%s'\n", aContext,
+    NS_ConvertUTF16toUTF8(error).get());
   ErrorResult ignored;
-  errorCb->Call(aError, ignored);
+  errorCb->Call(error, ignored);
 }
-
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -162,17 +162,17 @@ protected:
   void OnTakePictureComplete(nsIDOMBlob* aPicture);
   void OnFacesDetected(const nsTArray<ICameraControl::Face>& aFaces);
 
   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);
+  void OnUserError(CameraControlListener::UserContext aContext, nsresult aError);
 
   bool IsWindowStillActive();
 
   nsresult NotifyRecordingStatusChange(const nsString& aMsg);
 
   nsRefPtr<ICameraControl> mCameraControl; // non-DOM camera control
 
   // An agent used to join audio channel service.
--- a/dom/camera/DOMCameraControlListener.cpp
+++ b/dom/camera/DOMCameraControlListener.cpp
@@ -349,58 +349,34 @@ DOMCameraControlListener::OnTakePictureC
     uint32_t mLength;
     nsString mMimeType;
   };
 
   NS_DispatchToMainThread(new Callback(mDOMCameraControl, aData, aLength, aMimeType));
 }
 
 void
-DOMCameraControlListener::OnError(CameraErrorContext aContext, CameraError aError)
+DOMCameraControlListener::OnUserError(UserContext aContext, nsresult aError)
 {
   class Callback : public DOMCallback
   {
   public:
     Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
-             CameraErrorContext aContext,
-             CameraError aError)
+             UserContext aContext,
+             nsresult aError)
       : DOMCallback(aDOMCameraControl)
       , mContext(aContext)
       , mError(aError)
     { }
 
     virtual void
     RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
     {
-      nsString error;
-
-      switch (mError) {
-        case kErrorServiceFailed:
-          error = NS_LITERAL_STRING("ErrorServiceFailed");
-          break;
-
-        case kErrorSetPictureSizeFailed:
-          error = NS_LITERAL_STRING("ErrorSetPictureSizeFailed");
-          break;
-
-        case kErrorSetThumbnailSizeFailed:
-          error = NS_LITERAL_STRING("ErrorSetThumbnailSizeFailed");
-          break;
-
-        case kErrorApiFailed:
-          // XXXmikeh legacy error placeholder
-          error = NS_LITERAL_STRING("FAILURE");
-          break;
-
-        default:
-          error = NS_LITERAL_STRING("ErrorUnknown");
-          break;
-      }
-      aDOMCameraControl->OnError(mContext, error);
+      aDOMCameraControl->OnUserError(mContext, mError);
     }
 
   protected:
-    CameraErrorContext mContext;
-    CameraError mError;
+    UserContext mContext;
+    nsresult mError;
   };
 
   NS_DispatchToMainThread(new Callback(mDOMCameraControl, aContext, aError));
 }
--- a/dom/camera/DOMCameraControlListener.h
+++ b/dom/camera/DOMCameraControlListener.h
@@ -24,17 +24,17 @@ public:
   virtual void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType) MOZ_OVERRIDE;
 
   virtual void OnHardwareStateChange(HardwareState aState) MOZ_OVERRIDE;
   virtual void OnPreviewStateChange(PreviewState aState) MOZ_OVERRIDE;
   virtual void OnRecorderStateChange(RecorderState aState, int32_t aStatus, int32_t aTrackNum) MOZ_OVERRIDE;
   virtual void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration) MOZ_OVERRIDE;
   virtual void OnShutter() MOZ_OVERRIDE;
   virtual bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight) MOZ_OVERRIDE;
-  virtual void OnError(CameraErrorContext aContext, CameraError aError) MOZ_OVERRIDE;
+  virtual void OnUserError(UserContext aContext, nsresult aError) MOZ_OVERRIDE;
 
 protected:
   virtual ~DOMCameraControlListener();
 
   nsMainThreadPtrHandle<nsDOMCameraControl> mDOMCameraControl;
   CameraPreviewMediaStream* mStream;
 
   class DOMCallback;
--- a/dom/camera/FallbackCameraControl.cpp
+++ b/dom/camera/FallbackCameraControl.cpp
@@ -19,61 +19,51 @@ namespace mozilla {
  * Fallback camera control subclass. Can be used as a template for the
  * definition of new camera support classes.
  */
 class FallbackCameraControl : public CameraControlImpl
 {
 public:
   FallbackCameraControl(uint32_t aCameraId) : CameraControlImpl(aCameraId) { }
 
-  void OnAutoFocusComplete(bool aSuccess);
-  void OnAutoFocusMoving(bool aIsMoving) { }
-  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) { }
+  virtual nsresult Set(uint32_t aKey, const nsAString& aValue) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual nsresult Get(uint32_t aKey, nsAString& aValue) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual nsresult Set(uint32_t aKey, double aValue) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual nsresult Get(uint32_t aKey, double& aValue) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual nsresult Set(uint32_t aKey, int32_t aValue) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual nsresult Get(uint32_t aKey, int32_t& aValue) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual nsresult Set(uint32_t aKey, int64_t aValue) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual nsresult Get(uint32_t aKey, int64_t& aValue) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual nsresult Set(uint32_t aKey, const Size& aValue) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual nsresult Get(uint32_t aKey, Size& aValue) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual nsresult Set(uint32_t aKey, const nsTArray<Region>& aRegions) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual nsresult Get(uint32_t aKey, nsTArray<Region>& aRegions) MOZ_OVERRIDE { 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; }
+  virtual nsresult SetLocation(const Position& aLocation) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
 
-  virtual nsresult SetLocation(const Position& aLocation) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, nsTArray<Size>& aSizes) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual nsresult Get(uint32_t aKey, nsTArray<nsString>& aValues) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
+  virtual nsresult Get(uint32_t aKey, nsTArray<double>& aValues) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
 
-  virtual nsresult 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 PushParameters() { return NS_ERROR_FAILURE; }
-  nsresult PullParameters() { return NS_ERROR_FAILURE; }
+  nsresult PushParameters() { return NS_ERROR_NOT_INITIALIZED; }
+  nsresult PullParameters() { return NS_ERROR_NOT_INITIALIZED; }
 
 protected:
   ~FallbackCameraControl();
 
-  virtual nsresult StartPreviewImpl() MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
-  virtual nsresult StopPreviewImpl() MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
-  virtual nsresult AutoFocusImpl() MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
-  virtual nsresult StartFaceDetectionImpl() MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
-  virtual nsresult StopFaceDetectionImpl() MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
-  virtual nsresult TakePictureImpl() MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult StartPreviewImpl() { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult StopPreviewImpl() { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult AutoFocusImpl() { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult StartFaceDetectionImpl() { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult StopFaceDetectionImpl() { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult TakePictureImpl() { return NS_ERROR_NOT_INITIALIZED; }
   virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
                                       const StartRecordingOptions* aOptions = nullptr) MOZ_OVERRIDE
-                                        { return NS_ERROR_FAILURE; }
-  virtual nsresult StopRecordingImpl() MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
-  virtual nsresult ResumeContinuousFocusImpl() MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
-  virtual nsresult PushParametersImpl() MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
-  virtual nsresult PullParametersImpl() MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+                                        { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult StopRecordingImpl() { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult PushParametersImpl() { return NS_ERROR_NOT_INITIALIZED; }
+  virtual nsresult PullParametersImpl() { return NS_ERROR_NOT_INITIALIZED; }
   virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl() MOZ_OVERRIDE { return nullptr; }
 
 private:
   FallbackCameraControl(const FallbackCameraControl&) MOZ_DELETE;
   FallbackCameraControl& operator=(const FallbackCameraControl&) MOZ_DELETE;
 };
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -48,18 +48,19 @@ using namespace mozilla;
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 using namespace mozilla::ipc;
 using namespace android;
 
 #define RETURN_IF_NO_CAMERA_HW()                                          \
   do {                                                                    \
     if (!mCameraHw.get()) {                                               \
+      NS_WARNING("Camera hardware is not initialized");                   \
       DOM_CAMERA_LOGE("%s:%d : mCameraHw is null\n", __func__, __LINE__); \
-      return NS_ERROR_NOT_AVAILABLE;                                      \
+      return NS_ERROR_NOT_INITIALIZED;                                    \
     }                                                                     \
   } while(0)
 
 // Construct nsGonkCameraControl on the main thread.
 nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId)
   : CameraControlImpl(aCameraId)
   , mLastPictureSize({0, 0})
   , mLastThumbnailSize({0, 0})
@@ -102,17 +103,17 @@ nsGonkCameraControl::StartImpl(const Con
    * 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.
    */
   MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
 
   nsresult rv = Initialize();
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
+    return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (aInitialConfig) {
     rv = SetConfigurationInternal(*aInitialConfig);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       // The initial configuration failed, close up the hardware
       StopImpl();
       return rv;
@@ -121,20 +122,25 @@ nsGonkCameraControl::StartImpl(const Con
 
   OnHardwareStateChange(CameraControlListener::kHardwareOpen);
   return StartPreviewImpl();
 }
 
 nsresult
 nsGonkCameraControl::Initialize()
 {
+  if (mCameraHw.get()) {
+    DOM_CAMERA_LOGI("Camera %d already connected (this=%p)\n", mCameraId, this);
+    return NS_ERROR_ALREADY_INITIALIZED;
+  }
+
   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;
+    return NS_ERROR_NOT_INITIALIZED;
   }
 
   DOM_CAMERA_LOGI("Initializing camera %d (this=%p, mCameraHw=%p)\n", mCameraId, this, mCameraHw.get());
 
   // Initialize our camera configuration database.
   PullParametersImpl();
 
   // Set preferred preview frame format.
@@ -691,18 +697,17 @@ nsGonkCameraControl::SetThumbnailSize(co
     }
     ~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);
+        mCameraControl->OnUserError(CameraControlListener::kInSetThumbnailSize, rv);
       }
       return NS_OK;
     }
 
   protected:
     nsRefPtr<nsGonkCameraControl> mCameraControl;
     Size mSize;
   };
@@ -827,18 +832,17 @@ nsGonkCameraControl::SetPictureSize(cons
     }
     ~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);
+        mCameraControl->OnUserError(CameraControlListener::kInSetPictureSize, rv);
       }
       return NS_OK;
     }
 
   protected:
     nsRefPtr<nsGonkCameraControl> mCameraControl;
     Size mSize;
   };
@@ -946,17 +950,19 @@ nsGonkCameraControl::StartRecordingImpl(
   /**
    * 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.
    */
-  NS_ENSURE_TRUE(aFileDescriptor, NS_ERROR_FAILURE);
+  if (NS_WARN_IF(!aFileDescriptor)) {
+    return NS_ERROR_INVALID_ARG;
+  }
   nsAutoString fullPath;
   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");
@@ -1201,18 +1207,18 @@ nsGonkCameraControl::OnTakePictureComple
   }
 
   DOM_CAMERA_LOGI("nsGonkCameraControl::OnTakePictureComplete() done\n");
 }
 
 void
 nsGonkCameraControl::OnTakePictureError()
 {
-  CameraControlImpl::OnError(CameraControlListener::kInTakePicture,
-                             CameraControlListener::kErrorApiFailed);
+  CameraControlImpl::OnUserError(CameraControlListener::kInTakePicture,
+                                 NS_ERROR_FAILURE);
 }
 
 nsresult
 nsGonkCameraControl::SetPreviewSize(const Size& aSize)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
 
   nsTArray<Size> previewSizes;
@@ -1637,25 +1643,25 @@ nsGonkCameraControl::OnNewPreviewFrame(l
                           mCurrentConfiguration.mPreviewSize.height);
   videoImage->SetData(data);
 
   OnNewPreviewFrame(frame, mCurrentConfiguration.mPreviewSize.width,
                     mCurrentConfiguration.mPreviewSize.height);
 }
 
 void
-nsGonkCameraControl::OnError(CameraControlListener::CameraErrorContext aWhere,
-                             CameraControlListener::CameraError aError)
+nsGonkCameraControl::OnSystemError(CameraControlListener::SystemContext aWhere,
+                                   nsresult aError)
 {
-  if (aError == CameraControlListener::kErrorServiceFailed) {
+  if (aWhere == CameraControlListener::kSystemService) {
     OnPreviewStateChange(CameraControlListener::kPreviewStopped);
     OnHardwareStateChange(CameraControlListener::kHardwareClosed);
   }
 
-  CameraControlImpl::OnError(aWhere, aError);
+  CameraControlImpl::OnSystemError(aWhere, aError);
 }
 
 // Gonk callback handlers.
 namespace mozilla {
 
 void
 OnTakePictureComplete(nsGonkCameraControl* gc, uint8_t* aData, uint32_t aLength)
 {
@@ -1700,21 +1706,22 @@ OnShutter(nsGonkCameraControl* gc)
 
 void
 OnClosed(nsGonkCameraControl* gc)
 {
   gc->OnClosed();
 }
 
 void
-OnError(nsGonkCameraControl* gc, CameraControlListener::CameraError aError,
-        int32_t aArg1, int32_t aArg2)
+OnSystemError(nsGonkCameraControl* gc,
+              CameraControlListener::SystemContext aWhere,
+              int32_t aArg1, int32_t aArg2)
 {
 #ifdef PR_LOGGING
-  DOM_CAMERA_LOGE("OnError : aError=%d, aArg1=%d, aArg2=%d\n", aError, aArg1, aArg2);
+  DOM_CAMERA_LOGE("OnSystemError : aWhere=%d, aArg1=%d, aArg2=%d\n", aWhere, aArg1, aArg2);
 #else
   unused << aArg1;
   unused << aArg2;
 #endif
-  gc->OnError(CameraControlListener::kInUnspecified, aError);
+  gc->OnSystemError(aWhere, NS_ERROR_FAILURE);
 }
 
 } // namespace mozilla
--- a/dom/camera/GonkCameraControl.h
+++ b/dom/camera/GonkCameraControl.h
@@ -49,19 +49,19 @@ public:
   nsGonkCameraControl(uint32_t aCameraId);
 
   void OnAutoFocusComplete(bool aSuccess);
   void OnFacesDetected(camera_frame_metadata_t* aMetaData);
   void OnTakePictureComplete(uint8_t* aData, uint32_t aLength);
   void OnTakePictureError();
   void OnNewPreviewFrame(layers::TextureClient* aBuffer);
   void OnRecorderEvent(int msg, int ext1, int ext2);
-  void OnError(CameraControlListener::CameraErrorContext aWhere,
-               CameraControlListener::CameraError aError);
-
+  void OnSystemError(CameraControlListener::SystemContext aWhere, nsresult aError);
+ 
+  // See ICameraControl.h for getter/setter return values.
   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;
@@ -82,32 +82,33 @@ public:
 protected:
   ~nsGonkCameraControl();
 
   using CameraControlImpl::OnNewPreviewFrame;
   using CameraControlImpl::OnAutoFocusComplete;
   using CameraControlImpl::OnFacesDetected;
   using CameraControlImpl::OnTakePictureComplete;
   using CameraControlImpl::OnConfigurationChange;
-  using CameraControlImpl::OnError;
+  using CameraControlImpl::OnUserError;
 
   virtual void BeginBatchParameterSet() MOZ_OVERRIDE;
   virtual void EndBatchParameterSet() MOZ_OVERRIDE;
 
-  virtual nsresult StartImpl(const Configuration* aInitialConfig = nullptr) MOZ_OVERRIDE;
-  virtual nsresult StopImpl() MOZ_OVERRIDE;
   nsresult Initialize();
 
-  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);
 
+  // See CameraControlImpl.h for these methods' return values.
+  virtual nsresult StartImpl(const Configuration* aInitialConfig = nullptr) MOZ_OVERRIDE;
+  virtual nsresult SetConfigurationImpl(const Configuration& aConfig) MOZ_OVERRIDE;
+  virtual nsresult StopImpl() MOZ_OVERRIDE;
   virtual nsresult StartPreviewImpl() MOZ_OVERRIDE;
   virtual nsresult StopPreviewImpl() MOZ_OVERRIDE;
   virtual nsresult AutoFocusImpl() MOZ_OVERRIDE;
   virtual nsresult StartFaceDetectionImpl() MOZ_OVERRIDE;
   virtual nsresult StopFaceDetectionImpl() MOZ_OVERRIDE;
   virtual nsresult TakePictureImpl() MOZ_OVERRIDE;
   virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
                                       const StartRecordingOptions* aOptions = nullptr) MOZ_OVERRIDE;
@@ -174,14 +175,15 @@ private:
 void OnTakePictureComplete(nsGonkCameraControl* gc, uint8_t* aData, uint32_t aLength);
 void OnTakePictureError(nsGonkCameraControl* gc);
 void OnAutoFocusComplete(nsGonkCameraControl* gc, bool aSuccess);
 void OnAutoFocusMoving(nsGonkCameraControl* gc, bool aIsMoving);
 void OnFacesDetected(nsGonkCameraControl* gc, camera_frame_metadata_t* aMetaData);
 void OnNewPreviewFrame(nsGonkCameraControl* gc, layers::TextureClient* aBuffer);
 void OnShutter(nsGonkCameraControl* gc);
 void OnClosed(nsGonkCameraControl* gc);
-void OnError(nsGonkCameraControl* gc, CameraControlListener::CameraError aError,
-             int32_t aArg1, int32_t aArg2);
+void OnSystemError(nsGonkCameraControl* gc,
+                   CameraControlListener::SystemContext aWhere,
+                   int32_t aArg1, int32_t aArg2);
 
 } // namespace mozilla
 
 #endif // DOM_CAMERA_GONKCAMERACONTROL_H
--- a/dom/camera/GonkCameraHwMgr.cpp
+++ b/dom/camera/GonkCameraHwMgr.cpp
@@ -108,17 +108,17 @@ GonkCameraHardware::notify(int32_t aMsgT
       break;
 #endif
 
     case CAMERA_MSG_SHUTTER:
       OnShutter(mTarget);
       break;
 
     case CAMERA_MSG_ERROR:
-      OnError(mTarget, CameraControlListener::kErrorServiceFailed, ext1, ext2);
+      OnSystemError(mTarget, CameraControlListener::kSystemService, ext1, ext2);
       break;
 
     default:
       DOM_CAMERA_LOGE("Unhandled notify callback event %d\n", aMsgType);
       break;
   }
 }
 
@@ -143,17 +143,17 @@ nsresult
 GonkCameraHardware::Init()
 {
   DOM_CAMERA_LOGT("%s: this=%p\n", __func__, (void* )this);
 
   CameraInfo info;
   int rv = Camera::getCameraInfo(mCameraId, &info);
   if (rv != 0) {
     DOM_CAMERA_LOGE("%s: failed to get CameraInfo mCameraId %d\n", __func__, mCameraId);
-    return NS_ERROR_FAILURE;
+    return NS_ERROR_NOT_INITIALIZED;
    }
 
   mRawSensorOrientation = info.orientation;
   mSensorOrientation = mRawSensorOrientation;
 
   /**
    * Non-V4L2-based camera driver adds extra offset onto picture orientation
    * set by gecko, so we have to adjust it back.
--- a/dom/camera/GonkCameraHwMgr.h
+++ b/dom/camera/GonkCameraHwMgr.h
@@ -38,16 +38,22 @@ namespace mozilla {
 namespace android {
 
 class GonkCameraHardware : public GonkNativeWindowNewFrameCallback
                          , public CameraListener
 {
 protected:
   GonkCameraHardware(mozilla::nsGonkCameraControl* aTarget, uint32_t aCameraId, const sp<Camera>& aCamera);
   virtual ~GonkCameraHardware();
+
+  // Initialize the AOSP camera interface.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_NOT_INITIALIZED if the interface could not be initialized.
   virtual nsresult Init();
 
 public:
   static sp<GonkCameraHardware> Connect(mozilla::nsGonkCameraControl* aTarget, uint32_t aCameraId);
   virtual void Close();
 
   // derived from GonkNativeWindowNewFrameCallback
   virtual void OnNewFrame() MOZ_OVERRIDE;
--- a/dom/camera/GonkCameraManager.cpp
+++ b/dom/camera/GonkCameraManager.cpp
@@ -15,16 +15,18 @@
  */
 
 #include "ICameraControl.h"
 
 #include <camera/Camera.h>
 
 #include "CameraCommon.h"
 #include "GonkCameraControl.h"
+#include "mozilla/Preferences.h"
+#include "TestGonkCameraControl.h"
 
 using namespace mozilla;
 
 // From ICameraControl, gonk-specific management functions
 nsresult
 ICameraControl::GetNumberOfCameras(int32_t& aDeviceCount)
 {
   aDeviceCount = android::Camera::getNumberOfCameras();
@@ -35,17 +37,17 @@ nsresult
 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 (deviceNum < 0 || deviceNum > count) {
     DOM_CAMERA_LOGE("GetCameraName : invalid device number (%u)\n", aDeviceNum);
-    return NS_ERROR_NOT_AVAILABLE;
+    return NS_ERROR_INVALID_ARG;
   }
 
   android::CameraInfo info;
   int rv = android::Camera::getCameraInfo(deviceNum, &info);
   if (rv != 0) {
     DOM_CAMERA_LOGE("GetCameraName : get_camera_info(%d) failed: %d\n", deviceNum, rv);
     return NS_ERROR_NOT_AVAILABLE;
   }
@@ -68,16 +70,17 @@ ICameraControl::GetCameraName(uint32_t a
 }
 
 nsresult
 ICameraControl::GetListOfCameras(nsTArray<nsString>& aList)
 {
   int32_t count = android::Camera::getNumberOfCameras();
   DOM_CAMERA_LOGI("getListOfCameras : getNumberOfCameras() returned %d\n", count);
   if (count <= 0) {
+    aList.Clear();
     return NS_OK;
   }
 
   // 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;
@@ -109,15 +112,24 @@ ICameraControl::GetListOfCameras(nsTArra
 
   if (!gotBack) {
     aList.RemoveElementAt(0);
   }
 
   return NS_OK;
 }
 
+static const char* sTestModeEnabled = "camera.control.test.enabled";
+
 // implementation-specific camera factory
 already_AddRefed<ICameraControl>
 ICameraControl::Create(uint32_t aCameraId)
 {
-  nsRefPtr<nsGonkCameraControl> control = new nsGonkCameraControl(aCameraId);
+  const nsAdoptingCString& test = Preferences::GetCString(sTestModeEnabled);
+  nsRefPtr<nsGonkCameraControl> control;
+  if (test.EqualsASCII("control")) {
+    NS_WARNING("Using test CameraControl layer");
+    control = new TestGonkCameraControl(aCameraId);
+  } else {
+    control = new nsGonkCameraControl(aCameraId);
+  }
   return control.forget();
 }
--- a/dom/camera/GonkCameraParameters.cpp
+++ b/dom/camera/GonkCameraParameters.cpp
@@ -134,23 +134,24 @@ GonkCameraParameters::Parameters::GetTex
 
 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");
+    MOZ_CRASH("Out of memory getting new PRRWLock");
   }
 }
 
 GonkCameraParameters::~GonkCameraParameters()
 {
   MOZ_COUNT_DTOR(GonkCameraParameters);
+  MOZ_ASSERT(mLock, "mLock missing in ~GonkCameraParameters()");
   if (mLock) {
     PR_DestroyRWLock(mLock);
     mLock = nullptr;
   }
 }
 
 nsresult
 GonkCameraParameters::MapIsoToGonk(const nsAString& aIso, nsACString& aIsoOut)
@@ -158,17 +159,17 @@ GonkCameraParameters::MapIsoToGonk(const
   if (aIso.EqualsASCII("hjr")) {
     aIsoOut = "ISO_HJR";
   } else if (aIso.EqualsASCII("auto")) {
     aIsoOut = "auto";
   } else {
     nsAutoCString v = NS_LossyConvertUTF16toASCII(aIso);
     unsigned int iso;
     if (sscanf(v.get(), "%u", &iso) != 1) {
-      return NS_ERROR_FAILURE;
+      return NS_ERROR_INVALID_ARG;
     }
     aIsoOut = nsPrintfCString("ISO%u", iso);
   }
 
   return NS_OK;
 }
 
 nsresult
@@ -176,17 +177,17 @@ GonkCameraParameters::MapIsoFromGonk(con
 {
   if (strcmp(aIso, "ISO_HJR") == 0) {
     aIsoOut.AssignASCII("hjr");
   } else if (strcmp(aIso, "auto") == 0) {
     aIsoOut.AssignASCII("auto");
   } else {
     unsigned int iso;
     if (sscanf(aIso, "ISO%u", &iso) != 1) {
-      return NS_ERROR_FAILURE;
+      return NS_ERROR_INVALID_ARG;
     }
     aIsoOut.AppendInt(iso);
   }
 
   return NS_OK;
 }
 
 // Any members that need to be initialized on the first parameter pull
@@ -328,39 +329,49 @@ GonkCameraParameters::GetTranslated(uint
 {
   nsresult rv;
 
   if (aKey == CAMERA_PARAM_THUMBNAILSIZE) {
     int width;
     int height;
 
     rv = GetImpl(Parameters::KEY_JPEG_THUMBNAIL_WIDTH, width);
-    if (NS_FAILED(rv) || width < 0) {
-      return NS_ERROR_FAILURE;
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    if (width < 0) {
+      return NS_ERROR_NOT_AVAILABLE;
     }
     rv = GetImpl(Parameters::KEY_JPEG_THUMBNAIL_HEIGHT, height);
-    if (NS_FAILED(rv) || height < 0) {
-      return NS_ERROR_FAILURE;
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    if (height < 0) {
+      return NS_ERROR_NOT_AVAILABLE;
     }
 
     aSize.width = static_cast<uint32_t>(width);
     aSize.height = static_cast<uint32_t>(height);
     return NS_OK;
   }
 
   const char* value;
   rv = GetImpl(aKey, value);
-  if (NS_FAILED(rv) || !value || *value == '\0') {
-    DOM_CAMERA_LOGW("Camera parameter aKey=%d not available (0x%x)\n", aKey, rv);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (!value || *value == '\0') {
+    DOM_CAMERA_LOGW("Camera parameter aKey=%d not available\n", aKey);
     return NS_ERROR_NOT_AVAILABLE;
   }
   if (sscanf(value, "%ux%u", &aSize.width, &aSize.height) != 2) {
     DOM_CAMERA_LOGE("Camera parameter aKey=%d size tuple '%s' is invalid\n", aKey, value);
-    return NS_ERROR_FAILURE;
+    return NS_ERROR_NOT_AVAILABLE;
   }
+
   return NS_OK;
 }
 
 // Handle arrays of ICameraControl::Regions
 nsresult
 GonkCameraParameters::SetTranslated(uint32_t aKey, const nsTArray<ICameraControl::Region>& aRegions)
 {
   uint32_t length = aRegions.Length();
@@ -385,18 +396,22 @@ GonkCameraParameters::SetTranslated(uint
 
 nsresult
 GonkCameraParameters::GetTranslated(uint32_t aKey, nsTArray<ICameraControl::Region>& aRegions)
 {
   aRegions.Clear();
 
   const char* value;
   nsresult rv = GetImpl(aKey, value);
-  if (NS_FAILED(rv) || !value || *value == '\0') {
-    return NS_ERROR_FAILURE;
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (!value || *value == '\0') {
+    DOM_CAMERA_LOGW("Camera parameter aKey=%d not available\n", aKey);
+    return NS_ERROR_NOT_AVAILABLE;
   }
 
   const char* p = value;
   uint32_t count = 1;
 
   // count the number of regions in the string
   while ((p = strstr(p, "),("))) {
     ++count;
@@ -406,19 +421,19 @@ GonkCameraParameters::GetTranslated(uint
   aRegions.SetCapacity(count);
   ICameraControl::Region* 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);
+      DOM_CAMERA_LOGE("Camera parameter aKey=%d region tuple has bad format: '%s'\n", aKey, p);
       aRegions.Clear();
-      return NS_ERROR_FAILURE;
+      return NS_ERROR_NOT_AVAILABLE;
     }
   }
 
   return NS_OK;
 }
 
 // Handle ICameraControl::Positions
 nsresult
@@ -438,16 +453,17 @@ GonkCameraParameters::SetTranslated(uint
   if (!isnan(aPosition.altitude)) {
     DOM_CAMERA_LOGI("setting picture altitude to %lf\n", aPosition.altitude);
     SetImpl(Parameters::KEY_GPS_ALTITUDE, nsPrintfCString("%lf", aPosition.altitude).get());
   }
   if (!isnan(aPosition.timestamp)) {
     DOM_CAMERA_LOGI("setting picture timestamp to %lf\n", aPosition.timestamp);
     SetImpl(Parameters::KEY_GPS_TIMESTAMP, nsPrintfCString("%lf", aPosition.timestamp).get());
   }
+
   return NS_OK;
 }
 
 // Handle int64_ts
 nsresult
 GonkCameraParameters::SetTranslated(uint32_t aKey, const int64_t& aValue)
 {
   switch (aKey) {
@@ -677,17 +693,17 @@ nsresult
 GonkCameraParameters::GetTranslated(uint32_t aKey, uint32_t& aValue)
 {
   int val;
   nsresult rv = GetImpl(aKey, val);
   if (NS_FAILED(rv)) {
     return rv;
   }
   if (val < 0) {
-    return NS_ERROR_FAILURE;
+    return NS_ERROR_NOT_AVAILABLE;
   }
 
   aValue = val;
   return NS_OK;
 }
 
 // Handle bools
 nsresult
@@ -704,18 +720,18 @@ GonkCameraParameters::GetTranslated(uint
 
 nsresult
 ParseItem(const char* aStart, const char* aEnd, ICameraControl::Size* aItem)
 {
   if (sscanf(aStart, "%ux%u", &aItem->width, &aItem->height) == 2) {
     return NS_OK;
   }
 
-  DOM_CAMERA_LOGE("Size tuple has bad format: '%s'\n", __func__, __LINE__, aStart);
-  return NS_ERROR_FAILURE;
+  DOM_CAMERA_LOGE("Size tuple has bad format: '%s'\n", aStart);
+  return NS_ERROR_NOT_AVAILABLE;
 }
 
 nsresult
 ParseItem(const char* aStart, const char* aEnd, nsAString* aItem)
 {
   if (aEnd) {
     aItem->AssignASCII(aStart, aEnd - aStart);
   } else {
@@ -737,27 +753,27 @@ ParseItem(const char* aStart, const char
 
 nsresult
 ParseItem(const char* aStart, const char* aEnd, double* aItem)
 {
   if (sscanf(aStart, "%lf", aItem) == 1) {
     return NS_OK;
   }
 
-  return NS_ERROR_FAILURE;
+  return NS_ERROR_NOT_AVAILABLE;
 }
 
 nsresult
 ParseItem(const char* aStart, const char* aEnd, int* aItem)
 {
   if (sscanf(aStart, "%d", aItem) == 1) {
     return NS_OK;
   }
 
-  return NS_ERROR_FAILURE;
+  return NS_ERROR_NOT_AVAILABLE;
 }
 
 template<class T> nsresult
 GonkCameraParameters::GetListAsArray(uint32_t aKey, nsTArray<T>& aArray)
 {
   const char* p;
   nsresult rv = GetImpl(aKey, p);
   if (NS_FAILED(rv)) {
@@ -774,21 +790,18 @@ GonkCameraParameters::GetListAsArray(uin
   if (*p == '\0') {
     DOM_CAMERA_LOGI("Camera parameter %d not available (value is empty string)\n", aKey);
     return NS_OK;
   }
 
   const char* comma;
 
   while (p) {
+    // nsTArray::AppendElement() is infallible
     T* v = aArray.AppendElement();
-    if (!v) {
-      aArray.Clear();
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
     comma = strchr(p, ',');
     if (comma != p) {
       rv = ParseItem(p, comma, v);
       if (NS_FAILED(rv)) {
         aArray.Clear();
         return rv;
       }
       p = comma;
--- a/dom/camera/GonkCameraParameters.h
+++ b/dom/camera/GonkCameraParameters.h
@@ -32,16 +32,19 @@ class GonkCameraParameters
 public:
   GonkCameraParameters();
   virtual ~GonkCameraParameters();
 
   // IMPORTANT: This class is read and written by multiple threads --
   // ALL public methods must hold mLock, for either reading or writing,
   // for the life of their operation. Not doing so was the cause of
   // bug 928856, which was -painful- to track down.
+  //
+  // Return values:
+  //  - see return values for GetTranslated() and SetTranslated() below.
   template<class T> nsresult
   Set(uint32_t aKey, const T& aValue)
   {
     RwLockAutoEnterWrite lock(mLock);
     nsresult rv = SetTranslated(aKey, aValue);
     mDirty = mDirty || NS_SUCCEEDED(rv);
     return rv;
   }
@@ -119,31 +122,35 @@ protected:
     static const char* GetTextKey(uint32_t aKey);
   };
 
   Parameters mParams;
 
   // The *Impl() templates handle converting the parameter keys from
   // their enum values to string types, if necessary. These are the
   // bottom layer accessors to mParams.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_NOT_IMPLEMENTED if the numeric 'aKey' value is invalid.
   template<typename T> nsresult
   SetImpl(uint32_t aKey, const T& aValue)
   {
     const char* key = Parameters::GetTextKey(aKey);
-    NS_ENSURE_TRUE(key, NS_ERROR_NOT_AVAILABLE);
+    NS_ENSURE_TRUE(key, NS_ERROR_NOT_IMPLEMENTED);
 
     mParams.set(key, aValue);
     return NS_OK;
   }
 
   template<typename T> nsresult
   GetImpl(uint32_t aKey, T& aValue)
   {
     const char* key = Parameters::GetTextKey(aKey);
-    NS_ENSURE_TRUE(key, NS_ERROR_NOT_AVAILABLE);
+    NS_ENSURE_TRUE(key, NS_ERROR_NOT_IMPLEMENTED);
 
     mParams.get(key, aValue);
     return NS_OK;
   }
 
   template<class T> nsresult
   SetImpl(const char* aKey, const T& aValue)
   {
@@ -157,16 +164,25 @@ protected:
     mParams.get(aKey, aValue);
     return NS_OK;
   }
 
   // The *Translated() functions allow us to handle special cases;
   // for example, where the thumbnail size setting is exposed as an
   // ICameraControl::Size object, but is handled by the AOSP layer
   // as two separate parameters.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aValue' contains an invalid value;
+  //  - NS_ERROR_NOT_IMPLEMENTED if 'aKey' is invalid;
+  //  - NS_ERROR_NOT_AVAILABLE if the getter fails to retrieve a valid value,
+  //      or if a setter fails because it requires one or more values that
+  //      could not be retrieved;
+  //  - NS_ERROR_FAILURE on unexpected internal failures.
   nsresult SetTranslated(uint32_t aKey, const nsAString& aValue);
   nsresult GetTranslated(uint32_t aKey, nsAString& aValue);
   nsresult SetTranslated(uint32_t aKey, const ICameraControl::Size& aSize);
   nsresult GetTranslated(uint32_t aKey, ICameraControl::Size& aSize);
   nsresult GetTranslated(uint32_t aKey, nsTArray<ICameraControl::Size>& aSizes);
   nsresult SetTranslated(uint32_t aKey, const nsTArray<ICameraControl::Region>& aRegions);
   nsresult GetTranslated(uint32_t aKey, nsTArray<ICameraControl::Region>& aRegions);
   nsresult SetTranslated(uint32_t aKey, const ICameraControl::Position& aPosition);
@@ -178,18 +194,35 @@ protected:
   nsresult GetTranslated(uint32_t aKey, int& aValue);
   nsresult SetTranslated(uint32_t aKey, const uint32_t& aValue);
   nsresult GetTranslated(uint32_t aKey, uint32_t& aValue);
   nsresult SetTranslated(uint32_t aKey, const bool& aValue);
   nsresult GetTranslated(uint32_t aKey, bool& aValue);
   nsresult GetTranslated(uint32_t aKey, nsTArray<nsString>& aValues);
   nsresult GetTranslated(uint32_t aKey, nsTArray<double>& aValues);
 
+  // Converts a string of multiple, comma-separated values into an array
+  // of the appropriate type.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_NOT_IMPLEMENTED if 'aKey' is invalid;
+  //  - NS_ERROR_NOT_AVAILABLE if a valid value could not be returned.
   template<class T> nsresult GetListAsArray(uint32_t aKey, nsTArray<T>& aArray);
+
+  // Converts ISO values (e.g., "auto", "hjr", "100", "200", etc.) to and from
+  // values understood by Gonk (e.g., "auto", "ISO_HJR", "ISO100", "ISO200",
+  // respectively).
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if the 'aIso' argument is not a valid form.
   nsresult MapIsoToGonk(const nsAString& aIso, nsACString& aIsoOut);
   nsresult MapIsoFromGonk(const char* aIso, nsAString& aIsoOut);
 
+  // Call once to initialize local cached values used in translating other
+  // arguments between Gecko and Gonk. Always returns NS_OK.
   nsresult Initialize();
 };
 
 } // namespace mozilla
 
 #endif // DOM_CAMERA_GONKCAMERAPARAMETERS_H
--- a/dom/camera/GonkRecorderProfiles.cpp
+++ b/dom/camera/GonkRecorderProfiles.cpp
@@ -88,16 +88,21 @@ GonkRecorderProfile::GonkRecorderProfile
 GonkRecorderProfile::~GonkRecorderProfile()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
 }
 
 nsresult
 GonkRecorderProfile::ConfigureRecorder(GonkRecorder* aRecorder)
 {
+  if (!aRecorder) {
+    DOM_CAMERA_LOGW("ConfigureRecorder() called with null aRecorder\n");
+    return NS_ERROR_INVALID_ARG;
+  }
+
   static const size_t SIZE = 256;
   char buffer[SIZE];
 
   // set all the params
   CHECK_SETARG(aRecorder->setAudioSource(AUDIO_SOURCE_CAMCORDER));
   CHECK_SETARG(aRecorder->setVideoSource(VIDEO_SOURCE_CAMERA));
   CHECK_SETARG(aRecorder->setOutputFormat(GetOutputFormat()));
   CHECK_SETARG(aRecorder->setVideoFrameRate(mVideo.GetFramerate()));
--- a/dom/camera/GonkRecorderProfiles.h
+++ b/dom/camera/GonkRecorderProfiles.h
@@ -9,17 +9,17 @@
 #include "CameraRecorderProfiles.h"
 #include "ICameraControl.h"
 
 #ifndef CHECK_SETARG
 #define CHECK_SETARG(x)                 \
   do {                                  \
     if (x) {                            \
       DOM_CAMERA_LOGE(#x " failed\n");  \
-      return NS_ERROR_INVALID_ARG;      \
+      return NS_ERROR_NOT_AVAILABLE;    \
     }                                   \
   } while(0)
 #endif
 
 
 namespace android {
 class GonkRecorder;
 };
@@ -61,16 +61,23 @@ class GonkRecorderProfile : public Recor
 {
 public:
   GonkRecorderProfile(uint32_t aCameraId, uint32_t aQualityIndex);
 
   GonkRecorderAudioProfile* GetGonkAudioProfile() { return &mAudio; }
   GonkRecorderVideoProfile* GetGonkVideoProfile() { return &mVideo; }
 
   android::output_format GetOutputFormat() const { return mPlatformOutputFormat; }
+
+  // Configures the specified recorder using this profile.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aRecorder' is null;
+  //  - NS_ERROR_NOT_AVAILABLE if the recorder rejected this profile.
   nsresult ConfigureRecorder(android::GonkRecorder* aRecorder);
 
 protected:
   virtual ~GonkRecorderProfile();
 
   android::output_format mPlatformOutputFormat;
 };
 
@@ -95,17 +102,16 @@ public:
    * SetSupportedResolutions().
    */
   void ClearSupportedResolutions() { mSupportedSizes.Clear(); }
 
   bool IsSupported(uint32_t aQualityIndex) const;
 
   already_AddRefed<RecorderProfile> Get(uint32_t aQualityIndex) const;
   already_AddRefed<GonkRecorderProfile> Get(const char* aProfileName) const;
-  nsresult ConfigureRecorder(android::GonkRecorder* aRecorder);
 
 protected:
   virtual ~GonkRecorderProfileManager();
 
   nsTArray<ICameraControl::Size> mSupportedSizes;
 };
 
 }; // namespace mozilla
--- a/dom/camera/ICameraControl.h
+++ b/dom/camera/ICameraControl.h
@@ -75,18 +75,38 @@ enum {
   CAMERA_PARAM_SUPPORTED_ISOMODES
 };
 
 class ICameraControl
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ICameraControl)
 
+  // Returns the number of cameras supported by the system.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_FAILURE if the camera count cannot be retrieved.
   static nsresult GetNumberOfCameras(int32_t& aDeviceCount);
+
+  // Gets the (possibly-meaningful) name of a particular camera.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aDeviceNum' is not a valid camera number;
+  //  - NS_ERROR_NOT_AVAILABLE if 'aDeviceNum' is valid but the camera name
+  //      could still not be retrieved.
   static nsresult GetCameraName(uint32_t aDeviceNum, nsCString& aDeviceName);
+
+  // Returns a list of names of all cameras supported by the system.
+  //
+  // Return values:
+  //  - NS_OK on success, even if no camera names are returned (in which
+  //      case 'aList' will be empty);
+  //  - NS_ERROR_NOT_AVAILABLE if the list of cameras cannot be retrieved.
   static nsresult GetListOfCameras(nsTArray<nsString>& aList);
 
   enum Mode {
     kUnspecifiedMode,
     kPictureMode,
     kVideoMode,
   };
 
@@ -118,18 +138,17 @@ public:
   };
 
   struct Configuration {
     Mode      mMode;
     Size      mPreviewSize;
     nsString  mRecorderProfile;
   };
 
-  struct Point
-  {
+  struct Point {
     int32_t   x;
     int32_t   y;
   };
 
   struct Face {
     uint32_t  id;
     uint32_t  score;
     Region    bound;
@@ -138,35 +157,51 @@ public:
     bool      hasRightEye;
     Point     rightEye;
     bool      hasMouth;
     Point     mouth;
   };
 
   static already_AddRefed<ICameraControl> Create(uint32_t aCameraId);
 
-  virtual nsresult Start(const Configuration* aInitialConfig = nullptr) = 0;
-  virtual nsresult Stop() = 0;
-
-  virtual nsresult SetConfiguration(const Configuration& aConfig) = 0;
-
   virtual void AddListener(CameraControlListener* aListener) = 0;
   virtual void RemoveListener(CameraControlListener* aListener) = 0;
 
+  // Camera control methods.
+  //
+  // Return values:
+  //  - NS_OK on success (if the method requires an asynchronous process,
+  //      this value indicates that the process has begun successfully);
+  //  - NS_ERROR_INVALID_ARG if one or more arguments is invalid;
+  //  - NS_ERROR_FAILURE if an asynchronous method could not be dispatched.
+  virtual nsresult Start(const Configuration* aInitialConfig = nullptr) = 0;
+  virtual nsresult Stop() = 0;
+  virtual nsresult SetConfiguration(const Configuration& aConfig) = 0;
   virtual nsresult StartPreview() = 0;
   virtual nsresult StopPreview() = 0;
   virtual nsresult AutoFocus() = 0;
   virtual nsresult TakePicture() = 0;
   virtual nsresult StartRecording(DeviceStorageFileDescriptor *aFileDescriptor,
                                   const StartRecordingOptions* aOptions = nullptr) = 0;
   virtual nsresult StopRecording() = 0;
   virtual nsresult StartFaceDetection() = 0;
   virtual nsresult StopFaceDetection() = 0;
   virtual nsresult ResumeContinuousFocus() = 0;
 
+  // Camera parameter getters and setters. 'aKey' must be one of the
+  // CAMERA_PARAM_* values enumerated above.
+  //
+  // Return values:
+  //  - NS_OK on success;
+  //  - NS_ERROR_INVALID_ARG if 'aValue' contains an invalid value;
+  //  - NS_ERROR_NOT_IMPLEMENTED if 'aKey' is invalid;
+  //  - NS_ERROR_NOT_AVAILABLE if the getter fails to retrieve a valid value,
+  //      or if a setter fails because it requires one or more values that
+  //      could not be retrieved;
+  //  - NS_ERROR_FAILURE on unexpected internal failures.
   virtual nsresult Set(uint32_t aKey, const nsAString& aValue) = 0;
   virtual nsresult Get(uint32_t aKey, nsAString& aValue) = 0;
   virtual nsresult Set(uint32_t aKey, double aValue) = 0;
   virtual nsresult Get(uint32_t aKey, double& aValue) = 0;
   virtual nsresult Set(uint32_t aKey, int32_t aValue) = 0;
   virtual nsresult Get(uint32_t aKey, int32_t& aValue) = 0;
   virtual nsresult Set(uint32_t aKey, int64_t aValue) = 0;
   virtual nsresult Get(uint32_t aKey, int64_t& aValue) = 0;
new file mode 100644
--- /dev/null
+++ b/dom/camera/TestGonkCameraControl.cpp
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 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 "TestGonkCameraControl.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+
+static const char* sMethodErrorOverride = "camera.control.test.method.error";
+static const char* sAsyncErrorOverride = "camera.control.test.async.error";
+
+TestGonkCameraControl::TestGonkCameraControl(uint32_t aCameraId)
+  : nsGonkCameraControl(aCameraId)
+{
+  DOM_CAMERA_LOGA("v===== Created TestGonkCameraControl =====v\n");
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
+
+TestGonkCameraControl::~TestGonkCameraControl()
+{
+  DOM_CAMERA_LOGA("^===== Destroyed TestGonkCameraControl =====^\n");
+}
+
+nsresult
+TestGonkCameraControl::ForceMethodFailWithCodeInternal(const char* aFile, int aLine)
+{
+  nsresult rv =
+    static_cast<nsresult>(Preferences::GetInt(sMethodErrorOverride,
+                                              static_cast<int32_t>(NS_OK)));
+  if (NS_FAILED(rv)) {
+    DOM_CAMERA_LOGI("[%s:%d] CameraControl method error override: 0x%x\n",
+      aFile, aLine, rv);
+  }
+  return rv;
+}
+
+nsresult
+TestGonkCameraControl::ForceAsyncFailWithCodeInternal(const char* aFile, int aLine)
+{
+  nsresult rv =
+    static_cast<nsresult>(Preferences::GetInt(sAsyncErrorOverride,
+                                              static_cast<int32_t>(NS_OK)));
+  if (NS_FAILED(rv)) {
+    DOM_CAMERA_LOGI("[%s:%d] CameraControl async error override: 0x%x\n",
+      aFile, aLine, rv);
+  }
+  return rv;
+}
+
+nsresult
+TestGonkCameraControl::Start(const Configuration* aConfig)
+{
+  nsresult rv = ForceMethodFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::Start(aConfig);
+}
+
+nsresult
+TestGonkCameraControl::StartImpl(const Configuration* aInitialConfig)
+{
+  nsresult rv = ForceAsyncFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::StartImpl(aInitialConfig);
+}
+
+nsresult
+TestGonkCameraControl::Stop()
+{
+  nsresult rv = ForceMethodFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::Stop();
+}
+
+nsresult
+TestGonkCameraControl::StopImpl()
+{
+  nsresult rv = ForceAsyncFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::StopImpl();
+}
+
+nsresult
+TestGonkCameraControl::SetConfiguration(const Configuration& aConfig)
+{
+  nsresult rv = ForceMethodFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::SetConfiguration(aConfig);
+}
+
+nsresult
+TestGonkCameraControl::SetConfigurationImpl(const Configuration& aConfig)
+{
+  nsresult rv = ForceAsyncFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::SetConfigurationImpl(aConfig);
+}
+
+nsresult
+TestGonkCameraControl::StartPreview()
+{
+  nsresult rv = ForceMethodFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::StartPreview();
+}
+
+nsresult
+TestGonkCameraControl::StartPreviewImpl()
+{
+  nsresult rv = ForceAsyncFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::StartImpl();
+}
+
+nsresult
+TestGonkCameraControl::StopPreview()
+{
+  nsresult rv = ForceMethodFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::StopPreview();
+}
+
+nsresult
+TestGonkCameraControl::StopPreviewImpl()
+{
+  nsresult rv = ForceAsyncFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::StartImpl();
+}
+
+nsresult
+TestGonkCameraControl::AutoFocus()
+{
+  nsresult rv = ForceMethodFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::AutoFocus();
+}
+
+nsresult
+TestGonkCameraControl::AutoFocusImpl()
+{
+  nsresult rv = ForceAsyncFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::AutoFocusImpl();
+}
+
+nsresult
+TestGonkCameraControl::StartFaceDetection()
+{
+  nsresult rv = ForceMethodFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::StartFaceDetection();
+}
+
+nsresult
+TestGonkCameraControl::StartFaceDetectionImpl()
+{
+  nsresult rv = ForceAsyncFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::StartFaceDetectionImpl();
+}
+
+nsresult
+TestGonkCameraControl::StopFaceDetection()
+{
+  nsresult rv = ForceMethodFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::StopFaceDetection();
+}
+
+nsresult
+TestGonkCameraControl::StopFaceDetectionImpl()
+{
+  nsresult rv = ForceAsyncFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::StopFaceDetectionImpl();
+}
+
+nsresult
+TestGonkCameraControl::TakePicture()
+{
+  nsresult rv = ForceMethodFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::TakePicture();
+}
+
+nsresult
+TestGonkCameraControl::TakePictureImpl()
+{
+  nsresult rv = ForceAsyncFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::TakePictureImpl();
+}
+
+nsresult
+TestGonkCameraControl::StartRecording(DeviceStorageFileDescriptor* aFileDescriptor,
+                                      const StartRecordingOptions* aOptions)
+{
+  nsresult rv = ForceMethodFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::StartRecording(aFileDescriptor, aOptions);
+}
+
+nsresult
+TestGonkCameraControl::StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
+                                          const StartRecordingOptions* aOptions)
+{
+  nsresult rv = ForceAsyncFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::StartRecordingImpl(aFileDescriptor, aOptions);
+}
+
+nsresult
+TestGonkCameraControl::StopRecording()
+{
+  nsresult rv = ForceMethodFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::StopRecording();
+}
+
+nsresult
+TestGonkCameraControl::StopRecordingImpl()
+{
+  nsresult rv = ForceAsyncFailWithCode();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  return nsGonkCameraControl::StopRecordingImpl();
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/TestGonkCameraControl.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#ifndef DOM_CAMERA_TESTGONKCAMERACONTROL_H
+#define DOM_CAMERA_TESTGONKCAMERACONTROL_H
+
+#include "GonkCameraControl.h"
+
+namespace mozilla {
+
+class TestGonkCameraControl : public nsGonkCameraControl
+{
+public:
+  TestGonkCameraControl(uint32_t aCameraId);
+
+  virtual nsresult Start(const Configuration* aConfig = nullptr) MOZ_OVERRIDE;
+  virtual nsresult Stop() MOZ_OVERRIDE;
+  virtual nsresult SetConfiguration(const Configuration& aConfig) MOZ_OVERRIDE;
+  virtual nsresult StartPreview() MOZ_OVERRIDE;
+  virtual nsresult StopPreview() MOZ_OVERRIDE;
+  virtual nsresult AutoFocus() MOZ_OVERRIDE;
+  virtual nsresult StartFaceDetection() MOZ_OVERRIDE;
+  virtual nsresult StopFaceDetection() MOZ_OVERRIDE;
+  virtual nsresult TakePicture() MOZ_OVERRIDE;
+  virtual nsresult StartRecording(DeviceStorageFileDescriptor* aFileDescriptor,
+                                  const StartRecordingOptions* aOptions) MOZ_OVERRIDE;
+  virtual nsresult StopRecording() MOZ_OVERRIDE;
+
+protected:
+  virtual ~TestGonkCameraControl();
+
+  virtual nsresult StartImpl(const Configuration* aInitialConfig = nullptr) MOZ_OVERRIDE;
+  virtual nsresult StopImpl() MOZ_OVERRIDE;
+  virtual nsresult SetConfigurationImpl(const Configuration& aConfig) MOZ_OVERRIDE;
+  virtual nsresult StartPreviewImpl() MOZ_OVERRIDE;
+  virtual nsresult StopPreviewImpl() MOZ_OVERRIDE;
+  virtual nsresult AutoFocusImpl() MOZ_OVERRIDE;
+  virtual nsresult StartFaceDetectionImpl() MOZ_OVERRIDE;
+  virtual nsresult StopFaceDetectionImpl() MOZ_OVERRIDE;
+  virtual nsresult TakePictureImpl() MOZ_OVERRIDE;
+  virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
+                                      const StartRecordingOptions* aOptions = nullptr) MOZ_OVERRIDE;
+  virtual nsresult StopRecordingImpl() MOZ_OVERRIDE;
+
+  nsresult ForceMethodFailWithCodeInternal(const char* aFile, int aLine);
+  nsresult ForceAsyncFailWithCodeInternal(const char* aFile, int aLine);
+
+private:
+  TestGonkCameraControl(const TestGonkCameraControl&) MOZ_DELETE;
+  TestGonkCameraControl& operator=(const TestGonkCameraControl&) MOZ_DELETE;
+};
+
+#define ForceMethodFailWithCode() ForceMethodFailWithCodeInternal(__FILE__, __LINE__)
+#define ForceAsyncFailWithCode()  ForceAsyncFailWithCodeInternal(__FILE__, __LINE__)
+
+} // namespace mozilla
+
+#endif // DOM_CAMERA_TESTGONKCAMERACONTROL_H
--- a/dom/camera/TestGonkCameraHardware.cpp
+++ b/dom/camera/TestGonkCameraHardware.cpp
@@ -38,17 +38,17 @@ TestGonkCameraHardware::~TestGonkCameraH
   MOZ_COUNT_DTOR(TestGonkCameraHardware);
   DOM_CAMERA_LOGA("^===== Destroyed TestGonkCameraHardware =====^\n");
 }
 
 nsresult
 TestGonkCameraHardware::Init()
 {
   if (IsTestCase("init-failure")) {
-    return NS_ERROR_FAILURE;
+    return NS_ERROR_NOT_INITIALIZED;
   }
 
   return GonkCameraHardware::Init();
 }
 
 const nsCString
 TestGonkCameraHardware::TestCase()
 {
--- a/dom/camera/moz.build
+++ b/dom/camera/moz.build
@@ -29,16 +29,17 @@ if CONFIG['MOZ_B2G_CAMERA']:
     SOURCES += [
         'GonkCameraControl.cpp',
         'GonkCameraHwMgr.cpp',
         'GonkCameraManager.cpp',
         'GonkCameraParameters.cpp',
         'GonkCameraSource.cpp',
         'GonkRecorder.cpp',
         'GonkRecorderProfiles.cpp',
+        'TestGonkCameraControl.cpp',
         'TestGonkCameraHardware.cpp',
     ]
 else:
     SOURCES += [
         'FallbackCameraControl.cpp',
         'FallbackCameraManager.cpp',
     ]
 
--- a/dom/camera/test/test_bug975472.html
+++ b/dom/camera/test/test_bug975472.html
@@ -1,14 +1,15 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test for bug 975472</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="camera_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <video id="viewfinder" width="200" height="200" autoplay></video>
 <img src="#" alt="This image is going to load" id="testimage"/>
 <script class="testbody" type="text/javascript;version=1.7">
 
 var whichCamera = navigator.mozCameras.getListOfCameras()[0];
@@ -81,43 +82,43 @@ var tests = [
   },
   {
     key: "auto-focus-after-release",
     func: function testAutoFocus(camera) {
       function onSuccess(success) {
         ok(false, "autoFocus() succeeded incorrectly");
       }
       function onError(error) {
-        ok(true, "autoFocus() failed correctly with: " + error);
+        ok(error === "hardware-closed", "autoFocus() failed with: " + error);
         next();
       }
       camera.autoFocus(onSuccess, onError);
     }
   },
   {
     key: "take-picture-after-release",
     func: function testTakePicture(camera) {
       function onSuccess(picture) {
         ok(false, "takePicture() succeeded incorrectly");
       }
       function onError(error) {
-        ok(true, "takePicture() failed correctly with: " + error);
+        ok(error === "hardware-closed", "takePicture() failed with: " + error);
         next();
       }
       camera.takePicture(null, onSuccess, onError);
     }
   },
   {
     key: "start-recording-after-release",
     func: function testStartRecording(camera) {
       function onSuccess(picture) {
         ok(false, "startRecording() process succeeded incorrectly");
       }
       function onError(error) {
-        ok(true, "startRecording() process failed correctly with: " + error);
+        ok(error === "hardware-closed", "startRecording() failed with: " + error);
         next();
       }
       var recordingOptions = {
         profile: 'cif',
         rotation: 0
       };
       camera.startRecording(recordingOptions,
                             navigator.getDeviceStorage('videos'),
@@ -134,17 +135,17 @@ var tests = [
   },
   {
     key: "set-configuration-after-release",
     func: function testSetConfiguration(camera) {
       function onSuccess(picture) {
         ok(false, "setConfiguration() process succeeded incorrectly");
       }
       function onError(error) {
-        ok(true, "setConfiguration() process failed correctly with: " + error);
+        ok(error === "hardware-closed", "setConfiguration() failed with: " + error);
         next();
       }
       camera.setConfiguration(config, onSuccess, onError);
     }
   },
 ];
 
 var testGenerator = function() {
@@ -162,17 +163,17 @@ var Camera = {
   onCameraReady: function () {
     Camera.nextTest = function() {
       try {
         var t = testGenerator.next();
         info("test: " + t.key);
         t.func(Camera.cameraObj);
       } catch(e) {
         if (e instanceof StopIteration) {
-          SimpleTest.finish();
+          CameraTest.end();
         } else {
           throw e;
         }
       }
     };
     // Release the camera hardware, and call all of the asynchronous methods
     // to make sure they properly handle being in this state.
     Camera.cameraObj.release();