Bug 940424 - add a failable test shim to the Gonk hardware wrapper, r=dclarke,dhylands,jesup
authorMike Habicher <mikeh@mozilla.com>
Wed, 19 Feb 2014 23:18:52 -0500
changeset 170001 4ab6a5b763d8221dd2a189b106bba60b202fd6c2
parent 170000 01fa011daae62329a33c14279976d621fd7b8550
child 170002 8a58bb59c57fc9e00f6f0545438ee1bbfa3c924e
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersdclarke, dhylands, jesup
bugs940424
milestone30.0a1
Bug 940424 - add a failable test shim to the Gonk hardware wrapper, r=dclarke,dhylands,jesup
content/media/webrtc/MediaEngineWebRTC.h
content/media/webrtc/MediaEngineWebRTCVideo.cpp
dom/camera/CameraControlImpl.cpp
dom/camera/CameraControlImpl.h
dom/camera/CameraControlListener.h
dom/camera/DOMCameraControl.cpp
dom/camera/DOMCameraControlListener.cpp
dom/camera/DOMCameraControlListener.h
dom/camera/FallbackCameraManager.cpp
dom/camera/GonkCameraControl.cpp
dom/camera/GonkCameraControl.h
dom/camera/GonkCameraHwMgr.cpp
dom/camera/GonkCameraHwMgr.h
dom/camera/GonkCameraManager.cpp
dom/camera/ICameraControl.h
dom/camera/TestGonkCameraHardware.cpp
dom/camera/TestGonkCameraHardware.h
dom/camera/moz.build
dom/camera/test/camera_common.js
dom/camera/test/mochitest.ini
dom/camera/test/test_camera.html
dom/camera/test/test_camera_2.html
dom/camera/test/test_camera_3.html
dom/camera/test/test_camera_hardware_failures.html
dom/camera/test/test_camera_hardware_init_failure.html
--- a/content/media/webrtc/MediaEngineWebRTC.h
+++ b/content/media/webrtc/MediaEngineWebRTC.h
@@ -157,19 +157,18 @@ public:
 #ifndef MOZ_B2G_CAMERA
   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);
-  void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration);
   bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);
-  void OnError(CameraErrorContext aContext, const nsACString& aError);
+  void OnError(CameraErrorContext aContext, CameraError 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();
 #endif
--- a/content/media/webrtc/MediaEngineWebRTCVideo.cpp
+++ b/content/media/webrtc/MediaEngineWebRTCVideo.cpp
@@ -495,79 +495,80 @@ MediaEngineWebRTCVideoSource::Shutdown()
 
 #ifdef MOZ_B2G_CAMERA
 
 // All these functions must be run on MainThread!
 void
 MediaEngineWebRTCVideoSource::AllocImpl() {
   MOZ_ASSERT(NS_IsMainThread());
 
-  mCameraControl = ICameraControl::Create(mCaptureIndex, nullptr);
-
-  // Add this as a listener for CameraControl events. We don't need
-  // to explicitly remove this--destroying the CameraControl object
-  // in DeallocImpl() will do that for us.
-  mCameraControl->AddListener(this);
+  mCameraControl = ICameraControl::Create(mCaptureIndex);
+  if (mCameraControl) {
+    mState = kAllocated;
+    // Add this as a listener for CameraControl events. We don't need
+    // to explicitly remove this--destroying the CameraControl object
+    // in DeallocImpl() will do that for us.
+    mCameraControl->AddListener(this);
+  }
+  mCallbackMonitor.Notify();
 }
 
 void
 MediaEngineWebRTCVideoSource::DeallocImpl() {
   MOZ_ASSERT(NS_IsMainThread());
 
-  mCameraControl->ReleaseHardware();
   mCameraControl = nullptr;
 }
 
 void
 MediaEngineWebRTCVideoSource::StartImpl(webrtc::CaptureCapability aCapability) {
   MOZ_ASSERT(NS_IsMainThread());
 
   ICameraControl::Configuration config;
   config.mMode = ICameraControl::kPictureMode;
   config.mPreviewSize.width = aCapability.width;
   config.mPreviewSize.height = aCapability.height;
-  mCameraControl->SetConfiguration(config);
+  mCameraControl->Start(&config);
   mCameraControl->Set(CAMERA_PARAM_PICTURESIZE, config.mPreviewSize);
 }
 
 void
 MediaEngineWebRTCVideoSource::StopImpl() {
   MOZ_ASSERT(NS_IsMainThread());
 
-  mCameraControl->StopPreview();
+  mCameraControl->Stop();
 }
 
 void
 MediaEngineWebRTCVideoSource::SnapshotImpl() {
   MOZ_ASSERT(NS_IsMainThread());
   mCameraControl->TakePicture();
 }
 
 void
 MediaEngineWebRTCVideoSource::OnHardwareStateChange(HardwareState aState)
 {
   ReentrantMonitorAutoEnter sync(mCallbackMonitor);
-  if (aState == CameraControlListener::kHardwareOpen) {
-    mState = kAllocated;
+  if (aState == CameraControlListener::kHardwareClosed) {
+    // When the first CameraControl listener is added, it gets pushed
+    // the current state of the camera--normally 'closed'. We only
+    // pay attention to that state if we've progressed out of the
+    // allocated state.
+    if (mState != kAllocated) {
+      mState = kReleased;
+      mCallbackMonitor.Notify();
+    }
   } else {
-    mState = kReleased;
+    mState = kStarted;
+    mCallbackMonitor.Notify();
   }
-  mCallbackMonitor.Notify();
 }
 
 void
-MediaEngineWebRTCVideoSource::OnConfigurationChange(const CameraListenerConfiguration& aConfiguration)
-{
-  ReentrantMonitorAutoEnter sync(mCallbackMonitor);
-  mState = kStarted;
-  mCallbackMonitor.Notify();
-}
-
-void
-MediaEngineWebRTCVideoSource::OnError(CameraErrorContext aContext, const nsACString& aError)
+MediaEngineWebRTCVideoSource::OnError(CameraErrorContext aContext, CameraError 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
@@ -230,22 +230,43 @@ void
 CameraControlImpl::OnError(CameraControlListener::CameraErrorContext aContext,
                            CameraControlListener::CameraError aError)
 {
   // This callback can run on threads other than the Main Thread and
   //  the Camera Thread.
   RwLockAutoEnterRead lock(mListenerLock);
 
 #ifdef PR_LOGGING
-  const char* error[] = { "camera-service-failed", "unknown" };
-  if (static_cast<unsigned int>(aError) < sizeof(error) / sizeof(error[0])) {
-    DOM_CAMERA_LOGW("CameraControlImpl::OnError : aContext=%u, msg='%s'\n",
-      aContext, error[aError]);
+  const char* error[] = {
+    "api-failed",
+    "init-failed",
+    "invalid-configuration",
+    "service-failed",
+    "set-picture-size-failred",
+    "set-thumbnail-size-failed",
+    "unknown"
+  };
+  const char* context[] = {
+    "StartCamera",
+    "StopCamera",
+    "AutoFocus",
+    "TakePicture",
+    "StartRecording",
+    "StopRecording",
+    "SetConfiguration",
+    "StartPreview",
+    "StopPreview",
+    "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);
   } else {
-    DOM_CAMERA_LOGE("CameraControlImpl::OnError : aContext=%u, unknown error=%d\n",
+    DOM_CAMERA_LOGE("CameraControlImpl::OnError : aContext=%u, aError=%d\n",
       aContext, aError);
   }
 #endif
 
   for (uint32_t i = 0; i < mListeners.Length(); ++i) {
     CameraControlListener* l = mListeners[i];
     l->OnError(aContext, aError);
   }
@@ -289,16 +310,52 @@ public:
   }
 
 protected:
   nsRefPtr<CameraControlImpl> mCameraControl;
   CameraControlListener::CameraErrorContext mContext;
 };
 
 nsresult
+CameraControlImpl::Start(const Configuration* aConfig)
+{
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext,
+            const Configuration* aConfig)
+      : ControlMessage(aCameraControl, aContext)
+      , mHaveInitialConfig(false)
+    {
+      if (aConfig) {
+        mConfig = *aConfig;
+        mHaveInitialConfig = true;
+      }
+    }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      if (mHaveInitialConfig) {
+        return mCameraControl->StartImpl(&mConfig);
+      }
+      return mCameraControl->StartImpl();
+    }
+
+  protected:
+    bool mHaveInitialConfig;
+    Configuration mConfig;
+  };
+
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInStartCamera, aConfig), NS_DISPATCH_NORMAL);
+}
+
+nsresult
 CameraControlImpl::SetConfiguration(const Configuration& aConfig)
 {
   class Message : public ControlMessage
   {
   public:
     Message(CameraControlImpl* aCameraControl,
             CameraControlListener::CameraErrorContext aContext,
             const Configuration& aConfig)
@@ -470,35 +527,35 @@ CameraControlImpl::StopPreview()
     }
   };
 
   return mCameraThread->Dispatch(
     new Message(this, CameraControlListener::kInStopPreview), NS_DISPATCH_NORMAL);
 }
 
 nsresult
-CameraControlImpl::ReleaseHardware()
+CameraControlImpl::Stop()
 {
   class Message : public ControlMessage
   {
   public:
     Message(CameraControlImpl* aCameraControl,
             CameraControlListener::CameraErrorContext aContext)
       : ControlMessage(aCameraControl, aContext)
     { }
 
     nsresult
     RunImpl() MOZ_OVERRIDE
     {
-      return mCameraControl->ReleaseHardwareImpl();
+      return mCameraControl->StopImpl();
     }
   };
 
   return mCameraThread->Dispatch(
-    new Message(this, CameraControlListener::kInReleaseHardware), NS_DISPATCH_NORMAL);
+    new Message(this, CameraControlListener::kInStopCamera), NS_DISPATCH_NORMAL);
 }
 
 class CameraControlImpl::ListenerMessage : public CameraControlImpl::ControlMessage
 {
 public:
   ListenerMessage(CameraControlImpl* aCameraControl,
                   CameraControlListener* aListener)
     : ControlMessage(aCameraControl, CameraControlListener::kInUnspecified)
@@ -510,16 +567,17 @@ protected:
 };
 
 void
 CameraControlImpl::AddListenerImpl(already_AddRefed<CameraControlListener> aListener)
 {
   RwLockAutoEnterWrite lock(mListenerLock);
 
   CameraControlListener* l = *mListeners.AppendElement() = aListener;
+  DOM_CAMERA_LOGI("Added camera control listener %p\n", l);
 
   // Update the newly-added listener's state
   l->OnConfigurationChange(mCurrentConfiguration);
   l->OnHardwareStateChange(mHardwareState);
   l->OnPreviewStateChange(mPreviewState);
 }
 
 void
@@ -546,16 +604,17 @@ CameraControlImpl::AddListener(CameraCon
 
 void
 CameraControlImpl::RemoveListenerImpl(CameraControlListener* aListener)
 {
   RwLockAutoEnterWrite lock(mListenerLock);
 
   nsRefPtr<CameraControlListener> l(aListener);
   mListeners.RemoveElement(l);
+  DOM_CAMERA_LOGI("Removed camera control listener %p\n", l.get());
   // XXXmikeh - do we want to notify the listener that it has been removed?
 }
 
 void
 CameraControlImpl::RemoveListener(CameraControlListener* aListener)
  {
   class Message : public ListenerMessage
   {
--- a/dom/camera/CameraControlImpl.h
+++ b/dom/camera/CameraControlImpl.h
@@ -31,25 +31,27 @@ 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;
 
+  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(bool aCancelExistingCall) MOZ_OVERRIDE;
   virtual nsresult TakePicture() MOZ_OVERRIDE;
   virtual nsresult StartRecording(DeviceStorageFileDescriptor* aFileDescriptor,
                                   const StartRecordingOptions* aOptions) MOZ_OVERRIDE;
   virtual nsresult StopRecording() MOZ_OVERRIDE;
-  virtual nsresult ReleaseHardware() MOZ_OVERRIDE;
 
   already_AddRefed<RecorderProfileManager> GetRecorderProfileManager();
   uint32_t GetCameraId() { return mCameraId; }
 
   virtual void Shutdown() MOZ_OVERRIDE;
 
   void OnShutter();
   void OnClosed();
@@ -58,17 +60,17 @@ public:
 
 protected:
   // Event handlers.
   void OnAutoFocusComplete(bool aAutoFocusSucceeded);
   void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType);
 
   bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);
   void OnRecorderStateChange(CameraControlListener::RecorderState aState,
-                             int32_t aStatus, int32_t aTrackNumber);
+                             int32_t aStatus = -1, int32_t aTrackNumber = -1);
   void OnPreviewStateChange(CameraControlListener::PreviewState aState);
   void OnHardwareStateChange(CameraControlListener::HardwareState aState);
   void OnConfigurationChange();
 
   // When we create a new CameraThread, we keep a static reference to it so
   // that multiple CameraControl instances can find and reuse it; but we
   // don't want that reference to keep the thread object around unnecessarily,
   // so we make it a weak reference. The strong dynamic references will keep
@@ -85,27 +87,28 @@ protected:
   void AddListenerImpl(already_AddRefed<CameraControlListener> aListener);
   void RemoveListenerImpl(CameraControlListener* aListener);
   nsTArray<nsRefPtr<CameraControlListener> > mListeners;
   PRRWLock* mListenerLock;
 
   class ControlMessage;
   class ListenerMessage;
 
+  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(bool aCancelExistingCall) = 0;
   virtual nsresult TakePictureImpl() = 0;
   virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
                                       const StartRecordingOptions* aOptions) = 0;
   virtual nsresult StopRecordingImpl() = 0;
   virtual nsresult PushParametersImpl() = 0;
   virtual nsresult PullParametersImpl() = 0;
-  virtual nsresult ReleaseHardwareImpl() = 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
@@ -12,17 +12,25 @@ namespace mozilla {
 
 namespace layers {
   class Image;
 }
 
 class CameraControlListener
 {
 public:
-  virtual ~CameraControlListener() { }
+  CameraControlListener()
+  {
+    MOZ_COUNT_CTOR(CameraControlListener);
+  }
+
+  virtual ~CameraControlListener()
+  {
+    MOZ_COUNT_DTOR(CameraControlListener);
+  }
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CameraControlListener);
 
   enum HardwareState
   {
     kHardwareOpen,
     kHardwareClosed
   };
@@ -66,23 +74,23 @@ public:
   };
   virtual void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration) { }
 
   virtual void OnAutoFocusComplete(bool aAutoFocusSucceeded) { }
   virtual void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType) { }
 
   enum CameraErrorContext
   {
-    kInGetCamera,
+    kInStartCamera,
+    kInStopCamera,
     kInAutoFocus,
     kInTakePicture,
     kInStartRecording,
     kInStopRecording,
     kInSetConfiguration,
-    kInReleaseHardware,
     kInStartPreview,
     kInStopPreview,
     kInUnspecified
   };
   enum CameraError
   {
     kErrorApiFailed,
     kErrorInitFailed,
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -196,29 +196,36 @@ nsDOMCameraControl::nsDOMCameraControl(u
     default:
       MOZ_ASSUME_UNREACHABLE("Unanticipated camera mode!");
   }
 
   config.mPreviewSize.width = aInitialConfig.mPreviewSize.mWidth;
   config.mPreviewSize.height = aInitialConfig.mPreviewSize.mHeight;
   config.mRecorderProfile = aInitialConfig.mRecorderProfile;
 
-  mCameraControl = ICameraControl::Create(aCameraId, &config);
+  mCameraControl = ICameraControl::Create(aCameraId);
   mCurrentConfiguration = initialConfig.forget();
 
   // Attach our DOM-facing media stream to our viewfinder stream.
   mStream = mInput;
   MOZ_ASSERT(mWindow, "Shouldn't be created with a null window!");
   if (mWindow->GetExtantDoc()) {
     CombineWithPrincipal(mWindow->GetExtantDoc()->NodePrincipal());
   }
 
   // Register a listener for camera events.
   mListener = new DOMCameraControlListener(this, mInput);
   mCameraControl->AddListener(mListener);
+
+  // Start the camera...
+  nsresult rv = mCameraControl->Start(&config);
+  if (NS_FAILED(rv)) {
+    mListener->OnError(DOMCameraControlListener::kInStartCamera,
+                       DOMCameraControlListener::kErrorApiFailed);
+  }
 }
 
 nsDOMCameraControl::~nsDOMCameraControl()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
 }
 
 JSObject*
@@ -906,17 +913,17 @@ nsDOMCameraControl::ReleaseHardware(cons
   if (aOnSuccess.WasPassed()) {
     mReleaseOnSuccessCb = &aOnSuccess.Value();
   }
   mReleaseOnErrorCb = nullptr;
   if (aOnError.WasPassed()) {
     mReleaseOnErrorCb = &aOnError.Value();
   }
 
-  aRv = mCameraControl->ReleaseHardware();
+  aRv = mCameraControl->Stop();
 }
 
 void
 nsDOMCameraControl::Shutdown()
 {
   DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
   MOZ_ASSERT(mCameraControl);
 
@@ -1152,21 +1159,26 @@ nsDOMCameraControl::OnTakePictureComplet
 
 void
 nsDOMCameraControl::OnError(CameraControlListener::CameraErrorContext aContext, const nsAString& aError)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<CameraErrorCallback>* errorCb;
   switch (aContext) {
-    case CameraControlListener::kInGetCamera:
+    case CameraControlListener::kInStartCamera:
       mGetCameraOnSuccessCb = nullptr;
       errorCb = &mGetCameraOnErrorCb;
       break;
 
+    case CameraControlListener::kInStopCamera:
+      mReleaseOnSuccessCb = nullptr;
+      errorCb = &mReleaseOnErrorCb;
+      break;
+
     case CameraControlListener::kInSetConfiguration:
       mSetConfigurationOnSuccessCb = nullptr;
       errorCb = &mSetConfigurationOnErrorCb;
       break;
 
     case CameraControlListener::kInAutoFocus:
       mAutoFocusOnSuccessCb = nullptr;
       errorCb = &mAutoFocusOnErrorCb;
@@ -1177,21 +1189,16 @@ nsDOMCameraControl::OnError(CameraContro
       errorCb = &mTakePictureOnErrorCb;
       break;
 
     case CameraControlListener::kInStartRecording:
       mStartRecordingOnSuccessCb = nullptr;
       errorCb = &mStartRecordingOnErrorCb;
       break;
 
-    case CameraControlListener::kInReleaseHardware:
-      mReleaseOnSuccessCb = nullptr;
-      errorCb = &mReleaseOnErrorCb;
-      break;
-
     case CameraControlListener::kInStopRecording:
       NS_WARNING("Failed to stop recording (which shouldn't happen)!");
       MOZ_CRASH();
       break;
 
     case CameraControlListener::kInStartPreview:
       NS_WARNING("Failed to (re)start preview!");
       MOZ_CRASH();
--- a/dom/camera/DOMCameraControlListener.cpp
+++ b/dom/camera/DOMCameraControlListener.cpp
@@ -8,24 +8,43 @@
 #include "CameraCommon.h"
 #include "DOMCameraControl.h"
 #include "CameraPreviewMediaStream.h"
 #include "mozilla/dom/CameraManagerBinding.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
+DOMCameraControlListener::DOMCameraControlListener(nsDOMCameraControl* aDOMCameraControl,
+                                                   CameraPreviewMediaStream* aStream)
+  : mDOMCameraControl(new nsMainThreadPtrHolder<nsDOMCameraControl>(aDOMCameraControl))
+  , mStream(aStream)
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p, camera=%p, stream=%p\n",
+    __func__, __LINE__, this, aDOMCameraControl, aStream);
+}
+
+DOMCameraControlListener::~DOMCameraControlListener()
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
+
 // Boilerplate callback runnable
 class DOMCameraControlListener::DOMCallback : public nsRunnable
 {
 public:
   DOMCallback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl)
     : mDOMCameraControl(aDOMCameraControl)
-  { }
-  virtual ~DOMCallback() { }
+  {
+    MOZ_COUNT_CTOR(DOMCameraControlListener::DOMCallback);
+  }
+  virtual ~DOMCallback()
+  {
+    MOZ_COUNT_DTOR(DOMCameraControlListener::DOMCallback);
+  }
 
   virtual void RunCallback(nsDOMCameraControl* aDOMCameraControl) = 0;
 
   NS_IMETHOD
   Run() MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
 
@@ -173,17 +192,18 @@ DOMCameraControlListener::OnConfiguratio
           config->mMode = CameraMode::Video;
           break;
 
         case ICameraControl::kPictureMode:
           config->mMode = CameraMode::Picture;
           break;
 
         default:
-          MOZ_ASSUME_UNREACHABLE("Unanticipated camera mode!");
+          DOM_CAMERA_LOGI("Camera mode still unspecified, nothing to do\n");
+          return;
       }
 
       // Map CameraControl parameters to their DOM-facing equivalents
       config->mRecorderProfile = mConfiguration.mRecorderProfile;
       config->mPreviewSize.mWidth = mConfiguration.mPreviewSize.width;
       config->mPreviewSize.mHeight = mConfiguration.mPreviewSize.height;
       config->mMaxMeteringAreas = mConfiguration.mMaxMeteringAreas;
       config->mMaxFocusAreas = mConfiguration.mMaxFocusAreas;
@@ -293,17 +313,17 @@ DOMCameraControlListener::OnError(Camera
     Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
              CameraErrorContext aContext,
              CameraError aError)
       : DOMCallback(aDOMCameraControl)
       , mContext(aContext)
       , mError(aError)
     { }
 
-    void
+    virtual void
     RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
     {
       nsString error;
 
       switch (mError) {
         case kErrorServiceFailed:
           error = NS_LITERAL_STRING("ErrorServiceFailed");
           break;
--- a/dom/camera/DOMCameraControlListener.h
+++ b/dom/camera/DOMCameraControlListener.h
@@ -10,39 +10,38 @@
 
 namespace mozilla {
 
 class nsDOMCameraControl;
 class CameraPreviewMediaStream;
 
 class DOMCameraControlListener : public CameraControlListener
 {
+public:
+  DOMCameraControlListener(nsDOMCameraControl* aDOMCameraControl, CameraPreviewMediaStream* aStream);
+
+  virtual void OnAutoFocusComplete(bool aAutoFocusSucceeded) MOZ_OVERRIDE;
+  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;
+
 protected:
+  virtual ~DOMCameraControlListener();
+
   nsMainThreadPtrHandle<nsDOMCameraControl> mDOMCameraControl;
   CameraPreviewMediaStream* mStream;
 
   class DOMCallback;
 
-public:
-  DOMCameraControlListener(nsDOMCameraControl* aDOMCameraControl, CameraPreviewMediaStream* aStream)
-    : mDOMCameraControl(new nsMainThreadPtrHolder<nsDOMCameraControl>(aDOMCameraControl))
-    , mStream(aStream)
-  { }
-
-  void OnAutoFocusComplete(bool aAutoFocusSucceeded);
-  void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType);
-
-  void OnHardwareStateChange(HardwareState aState);
-  void OnPreviewStateChange(PreviewState aState);
-  void OnRecorderStateChange(RecorderState aState, int32_t aStatus, int32_t aTrackNum);
-  void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration);
-  void OnShutter();
-  bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);
-  void OnError(CameraErrorContext aContext, CameraError aError);
-
 private:
   DOMCameraControlListener(const DOMCameraControlListener&) MOZ_DELETE;
   DOMCameraControlListener& operator=(const DOMCameraControlListener&) MOZ_DELETE;
 };
 
 } // namespace mozilla
 
 #endif // DOM_CAMERA_DOMCAMERACONTROLLISTENER_H
--- a/dom/camera/FallbackCameraManager.cpp
+++ b/dom/camera/FallbackCameraManager.cpp
@@ -21,12 +21,12 @@ ICameraControl::GetCameraName(uint32_t a
 
 nsresult
 ICameraControl::GetListOfCameras(nsTArray<nsString>& aList)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 already_AddRefed<ICameraControl>
-ICameraControl::Create(uint32_t aCameraId, const Configuration* aInitialConfig)
+ICameraControl::Create(uint32_t aCameraId)
 {
   return nullptr;
 }
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -72,100 +72,60 @@ nsGonkCameraControl::nsGonkCameraControl
   , mReentrantMonitor("GonkCameraControl::OnTakePictureMonitor")
 {
   // Constructor runs on the main thread...
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   mImageContainer = LayerManager::CreateImageContainer();
 }
 
 nsresult
-nsGonkCameraControl::Init(const Configuration* aInitialConfig)
+nsGonkCameraControl::StartImpl(const Configuration* aInitialConfig)
 {
-  class InitGonkCameraControl : public nsRunnable
-  {
-  public:
-    InitGonkCameraControl(nsGonkCameraControl* aCameraControl,
-                          const Configuration* aConfig)
-      : mCameraControl(aCameraControl)
-      , mHaveInitialConfig(false)
-    {
-      DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-      if (aConfig) {
-        mConfig = *aConfig;
-        mHaveInitialConfig = true;
-      }
-    }
-
-    ~InitGonkCameraControl()
-    {
-      DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-    }
+  /**
+   * For initialization, we try to return the camera control to the upper
+   * upper layer (i.e. the DOM) as quickly as possible. To do this, the
+   * camera is initialized in the following stages:
+   *
+   *  0. Initialize() initializes the hardware;
+   *  1. SetConfigurationInternal() does the minimal configuration
+   *     required so that we can start the preview -and- report a valid
+   *     configuration to the upper layer;
+   *  2. OnHardwareStateChange() reports that the hardware is ready,
+   *     which the upper (e.g. DOM) layer can (and does) use to return
+   *     the camera control object;
+   *  3. StartPreviewImpl() starts the flow of preview frames from the
+   *     camera hardware.
+   *
+   * The intent of the above flow is to let the Main Thread do as much work
+   * up-front as possible without waiting for blocking Camera Thread calls
+   * to complete.
+   */
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
 
-    /**
-     * For initialization, we try to return the camera control to the upper
-     * upper layer (i.e. the DOM) as quickly as possible. To do this, the
-     * camera is initialized in the following stages:
-     *
-     *  0. InitImpl() initializes the hardware;
-     *  1. SetConfigurationInternal() does the minimal configuration
-     *     required so that we can start the preview -and- report a valid
-     *     configuration to the upper layer;
-     *  2. OnHardwareStateChange() reports that the hardware is ready,
-     *     which the upper layer can (and does) use to return the camera
-     *     control object;
-     *  3. StartPreviewImpl() starts the flow of preview frames from the
-     *     camera hardware.
-     *
-     * The intent of the above flow is to let the Main Thread do as much work
-     * up-front as possible without waiting for blocking Camera Thread calls
-     * to complete.
-     */
-    NS_IMETHODIMP
-    Run() MOZ_OVERRIDE
-    {
-      nsresult rv = mCameraControl->InitImpl();
-      if (NS_FAILED(rv)) {
-        mCameraControl->OnError(CameraControlListener::kInGetCamera,
-                                CameraControlListener::kErrorInitFailed);
-        // The hardware failed to initialize, so close it up
-        mCameraControl->ReleaseHardware();
-        return rv;
-      }
+  nsresult rv = Initialize();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
-      if (mHaveInitialConfig) {
-        rv = mCameraControl->SetConfigurationInternal(mConfig);
-        if (NS_FAILED(rv)) {
-          mCameraControl->OnError(CameraControlListener::kInGetCamera,
-                                  CameraControlListener::kErrorInvalidConfiguration);
-          // The initial configuration failed, close up the hardware
-          mCameraControl->ReleaseHardware();
-          return rv;
-        }
-      }
+  if (aInitialConfig) {
+    rv = SetConfigurationInternal(*aInitialConfig);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      // The initial configuration failed, close up the hardware
+      StopImpl();
+      return rv;
+    }
+  }
 
-      mCameraControl->OnHardwareStateChange(CameraControlListener::kHardwareOpen);
-      return mCameraControl->StartPreviewImpl();
-    }
-
-  protected:
-    nsRefPtr<nsGonkCameraControl> mCameraControl;
-    Configuration mConfig;
-    bool mHaveInitialConfig;
-  };
-
-  // Initialization is carried out on the camera thread.
-  return mCameraThread->Dispatch(
-    new InitGonkCameraControl(this, aInitialConfig), NS_DISPATCH_NORMAL);
+  OnHardwareStateChange(CameraControlListener::kHardwareOpen);
+  return StartPreviewImpl();
 }
 
 nsresult
-nsGonkCameraControl::InitImpl()
+nsGonkCameraControl::Initialize()
 {
-  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
-
   mCameraHw = GonkCameraHardware::Connect(this, mCameraId);
   if (!mCameraHw.get()) {
     DOM_CAMERA_LOGE("Failed to connect to camera %d (this=%p)\n", mCameraId, this);
     return NS_ERROR_FAILURE;
   }
 
   DOM_CAMERA_LOGI("Initializing camera %d (this=%p, mCameraHw=%p)\n", mCameraId, this, mCameraHw.get());
 
@@ -206,17 +166,17 @@ nsGonkCameraControl::InitImpl()
 
   return NS_OK;
 }
 
 nsGonkCameraControl::~nsGonkCameraControl()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p, mCameraHw = %p\n", __func__, __LINE__, this, mCameraHw.get());
 
-  ReleaseHardwareImpl();
+  StopImpl();
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 }
 
 nsresult
 nsGonkCameraControl::SetConfigurationInternal(const Configuration& aConfig)
 {
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 
@@ -887,17 +847,17 @@ nsGonkCameraControl::StartRecordingImpl(
 
   if (mRecorder->start() != OK) {
     DOM_CAMERA_LOGE("mRecorder->start() failed\n");
     // important: we MUST destroy the recorder if start() fails!
     mRecorder = nullptr;
     return NS_ERROR_FAILURE;
   }
 
-  OnRecorderStateChange(CameraControlListener::kRecorderStarted, -1, -1);
+  OnRecorderStateChange(CameraControlListener::kRecorderStarted);
   return NS_OK;
 }
 
 nsresult
 nsGonkCameraControl::StopRecordingImpl()
 {
   class RecordingComplete : public nsRunnable
   {
@@ -924,17 +884,17 @@ nsGonkCameraControl::StopRecordingImpl()
 
   MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
 
   // nothing to do if we have no mRecorder
   NS_ENSURE_TRUE(mRecorder, NS_OK);
 
   mRecorder->stop();
   mRecorder = nullptr;
-  OnRecorderStateChange(CameraControlListener::kRecorderStopped, -1, -1);
+  OnRecorderStateChange(CameraControlListener::kRecorderStopped);
 
   // notify DeviceStorage that the new video file is closed and ready
   return NS_DispatchToMainThread(new RecordingComplete(mVideoFile), NS_DISPATCH_NORMAL);
 }
 
 void
 nsGonkCameraControl::OnAutoFocusComplete(bool aSuccess)
 {
@@ -1334,25 +1294,26 @@ nsGonkCameraControl::SetupRecording(int 
 
   // recording API needs file descriptor of output file
   CHECK_SETARG(mRecorder->setOutputFile(aFd, 0, 0));
   CHECK_SETARG(mRecorder->prepare());
   return NS_OK;
 }
 
 nsresult
-nsGonkCameraControl::ReleaseHardwareImpl()
+nsGonkCameraControl::StopImpl()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
 
   // if we're recording, stop recording
   if (mRecorder) {
-    DOM_CAMERA_LOGI("shutting down existing video recorder\n");
+    DOM_CAMERA_LOGI("Stopping existing video recorder\n");
     mRecorder->stop();
     mRecorder = nullptr;
+    OnRecorderStateChange(CameraControlListener::kRecorderStopped);
   }
 
   // stop the preview
   StopPreviewImpl();
 
   // release the hardware handle
   if (mCameraHw.get()){
      mCameraHw->Close();
--- a/dom/camera/GonkCameraControl.h
+++ b/dom/camera/GonkCameraControl.h
@@ -1,10 +1,10 @@
 /*
- * Copyright (C) 2012-2013 Mozilla Foundation
+ * Copyright (C) 2012-2014 Mozilla Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
@@ -42,18 +42,16 @@ namespace layers {
 
 class GonkRecorderProfile;
 class GonkRecorderProfileManager;
 
 class nsGonkCameraControl : public CameraControlImpl
 {
 public:
   nsGonkCameraControl(uint32_t aCameraId);
-  nsresult Init(const Configuration* aInitialConfig);
-  nsresult InitImpl();
 
   void OnAutoFocusComplete(bool aSuccess);
   void OnTakePictureComplete(uint8_t* aData, uint32_t aLength);
   void OnTakePictureError();
   void OnNewPreviewFrame(layers::GraphicBufferLocked* aBuffer);
   void OnRecorderEvent(int msg, int ext1, int ext2);
   void OnError(CameraControlListener::CameraErrorContext aWhere,
                CameraControlListener::CameraError aError);
@@ -87,33 +85,36 @@ protected:
   using CameraControlImpl::OnAutoFocusComplete;
   using CameraControlImpl::OnTakePictureComplete;
   using CameraControlImpl::OnConfigurationChange;
   using CameraControlImpl::OnError;
 
   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);
 
   virtual nsresult StartPreviewImpl() MOZ_OVERRIDE;
   virtual nsresult StopPreviewImpl() MOZ_OVERRIDE;
   virtual nsresult AutoFocusImpl(bool aCancelExistingCall) MOZ_OVERRIDE;
   virtual nsresult TakePictureImpl() MOZ_OVERRIDE;
   virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
                                       const StartRecordingOptions* aOptions = nullptr) MOZ_OVERRIDE;
   virtual nsresult StopRecordingImpl() MOZ_OVERRIDE;
   virtual nsresult PushParametersImpl() MOZ_OVERRIDE;
   virtual nsresult PullParametersImpl() MOZ_OVERRIDE;
-  virtual nsresult ReleaseHardwareImpl() MOZ_OVERRIDE;
   virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl() MOZ_OVERRIDE;
   already_AddRefed<GonkRecorderProfileManager> GetGonkRecorderProfileManager();
 
   nsresult SetupRecording(int aFd, int aRotation, int64_t aMaxFileSizeBytes, int64_t aMaxVideoLengthMs);
   nsresult SetupVideoMode(const nsAString& aProfile);
   nsresult SetPreviewSize(const Size& aSize);
 
   friend class SetPictureSize;
--- a/dom/camera/GonkCameraHwMgr.cpp
+++ b/dom/camera/GonkCameraHwMgr.cpp
@@ -1,50 +1,50 @@
 /*
- * Copyright (C) 2012-2013 Mozilla Foundation
+ * Copyright (C) 2012-2014 Mozilla Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "GonkCameraHwMgr.h"
+#include "TestGonkCameraHardware.h"
 
 #include <binder/IPCThreadState.h>
 #include <sys/system_properties.h>
 
 #include "base/basictypes.h"
 #include "nsDebug.h"
+#include "mozilla/Preferences.h"
 #include "GonkCameraControl.h"
 #include "GonkNativeWindow.h"
 #include "CameraCommon.h"
 
 using namespace mozilla;
 using namespace mozilla::layers;
 using namespace android;
 
 GonkCameraHardware::GonkCameraHardware(mozilla::nsGonkCameraControl* aTarget, uint32_t aCameraId, const sp<Camera>& aCamera)
   : mCameraId(aCameraId)
   , mClosing(false)
   , mNumFrames(0)
   , mCamera(aCamera)
   , mTarget(aTarget)
-  , mInitialized(false)
   , mSensorOrientation(0)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p (aTarget=%p)\n", __func__, __LINE__, (void*)this, (void*)aTarget);
-  Init();
 }
 
 void
 GonkCameraHardware::OnNewFrame()
 {
   if (mClosing) {
     return;
   }
@@ -122,26 +122,26 @@ GonkCameraHardware::postDataTimestamp(ns
     DOM_CAMERA_LOGI("Listener registered, posting recording frame!");
     mListener->postDataTimestamp(aTimestamp, aMsgType, aDataPtr);
   } else {
     DOM_CAMERA_LOGW("No listener was set. Drop a recording frame.");
     mCamera->releaseRecordingFrame(aDataPtr);
   }
 }
 
-void
+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;
+    return NS_ERROR_FAILURE;
    }
 
   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.
@@ -163,57 +163,73 @@ GonkCameraHardware::Init()
   mNativeWindow = new GonkNativeWindow();
   mNativeWindow->setNewFrameCallback(this);
   mCamera->setListener(this);
 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
   mCamera->setPreviewTexture(mNativeWindow->getBufferQueue());
 #else
   mCamera->setPreviewTexture(mNativeWindow);
 #endif
-  mInitialized = true;
+
+  return NS_OK;
 }
 
 sp<GonkCameraHardware>
 GonkCameraHardware::Connect(mozilla::nsGonkCameraControl* aTarget, uint32_t aCameraId)
 {
 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 18
   sp<Camera> camera = Camera::connect(aCameraId, /* clientPackageName */String16("gonk.camera"), Camera::USE_CALLING_UID);
 #else
   sp<Camera> camera = Camera::connect(aCameraId);
 #endif
 
-
   if (camera.get() == nullptr) {
     return nullptr;
   }
-  sp<GonkCameraHardware> cameraHardware = new GonkCameraHardware(aTarget, aCameraId, camera);
+
+  const nsAdoptingCString& test =
+    mozilla::Preferences::GetCString("camera.control.test.enabled");
+  sp<GonkCameraHardware> cameraHardware;
+  if (test.EqualsASCII("hardware")) {
+    NS_WARNING("Using test Gonk hardware layer");
+    cameraHardware = new TestGonkCameraHardware(aTarget, aCameraId, camera);
+  } else {
+    cameraHardware = new GonkCameraHardware(aTarget, aCameraId, camera);
+  }
+
+  nsresult rv = cameraHardware->Init();
+  if (NS_FAILED(rv)) {
+    DOM_CAMERA_LOGE("Failed to initialize camera hardware (0x%X)\n", rv);
+    return nullptr;
+  }
+
   return cameraHardware;
- }
+}
 
 void
 GonkCameraHardware::Close()
 {
-  DOM_CAMERA_LOGT( "%s:%d : this=%p\n", __func__, __LINE__, (void*)this );
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, (void*)this);
 
   mClosing = true;
   mCamera->stopPreview();
   mCamera->disconnect();
   if (mNativeWindow.get()) {
     mNativeWindow->abandon();
   }
   mCamera.clear();
   mNativeWindow.clear();
 
   // Ensure that ICamera's destructor is actually executed
   IPCThreadState::self()->flushCommands();
 }
 
 GonkCameraHardware::~GonkCameraHardware()
 {
-  DOM_CAMERA_LOGT( "%s:%d : this=%p\n", __func__, __LINE__, (void*)this );
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, (void*)this);
   mCamera.clear();
   mNativeWindow.clear();
 
   if (mClosing) {
     return;
   }
 
   /**
@@ -303,17 +319,17 @@ GonkCameraHardware::StartPreview()
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   return mCamera->startPreview();
 }
 
 void
 GonkCameraHardware::StopPreview()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  return mCamera->stopPreview();
+  mCamera->stopPreview();
 }
 
 int
 GonkCameraHardware::StartRecording()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   int rv = OK;
 
--- a/dom/camera/GonkCameraHwMgr.h
+++ b/dom/camera/GonkCameraHwMgr.h
@@ -1,10 +1,10 @@
 /*
- * Copyright (C) 2012 Mozilla Foundation
+ * Copyright (C) 2012-2014 Mozilla Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
@@ -38,24 +38,24 @@ namespace mozilla {
 namespace android {
 
 class GonkCameraHardware : public GonkNativeWindowNewFrameCallback
                          , public CameraListener
 {
 protected:
   GonkCameraHardware(mozilla::nsGonkCameraControl* aTarget, uint32_t aCameraId, const sp<Camera>& aCamera);
   virtual ~GonkCameraHardware();
-  void Init();
+  virtual nsresult Init();
 
 public:
-  static  sp<GonkCameraHardware>  Connect(mozilla::nsGonkCameraControl* aTarget, uint32_t aCameraId);
-  void    Close();
+  static sp<GonkCameraHardware> Connect(mozilla::nsGonkCameraControl* aTarget, uint32_t aCameraId);
+  virtual void Close();
 
   // derived from GonkNativeWindowNewFrameCallback
-  virtual void    OnNewFrame() MOZ_OVERRIDE;
+  virtual void OnNewFrame() MOZ_OVERRIDE;
 
   // derived from CameraListener
   virtual void notify(int32_t aMsgType, int32_t ext1, int32_t ext2);
   virtual void postData(int32_t aMsgType, const sp<IMemory>& aDataPtr, camera_frame_metadata_t* metadata);
   virtual void postDataTimestamp(nsecs_t aTimestamp, int32_t aMsgType, const sp<IMemory>& aDataPtr);
 
   /**
    * The physical orientation of the camera sensor: 0, 90, 180, or 270.
@@ -70,52 +70,45 @@ public:
    * RAW_SENSOR_ORIENTATION is the uncorrected orientation returned directly
    * by get_camera_info(); OFFSET_SENSOR_ORIENTATION is the offset adjusted
    * orientation.
    */
   enum {
     RAW_SENSOR_ORIENTATION,
     OFFSET_SENSOR_ORIENTATION
   };
-  int      GetSensorOrientation(uint32_t aType = RAW_SENSOR_ORIENTATION);
+  virtual int      GetSensorOrientation(uint32_t aType = RAW_SENSOR_ORIENTATION);
 
-  int      AutoFocus();
-  void     CancelAutoFocus();
-  int      TakePicture();
-  void     CancelTakePicture();
-  int      StartPreview();
-  void     StopPreview();
-  int      PushParameters(const mozilla::GonkCameraParameters& aParams);
-  int      PushParameters(const CameraParameters& aParams);
-  nsresult PullParameters(mozilla::GonkCameraParameters& aParams);
-  void     PullParameters(CameraParameters& aParams);
-  int      StartRecording();
-  int      StopRecording();
-  int      SetListener(const sp<GonkCameraListener>& aListener);
-  void     ReleaseRecordingFrame(const sp<IMemory>& aFrame);
-  int      StoreMetaDataInBuffers(bool aEnabled);
+  virtual int      AutoFocus();
+  virtual void     CancelAutoFocus();
+  virtual int      TakePicture();
+  virtual void     CancelTakePicture();
+  virtual int      StartPreview();
+  virtual void     StopPreview();
+  virtual int      PushParameters(const mozilla::GonkCameraParameters& aParams);
+  virtual int      PushParameters(const CameraParameters& aParams);
+  virtual nsresult PullParameters(mozilla::GonkCameraParameters& aParams);
+  virtual void     PullParameters(CameraParameters& aParams);
+  virtual int      StartRecording();
+  virtual int      StopRecording();
+  virtual int      SetListener(const sp<GonkCameraListener>& aListener);
+  virtual void     ReleaseRecordingFrame(const sp<IMemory>& aFrame);
+  virtual int      StoreMetaDataInBuffers(bool aEnabled);
 
 protected:
-
   uint32_t                      mCameraId;
   bool                          mClosing;
   uint32_t                      mNumFrames;
   sp<Camera>                    mCamera;
   mozilla::nsGonkCameraControl* mTarget;
   sp<GonkNativeWindow>          mNativeWindow;
   sp<GonkCameraListener>        mListener;
-  bool                          mInitialized;
   int                           mRawSensorOrientation;
   int                           mSensorOrientation;
 
-  bool IsInitialized()
-  {
-    return mInitialized;
-  }
-
 private:
   GonkCameraHardware(const GonkCameraHardware&) MOZ_DELETE;
   GonkCameraHardware& operator=(const GonkCameraHardware&) MOZ_DELETE;
 };
 
 } // namespace android
 
 #endif // GONK_IMPL_HW_MGR_H
--- a/dom/camera/GonkCameraManager.cpp
+++ b/dom/camera/GonkCameraManager.cpp
@@ -1,10 +1,10 @@
 /*
- * Copyright (C) 2012 Mozilla Foundation
+ * Copyright (C) 2012-2014 Mozilla Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
@@ -110,23 +110,13 @@ ICameraControl::GetListOfCameras(nsTArra
     aList.RemoveElementAt(0);
   }
 
   return NS_OK;
 }
 
 // implementation-specific camera factory
 already_AddRefed<ICameraControl>
-ICameraControl::Create(uint32_t aCameraId, const Configuration* aInitialConfig)
+ICameraControl::Create(uint32_t aCameraId)
 {
-  if (aInitialConfig) {
-    DOM_CAMERA_LOGI("Creating camera %d control, initial mode '%s'\n",
-      aCameraId, aInitialConfig->mMode == kVideoMode ? "video" : "picture");
-  } else {
-    DOM_CAMERA_LOGI("Creating camera %d control, no intial configuration\n", aCameraId);
-  }
-
   nsRefPtr<nsGonkCameraControl> control = new nsGonkCameraControl(aCameraId);
-  nsresult rv = control->Init(aInitialConfig);
-  NS_ENSURE_SUCCESS(rv, nullptr);
-
   return control.forget();
 }
--- a/dom/camera/ICameraControl.h
+++ b/dom/camera/ICameraControl.h
@@ -112,31 +112,33 @@ public:
     uint32_t  maxVideoLengthMs;
   };
 
   struct Configuration {
     Mode      mMode;
     Size      mPreviewSize;
     nsString  mRecorderProfile;
   };
-  static already_AddRefed<ICameraControl> Create(uint32_t aCameraId,
-                                                 const Configuration* aInitialConfig);
+  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;
 
   virtual nsresult StartPreview() = 0;
   virtual nsresult StopPreview() = 0;
   virtual nsresult AutoFocus(bool aCancelExistingCall) = 0;
   virtual nsresult TakePicture() = 0;
   virtual nsresult StartRecording(DeviceStorageFileDescriptor *aFileDescriptor,
                                   const StartRecordingOptions* aOptions = nullptr) = 0;
   virtual nsresult StopRecording() = 0;
-  virtual nsresult ReleaseHardware() = 0;
 
   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;
new file mode 100644
--- /dev/null
+++ b/dom/camera/TestGonkCameraHardware.cpp
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2013-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 "TestGonkCameraHardware.h"
+
+#include "mozilla/Preferences.h"
+#include "nsThreadUtils.h"
+
+using namespace android;
+using namespace mozilla;
+
+TestGonkCameraHardware::TestGonkCameraHardware(nsGonkCameraControl* aTarget,
+                                               uint32_t aCameraId,
+                                               const sp<Camera>& aCamera)
+  : GonkCameraHardware(aTarget, aCameraId, aCamera)
+{
+  DOM_CAMERA_LOGA("+===== Created TestGonkCameraHardware =====+\n");
+  DOM_CAMERA_LOGT("%s:%d : this=%p (aTarget=%p)\n",
+    __func__, __LINE__, this, aTarget);
+  MOZ_COUNT_CTOR(TestGonkCameraHardware);
+}
+
+TestGonkCameraHardware::~TestGonkCameraHardware()
+{
+  MOZ_COUNT_DTOR(TestGonkCameraHardware);
+  DOM_CAMERA_LOGA("+===== Destroyed TestGonkCameraHardware =====+\n");
+}
+
+nsresult
+TestGonkCameraHardware::Init()
+{
+  if (IsTestCase("init-failure")) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return GonkCameraHardware::Init();
+}
+
+const nsCString
+TestGonkCameraHardware::TestCase()
+{
+  const nsCString test = Preferences::GetCString("camera.control.test.hardware");
+  return test;
+}
+
+bool
+TestGonkCameraHardware::IsTestCaseInternal(const char* aTest, const char* aFile, int aLine)
+{
+  if (TestCase().EqualsASCII(aTest)) {
+    DOM_CAMERA_LOGA("TestGonkCameraHardware : test-case '%s' (%s:%d)\n",
+      aTest, aFile, aLine);
+    return true;
+  }
+
+  return false;
+}
+
+int
+TestGonkCameraHardware::TestCaseError(int aDefaultError)
+{
+  // for now, just return the default error
+  return aDefaultError;
+}
+
+int
+TestGonkCameraHardware::AutoFocus()
+{
+  class AutoFocusFailure : public nsRunnable
+  {
+  public:
+    AutoFocusFailure(nsGonkCameraControl* aTarget)
+      : mTarget(aTarget)
+    { }
+
+    NS_IMETHODIMP
+    Run()
+    {
+      OnAutoFocusComplete(mTarget, false);
+      return NS_OK;
+    }
+
+  protected:
+    nsGonkCameraControl* mTarget;
+  };
+
+  if (IsTestCase("auto-focus-failure")) {
+    return TestCaseError(UNKNOWN_ERROR);
+  }
+  if (IsTestCase("auto-focus-process-failure")) {
+    nsresult rv = NS_DispatchToCurrentThread(new AutoFocusFailure(mTarget));
+    if (NS_SUCCEEDED(rv)) {
+      return OK;
+    }
+    DOM_CAMERA_LOGE("Failed to dispatch AutoFocusFailure runnable (0x%08x)\n", rv);
+    return UNKNOWN_ERROR;
+  }
+
+  return GonkCameraHardware::AutoFocus();
+}
+
+int
+TestGonkCameraHardware::TakePicture()
+{
+  class TakePictureFailure : public nsRunnable
+  {
+  public:
+    TakePictureFailure(nsGonkCameraControl* aTarget)
+      : mTarget(aTarget)
+    { }
+
+    NS_IMETHODIMP
+    Run()
+    {
+      OnTakePictureError(mTarget);
+      return NS_OK;
+    }
+
+  protected:
+    nsGonkCameraControl* mTarget;
+  };
+
+  if (IsTestCase("take-picture-failure")) {
+    return TestCaseError(UNKNOWN_ERROR);
+  }
+  if (IsTestCase("take-picture-process-failure")) {
+    nsresult rv = NS_DispatchToCurrentThread(new TakePictureFailure(mTarget));
+    if (NS_SUCCEEDED(rv)) {
+      return OK;
+    }
+    DOM_CAMERA_LOGE("Failed to dispatch TakePictureFailure runnable (0x%08x)\n", rv);
+    return UNKNOWN_ERROR;
+  }
+
+  return GonkCameraHardware::TakePicture();
+}
+
+int
+TestGonkCameraHardware::StartPreview()
+{
+  if (IsTestCase("start-preview-failure")) {
+    return TestCaseError(UNKNOWN_ERROR);
+  }
+
+  return GonkCameraHardware::StartPreview();
+}
+
+int
+TestGonkCameraHardware::PushParameters(const GonkCameraParameters& aParams)
+{
+  if (IsTestCase("push-parameters-failure")) {
+    return TestCaseError(UNKNOWN_ERROR);
+  }
+
+  return GonkCameraHardware::PushParameters(aParams);
+}
+
+nsresult
+TestGonkCameraHardware::PullParameters(GonkCameraParameters& aParams)
+{
+  if (IsTestCase("pull-parameters-failure")) {
+    return static_cast<nsresult>(TestCaseError(UNKNOWN_ERROR));
+  }
+
+  return GonkCameraHardware::PullParameters(aParams);
+}
+
+int
+TestGonkCameraHardware::StartRecording()
+{
+  if (IsTestCase("start-recording-failure")) {
+    return TestCaseError(UNKNOWN_ERROR);
+  }
+
+  return GonkCameraHardware::StartRecording();
+}
+
+int
+TestGonkCameraHardware::StopRecording()
+{
+  if (IsTestCase("stop-recording-failure")) {
+    return TestCaseError(UNKNOWN_ERROR);
+  }
+
+  return GonkCameraHardware::StopRecording();
+}
+
+int
+TestGonkCameraHardware::SetListener(const sp<GonkCameraListener>& aListener)
+{
+  if (IsTestCase("set-listener-failure")) {
+    return TestCaseError(UNKNOWN_ERROR);
+  }
+
+  return GonkCameraHardware::SetListener(aListener);
+}
+
+int
+TestGonkCameraHardware::StoreMetaDataInBuffers(bool aEnabled)
+{
+  if (IsTestCase("store-metadata-in-buffers-failure")) {
+    return TestCaseError(UNKNOWN_ERROR);
+  }
+
+  return GonkCameraHardware::StoreMetaDataInBuffers(aEnabled);
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/TestGonkCameraHardware.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2013-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_TESTGONKCAMERAHARDWARE_H
+#define DOM_CAMERA_TESTGONKCAMERAHARDWARE_H
+
+#include "GonkCameraHwMgr.h"
+
+namespace android {
+
+class TestGonkCameraHardware : public android::GonkCameraHardware
+{
+public:
+  virtual int AutoFocus() MOZ_OVERRIDE;
+  virtual int TakePicture() MOZ_OVERRIDE;
+  virtual int StartPreview() MOZ_OVERRIDE;
+  virtual int PushParameters(const mozilla::GonkCameraParameters& aParams) MOZ_OVERRIDE;
+  virtual nsresult PullParameters(mozilla::GonkCameraParameters& aParams) MOZ_OVERRIDE;
+  virtual int StartRecording() MOZ_OVERRIDE;
+  virtual int StopRecording() MOZ_OVERRIDE;
+  virtual int SetListener(const sp<GonkCameraListener>& aListener) MOZ_OVERRIDE;
+  virtual int StoreMetaDataInBuffers(bool aEnabled) MOZ_OVERRIDE;
+
+  virtual int
+  PushParameters(const CameraParameters& aParams) MOZ_OVERRIDE
+  {
+    return GonkCameraHardware::PushParameters(aParams);
+  }
+
+  virtual void
+  PullParameters(CameraParameters& aParams) MOZ_OVERRIDE
+  {
+    GonkCameraHardware::PullParameters(aParams);
+  }
+
+  TestGonkCameraHardware(mozilla::nsGonkCameraControl* aTarget,
+                         uint32_t aCameraId,
+                         const sp<Camera>& aCamera);
+  virtual ~TestGonkCameraHardware();
+
+  virtual nsresult Init() MOZ_OVERRIDE;
+
+protected:
+  const nsCString TestCase();
+  bool IsTestCaseInternal(const char* aTest, const char* aFile, int aLine);
+  int TestCaseError(int aDefaultError);
+
+private:
+  TestGonkCameraHardware(const TestGonkCameraHardware&) MOZ_DELETE;
+  TestGonkCameraHardware& operator=(const TestGonkCameraHardware&) MOZ_DELETE;
+};
+
+#define IsTestCase(test)  IsTestCaseInternal((test), __FILE__, __LINE__)
+
+} // namespace android
+
+#endif // DOM_CAMERA_TESTGONKCAMERAHARDWARE_H
--- a/dom/camera/moz.build
+++ b/dom/camera/moz.build
@@ -28,16 +28,17 @@ if CONFIG['MOZ_B2G_CAMERA']:
     SOURCES += [
         'GonkCameraControl.cpp',
         'GonkCameraHwMgr.cpp',
         'GonkCameraManager.cpp',
         'GonkCameraParameters.cpp',
         'GonkCameraSource.cpp',
         'GonkRecorder.cpp',
         'GonkRecorderProfiles.cpp',
+        'TestGonkCameraHardware.cpp',
     ]
 else:
     SOURCES += [
         'FallbackCameraControl.cpp',
         'FallbackCameraManager.cpp',
     ]
 
 FAIL_ON_WARNINGS = True
new file mode 100644
--- /dev/null
+++ b/dom/camera/test/camera_common.js
@@ -0,0 +1,105 @@
+var CameraTest = (function() {
+  'use strict';
+
+  /**
+   * 'camera.control.test.enabled' is queried in Gecko to enable different
+   * test modes in the camera stack. The only currently-supported setting
+   * is 'hardware', which wraps the Gonk camera abstraction class in a
+   * shim class that supports injecting hardware camera API failures into
+   * the execution path.
+   *
+   * The affected API is specified by the 'camera.control.test.hardware'
+   * pref. Currently supported values should be determined by inspecting
+   * TestGonkCameraHardware.cpp.
+   *
+   * Some API calls are simple: e.g. 'start-recording-failure' will cause
+   * the DOM-facing startRecording() call to fail. More complex tests like
+   * 'take-picture-failure' will cause the takePicture() API to fail, while
+   * 'take-picture-process-failure' will simulate a failure of the
+   * asynchronous picture-taking process, even if the initial API call
+   * path seems to have succeeded.
+   */
+  const PREF_TEST_ENABLED = "camera.control.test.enabled";
+  const PREF_TEST_HARDWARE = "camera.control.test.hardware";
+  var oldTestEnabled;
+  var oldTestHw;
+  var testMode;
+
+  function testHardwareSet(test, callback) {
+    SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_HARDWARE, test]]}, function() {
+      var setTest = SpecialPowers.getCharPref(PREF_TEST_HARDWARE);
+      ise(setTest, test, "Test subtype set to " + setTest);
+      if (callback) {
+        callback(setTest);
+      }
+    });
+  }
+
+  function testHardwareDone(callback) {
+    testMode = null;
+    if (oldTestHw) {
+      SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_HARDWARE, oldTestHw]]}, callback);
+    } else {
+      SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_HARDWARE]]}, callback);
+    }
+  }
+
+  function testBegin(mode, callback) {
+    SimpleTest.waitForExplicitFinish();
+    try {
+      oldTestEnabled = SpecialPowers.getCharPref(PREF_TEST_ENABLED);
+    } catch(e) { }
+    SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_ENABLED, mode]]}, function() {
+      var setMode = SpecialPowers.getCharPref(PREF_TEST_ENABLED);
+      ise(setMode, mode, "Test mode set to " + setMode);
+      if (setMode === "hardware") {
+        try {
+          oldTestHw = SpecialPowers.getCharPref(PREF_TEST_HARDWARE);
+        } catch(e) { }
+        testMode = {
+          set: testHardwareSet,
+          done: testHardwareDone
+        };
+        if (callback) {
+          callback(testMode);
+        }
+      }
+    });
+  }
+
+  function testEnd(callback) {
+    function allDone(cb) {
+      function cb2() {
+        SimpleTest.finish();
+        if (cb) {
+          cb();
+        }
+      }
+      if (oldTestEnabled) {
+        SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_ENABLED, oldTestEnabled]]}, cb2);
+      } else {
+        SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_ENABLED]]}, cb2);
+      }
+    }
+
+    if (testMode) {
+      testMode.done(function() {
+        allDone(callback);
+      });
+      testMode = null;
+    } else {
+      allDone(function() {
+        if (callback) {
+          callback();
+        }
+      });
+    }
+  }
+
+  ise(SpecialPowers.sanityCheck(), "foo", "SpecialPowers passed sanity check");
+  return {
+    begin: testBegin,
+    end: testEnd
+  };
+
+})();
--- a/dom/camera/test/mochitest.ini
+++ b/dom/camera/test/mochitest.ini
@@ -1,4 +1,8 @@
 [DEFAULT]
+support-files = camera_common.js
 
 [test_camera.html]
 [test_camera_2.html]
+[test_camera_3.html]
+[test_camera_hardware_init_failure.html]
+[test_camera_hardware_failures.html]
--- a/dom/camera/test/test_camera.html
+++ b/dom/camera/test/test_camera.html
@@ -2,17 +2,17 @@
 <html>
 <head>
   <title>Test for mozCameras.getCamera() with separate .setConfiguration() call</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<video id="viewfinder" |width = "200" height = "200| autoplay></video>
+<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];
 var options = {
   mode: 'picture',
   recorderProfile: 'cif',
   previewSize: {
@@ -25,19 +25,16 @@ var config = {
   dateTime: Date.now() / 1000,
   pictureSize: null,
   fileFormat: 'jpeg',
   rotation: 90
 };
 
 function onError(e) {
   ok(false, "Error" + JSON.stringify(e));
-  Camera.cameraObj.release();
-  Camera.cameraObj = null;
-  Camera.viewfinder.mozSrcObject = null;
 }
 
 var capabilities = [ 'previewSizes', 'pictureSizes', 'fileFormats', 'maxFocusAreas', 'minExposureCompensation',
                      'maxExposureCompensation', 'stepExposureCompensation', 'maxMeteringAreas', 'videoSizes', 
                      'recorderProfiles'];
 
 var Camera = {
   cameraObj: null,
@@ -100,20 +97,16 @@ var Camera = {
     img.onload = function Imgsize() {
       ok(this.width == test.pictureSize.width, "The image taken has the width " + 
                                               this.width + " pictureSize width = " + test.pictureSize.width);
       ok(this.height == test.pictureSize.height, "The image taken has the height " + 
                                               this.height + " picturesize height = " + test.pictureSize.height);
       Camera._testsCompleted++;
       if(Camera._testsCompleted == Camera._tests.length) {
         ok(true, "test finishing");
-        Camera.cameraObj.release(function() {
-          Camera.cameraObj = null;
-        }, onError);
-        Camera.viewfinder.mozSrcObject = null;
         SimpleTest.finish();
       } else {
         Camera.runTests();
       }
     }
     ok(blob.size > 100 , "Blob Size Gathered = " + blob.size);
     ok("image/" + test.fileFormat ==  blob.type, "Blob Type = " + blob.type);
     img.src = window.URL.createObjectURL(blob);
@@ -195,16 +188,18 @@ var Camera = {
     navigator.mozCameras.getCamera(whichCamera, null, onSuccess, onError);
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 
 window.addEventListener('beforeunload', function() {
   Camera.viewfinder.mozSrcObject = null;
+  Camera.cameraObj.release();
+  Camera.cameraObj = null;
 });
 
 Camera.setUp();
 
 </script>
 </body>
 
 </html>
--- a/dom/camera/test/test_camera_2.html
+++ b/dom/camera/test/test_camera_2.html
@@ -2,17 +2,17 @@
 <html>
 <head>
   <title>Test for mozCameras.getCamera() using an initial configuration</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<video id="viewfinder" |width = "200" height = "200| autoplay></video>
+<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];
 var options = {
   mode: 'picture',
   recorderProfile: 'cif',
   previewSize: {
@@ -25,19 +25,16 @@ var config = {
   dateTime: Date.now() / 1000,
   pictureSize: null,
   fileFormat: 'jpeg',
   rotation: 90
 };
 
 function onError(e) {
   ok(false, "Error" + JSON.stringify(e));
-  Camera.cameraObj.release();
-  Camera.cameraObj = null;
-  Camera.viewfinder.mozSrcObject = null;
 }
 
 var capabilities = [ 'previewSizes', 'pictureSizes', 'fileFormats', 'maxFocusAreas', 'minExposureCompensation',
                      'maxExposureCompensation', 'stepExposureCompensation', 'maxMeteringAreas', 'videoSizes',
                      'recorderProfiles'];
 
 var Camera = {
   cameraObj: null,
@@ -100,20 +97,16 @@ var Camera = {
     img.onload = function Imgsize() {
       ok(this.width == test.pictureSize.width, "The image taken has the width " +
                                               this.width + " pictureSize width = " + test.pictureSize.width);
       ok(this.height == test.pictureSize.height, "The image taken has the height " +
                                               this.height + " picturesize height = " + test.pictureSize.height);
       Camera._testsCompleted++;
       if(Camera._testsCompleted == Camera._tests.length) {
         ok(true, "test finishing");
-        Camera.cameraObj.release(function() {
-          Camera.cameraObj = null;
-        }, onError);
-        Camera.viewfinder.mozSrcObject = null;
         SimpleTest.finish();
       } else {
         Camera.runTests();
       }
     }
     ok(blob.size > 100 , "Blob Size Gathered = " + blob.size);
     ok("image/" + test.fileFormat ==  blob.type, "Blob Type = " + blob.type);
     img.src = window.URL.createObjectURL(blob);
@@ -190,16 +183,18 @@ var Camera = {
     navigator.mozCameras.getCamera(whichCamera, options, onSuccess, onError);
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 
 window.addEventListener('beforeunload', function() {
   Camera.viewfinder.mozSrcObject = null;
+  Camera.cameraObj.release();
+  Camera.cameraObj = null;
 });
 
 Camera.setUp();
 
 </script>
 </body>
 
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/camera/test/test_camera_3.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for multiple calls to mozCameras.getCamera()</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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];
+var options = {
+  mode: 'picture',
+  recorderProfile: 'cif',
+  previewSize: {
+    width: 352,
+    height: 288
+  }
+};
+
+function onError(e) {
+  ok(false, "Error" + JSON.stringify(e));
+}
+
+var Camera = {
+  cameraObj: null,
+  get viewfinder() {
+    return document.getElementById('viewfinder');
+  },
+  onReady: function take_two() {
+    function onSuccess(camera, config) {
+      ok(false, "Unexpectedly got second camera instance: " + config.toSource);
+    }
+    function onFailure(error) {
+      ok(true, "Correctly failed to get camera again");
+      SimpleTest.finish();
+    }
+    navigator.mozCameras.getCamera(whichCamera, options, onSuccess, onFailure);
+  },
+  release: function release() {
+    cameraObj = null;
+  },
+  start: function run_test() {
+    function onSuccess(camera, config) {
+      Camera.cameraObj = camera;
+      Camera.viewfinder.mozSrcObject = camera;
+      Camera.viewfinder.play();
+      Camera.cameraObj.onPreviewStateChange = function(state) {
+        if (state === 'started') {
+          ok(true, "viewfinder is ready and playing");
+          Camera.cameraObj.onPreviewStateChange = null;
+          Camera.onReady();
+        }
+      };
+    };
+    navigator.mozCameras.getCamera(whichCamera, options, onSuccess, onError);
+  }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener('beforeunload', function() {
+  Camera.viewfinder.mozSrcObject = null;
+  Camera.cameraObj.release();
+  Camera.cameraObj = null;
+});
+
+Camera.start();
+
+</script>
+</body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/camera/test/test_camera_hardware_failures.html
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=940424
+-->
+<head>
+  <title>Bug 940424 - Test camera hardware API failure handling</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>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=940424">Mozilla Bug 940424</a>
+  <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];
+var initialConfig = {
+  mode: 'picture',
+  recorderProfile: 'cif',
+  previewSize: {
+    width: 352,
+    height: 288
+  }
+};
+
+var cameraObj;
+
+// Shorthand functions
+function end() {
+  CameraTest.end();
+}
+function next() {
+  CameraTest.next();
+}
+
+// The array of tests
+var tests = [
+  {
+    key: "auto-focus-failure",
+    func: function testAutoFocusApiFailure(camera) {
+      function onSuccess(success) {
+        ok(false, "autoFocus() succeeded incorrectly");
+        end();
+      }
+      function onError(error) {
+        ok(true, "autoFocus() failed correctly with: " + error);
+        next();
+      }
+      camera.autoFocus(onSuccess, onError);
+    }
+  },
+  {
+    key: "auto-focus-process-failure",
+    func: function testAutoFocusProcessFailure(camera) {
+      function onSuccess(success) {
+        if (success) {
+          ok(false, "autoFocus() process succeeded incorrectly");
+          end();
+        } else {
+          ok(true, "autoFocus() process failed correctly");
+          next();
+        }
+      }
+      function onError(error) {
+        ok(false, "autoFocus() process failed incorrectly with: " + error);
+        end();
+      }
+      camera.autoFocus(onSuccess, onError);
+    }
+  },
+  {
+    key: "take-picture-failure",
+    func: function testTakePictureApiFailure(camera) {
+      function onSuccess(picture) {
+        ok(false, "takePicture() succeeded incorrectly");
+        end();
+      }
+      function onError(error) {
+        ok(true, "takePicture() failed correctly with: " + error);
+        next();
+      }
+      camera.takePicture(null, onSuccess, onError);
+    }
+  },
+  {
+    key: "take-picture-process-failure",
+    func: function testTakePictureProcessFailure(camera) {
+      function onSuccess(picture) {
+        ok(false, "takePicture() process succeeded incorrectly");
+        end();
+      }
+      function onError(error) {
+        ok(true, "takePicture() process failed correctly with: " + error);
+        next();
+      }
+      camera.takePicture(null, onSuccess, onError);
+    }
+  },
+];
+
+var testGenerator = function() {
+  for (var i = 0; i < tests.length; ++i ) {
+    yield tests[i];
+  }
+}();
+
+window.addEventListener('beforeunload', function() {
+  document.getElementById('viewfinder').mozSrcObject = null;
+  cameraObj.release();
+  cameraObj = null;
+});
+
+CameraTest.begin("hardware", function(test) {
+  function onSuccess(camera, config) {
+    document.getElementById('viewfinder').mozSrcObject = camera;
+    cameraObj = camera;
+    CameraTest.next = function() {
+      try {
+        var t = testGenerator.next();
+        test.set(t.key, t.func.bind(undefined, camera));
+      } catch(e) {
+        if (e instanceof StopIteration) {
+          end();
+        } else {
+          throw e;
+        }
+      }
+    };
+    next();
+  }
+  function onError(error) {
+    ok(false, "getCamera() failed with: " + error);
+    end();
+  }
+  navigator.mozCameras.getCamera(whichCamera, initialConfig, onSuccess, onError);
+});
+
+</script>
+</body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/camera/test/test_camera_hardware_init_failure.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=940424
+-->
+<head>
+  <title>Bug 940424 - Test camera hardware init failure handling</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>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=940424">Mozilla Bug 940424</a>
+  <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];
+var initialConfig = {
+  mode: 'picture',
+  recorderProfile: 'cif',
+  previewSize: {
+    width: 352,
+    height: 288
+  }
+};
+
+function end() {
+  CameraTest.end();
+}
+
+CameraTest.begin("hardware", function(test) {
+  test.set("init-failure", function(type) {
+    function onSuccess(camera, config) {
+      ok(false, "onSuccess called incorrectly");
+      camera.release();
+      test.done(end);
+    }
+    function onError(error) {
+      ok(true, "onError called correctly on init failure");
+      test.done(end);
+    }
+    info("Running test: " + type);
+    navigator.mozCameras.getCamera(whichCamera, initialConfig, onSuccess, onError);
+  });
+});
+
+</script>
+</body>
+
+</html>