Bug 994912 - Add support for promises and event-based notifications to camera. r=mikeh r=bz
☠☠ backed out by 6850e4bf564c ☠ ☠
authorAndrew Osmond <aosmond@mozilla.com>
Wed, 24 Sep 2014 12:51:00 +0200
changeset 230418 8e754efd35cd5dea1291c84a99f9f78787519830
parent 230417 23b18c4d6f6a6fb8c88567783e4e4f42fecff0dc
child 230419 1caa84ba144c21a86328c7c52a85ad1d826fa9c4
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikeh, bz
bugs994912
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 994912 - Add support for promises and event-based notifications to camera. r=mikeh r=bz
content/base/src/nsGkAtomList.h
content/media/directshow/DirectShowReader.cpp
content/media/directshow/DirectShowUtils.cpp
content/media/directshow/DirectShowUtils.h
dom/camera/DOMCameraControl.cpp
dom/camera/DOMCameraControl.h
dom/camera/DOMCameraManager.cpp
dom/camera/DOMCameraManager.h
dom/camera/test/callback/test_bug1022766.html
dom/camera/test/callback/test_bug975472.html
dom/camera/test/callback/test_camera.html
dom/camera/test/callback/test_camera_2.html
dom/camera/test/callback/test_camera_3.html
dom/camera/test/callback/test_camera_hardware_auto_focus_moving_cb.html
dom/camera/test/callback/test_camera_hardware_face_detection.html
dom/camera/test/callback/test_camera_hardware_failures.html
dom/camera/test/callback/test_camera_hardware_init_failure.html
dom/camera/test/camera_common.js
dom/camera/test/mochitest.ini
dom/camera/test/test_bug1022766.html
dom/camera/test/test_bug1037322.html
dom/camera/test/test_bug975472.html
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_fake_parameters.html
dom/camera/test/test_camera_hardware_auto_focus_moving_cb.html
dom/camera/test/test_camera_hardware_face_detection.html
dom/camera/test/test_camera_hardware_failures.html
dom/camera/test/test_camera_hardware_init_failure.html
dom/events/test/test_all_synthetic_events.html
dom/tests/mochitest/general/test_interfaces.html
dom/webidl/CameraConfigurationEvent.webidl
dom/webidl/CameraControl.webidl
dom/webidl/CameraFacesDetectedEvent.webidl
dom/webidl/CameraManager.webidl
dom/webidl/CameraStateChangeEvent.webidl
dom/webidl/CameraUtil.webidl
dom/webidl/moz.build
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -694,16 +694,17 @@ GK_ATOM(onclick, "onclick")
 GK_ATOM(onclirmodechange, "onclirmodechange")
 GK_ATOM(onclose, "onclose")
 GK_ATOM(oncommand, "oncommand")
 GK_ATOM(oncommandupdate, "oncommandupdate")
 GK_ATOM(oncomplete, "oncomplete")
 GK_ATOM(oncompositionend, "oncompositionend")
 GK_ATOM(oncompositionstart, "oncompositionstart")
 GK_ATOM(oncompositionupdate, "oncompositionupdate")
+GK_ATOM(onconfigurationchange, "onconfigurationchange")
 GK_ATOM(onconnect, "onconnect")
 GK_ATOM(onconnected, "onconnected")
 GK_ATOM(onconnecting, "onconnecting")
 GK_ATOM(oncontextmenu, "oncontextmenu")
 GK_ATOM(oncopy, "oncopy")
 GK_ATOM(oncut, "oncut")
 GK_ATOM(ondatachange, "ondatachange")
 GK_ATOM(ondataerror, "ondataerror")
@@ -743,16 +744,17 @@ GK_ATOM(ondragleave, "ondragleave")
 GK_ATOM(ondragover, "ondragover")
 GK_ATOM(ondragstart, "ondragstart")
 GK_ATOM(ondrop, "ondrop")
 GK_ATOM(onenabled, "onenabled")
 GK_ATOM(onenterpincodereq, "onenterpincodereq")
 GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange")
 GK_ATOM(onerror, "onerror")
 GK_ATOM(onevicted, "onevicted")
+GK_ATOM(onfacesdetected, "onfacesdetected")
 GK_ATOM(onfailed, "onfailed")
 GK_ATOM(onfetch, "onfetch")
 GK_ATOM(onfocus, "onfocus")
 GK_ATOM(onfrequencychange, "onfrequencychange")
 GK_ATOM(onspeakerforcedchange, "onspeakerforcedchange")
 GK_ATOM(onget, "onget")
 GK_ATOM(ongroupchange, "ongroupchange")
 GK_ATOM(onhashchange, "onhashchange")
@@ -807,42 +809,46 @@ GK_ATOM(onoverflowchanged, "onoverflowch
 GK_ATOM(onpagehide, "onpagehide")
 GK_ATOM(onpageshow, "onpageshow")
 GK_ATOM(onpaint, "onpaint")
 GK_ATOM(onpairedstatuschanged, "onpairedstatuschanged")
 GK_ATOM(onpairingconfirmationreq, "onpairingconfirmationreq")
 GK_ATOM(onpairingconsentreq, "onpairingconsentreq")
 GK_ATOM(onpaste, "onpaste")
 GK_ATOM(onpendingchange, "onpendingchange")
+GK_ATOM(onpicture, "onpicture")
 GK_ATOM(onpopuphidden, "onpopuphidden")
 GK_ATOM(onpopuphiding, "onpopuphiding")
 GK_ATOM(onpopupshowing, "onpopupshowing")
 GK_ATOM(onpopupshown, "onpopupshown")
+GK_ATOM(onpreviewstatechange, "onpreviewstatechange")
 GK_ATOM(onradiostatechange, "onradiostatechange")
 GK_ATOM(onreaderror, "onreaderror")
 GK_ATOM(onreadsuccess, "onreadsuccess")
 GK_ATOM(onready, "onready")
 GK_ATOM(onreadystatechange, "onreadystatechange")
 GK_ATOM(onreceived, "onreceived")
+GK_ATOM(onrecorderstatechange, "onrecorderstatechange")
 GK_ATOM(onremoteheld, "onremoteheld")
 GK_ATOM(onremoteresumed, "onremoteresumed")
 GK_ATOM(onresourcetimingbufferfull, "onresourcetimingbufferfull")
 GK_ATOM(onretrieving, "onretrieving")
 GK_ATOM(onRequest, "onRequest")
 GK_ATOM(onrequestmediaplaystatus, "onrequestmediaplaystatus")
 GK_ATOM(onreset, "onreset")
 GK_ATOM(onresuming, "onresuming")
 GK_ATOM(onresize, "onresize")
 GK_ATOM(onscostatuschanged, "onscostatuschanged")
 GK_ATOM(onscroll, "onscroll")
 GK_ATOM(onselect, "onselect")
 GK_ATOM(onsending, "onsending")
 GK_ATOM(onsent, "onsent")
 GK_ATOM(onset, "onset")
 GK_ATOM(onshow, "onshow")
+GK_ATOM(onshutter, "onshutter")
 GK_ATOM(onstatechange, "onstatechange")
 GK_ATOM(onstatuschanged, "onstatuschanged")
 GK_ATOM(onstkcommand, "onstkcommand")
 GK_ATOM(onstksessionend, "onstksessionend")
 GK_ATOM(onsubmit, "onsubmit")
 GK_ATOM(onsuccess, "onsuccess")
 GK_ATOM(ontypechange, "ontypechange")
 GK_ATOM(ontext, "ontext")
--- a/content/media/directshow/DirectShowReader.cpp
+++ b/content/media/directshow/DirectShowReader.cpp
@@ -2,20 +2,19 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DirectShowReader.h"
 #include "MediaDecoderReader.h"
 #include "mozilla/RefPtr.h"
-#include "dshow.h"
+#include "DirectShowUtils.h"
 #include "AudioSinkFilter.h"
 #include "SourceFilter.h"
-#include "DirectShowUtils.h"
 #include "SampleSink.h"
 #include "MediaResource.h"
 #include "VideoUtils.h"
 
 namespace mozilla {
 
 
 #ifdef PR_LOGGING
--- a/content/media/directshow/DirectShowUtils.cpp
+++ b/content/media/directshow/DirectShowUtils.cpp
@@ -1,18 +1,17 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "dshow.h"
+#include "DirectShowUtils.h"
 #include "dmodshow.h"
 #include "wmcodecdsp.h"
 #include "dmoreg.h"
-#include "DirectShowUtils.h"
 #include "nsAutoPtr.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/RefPtr.h"
 #include "nsPrintfCString.h"
 
 #define WARN(...) NS_WARNING(nsPrintfCString(__VA_ARGS__).get())
 
 namespace mozilla {
--- a/content/media/directshow/DirectShowUtils.h
+++ b/content/media/directshow/DirectShowUtils.h
@@ -3,16 +3,24 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef _DirectShowUtils_h_
 #define _DirectShowUtils_h_
 
 #include <stdint.h>
 #include "dshow.h"
+
+// XXXbz windowsx.h defines GetFirstChild, GetNextSibling,
+// GetPrevSibling are macros, apparently... Eeevil.  We have functions
+// called that on some classes, so undef them.
+#undef GetFirstChild
+#undef GetNextSibling
+#undef GetPrevSibling
+
 #include "DShowTools.h"
 #include "prlog.h"
 
 namespace mozilla {
 
 // Win32 "Event" wrapper. Must be paired with a CriticalSection to create a
 // Java-style "monitor".
 class Signal {
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -15,45 +15,58 @@
 #include "mozilla/MediaManager.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "nsIAppsService.h"
 #include "nsIObserverService.h"
 #include "nsIDOMDeviceStorage.h"
 #include "nsIDOMEventListener.h"
 #include "nsIScriptSecurityManager.h"
+#include "nsDOMFile.h"
 #include "Navigator.h"
 #include "nsXULAppAPI.h"
 #include "DOMCameraManager.h"
 #include "DOMCameraCapabilities.h"
 #include "CameraCommon.h"
 #include "nsGlobalWindow.h"
 #include "CameraPreviewMediaStream.h"
+#include "mozilla/dom/CameraUtilBinding.h"
 #include "mozilla/dom/CameraControlBinding.h"
 #include "mozilla/dom/CameraManagerBinding.h"
 #include "mozilla/dom/CameraCapabilitiesBinding.h"
+#include "mozilla/dom/CameraConfigurationEvent.h"
+#include "mozilla/dom/CameraConfigurationEventBinding.h"
+#include "mozilla/dom/CameraFacesDetectedEvent.h"
+#include "mozilla/dom/CameraFacesDetectedEventBinding.h"
+#include "mozilla/dom/CameraStateChangeEvent.h"
+#include "mozilla/dom/BlobEvent.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)
 NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
 
 NS_IMPL_ADDREF_INHERITED(nsDOMCameraControl, DOMMediaStream)
 NS_IMPL_RELEASE_INHERITED(nsDOMCameraControl, DOMMediaStream)
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsDOMCameraControl, DOMMediaStream,
                                    mCapabilities,
                                    mWindow,
+                                   mGetCameraPromise,
+                                   mAutoFocusPromise,
+                                   mTakePicturePromise,
+                                   mStartRecordingPromise,
+                                   mReleasePromise,
+                                   mSetConfigurationPromise,
                                    mGetCameraOnSuccessCb,
                                    mGetCameraOnErrorCb,
                                    mAutoFocusOnSuccessCb,
                                    mAutoFocusOnErrorCb,
                                    mTakePictureOnSuccessCb,
                                    mTakePictureOnErrorCb,
                                    mStartRecordingOnSuccessCb,
                                    mStartRecordingOnErrorCb,
@@ -129,20 +142,22 @@ nsDOMCameraControl::DOMCameraConfigurati
 {
   MOZ_COUNT_DTOR(nsDOMCameraControl::DOMCameraConfiguration);
 }
 
 nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
                                        const CameraConfiguration& aInitialConfig,
                                        GetCameraCallback* aOnSuccess,
                                        CameraErrorCallback* aOnError,
+                                       Promise* aPromise,
                                        nsPIDOMWindow* aWindow)
   : DOMMediaStream()
   , mCameraControl(nullptr)
   , mAudioChannelAgent(nullptr)
+  , mGetCameraPromise(aPromise)
   , mGetCameraOnSuccessCb(aOnSuccess)
   , mGetCameraOnErrorCb(aOnError)
   , mAutoFocusOnSuccessCb(nullptr)
   , mAutoFocusOnErrorCb(nullptr)
   , mTakePictureOnSuccessCb(nullptr)
   , mTakePictureOnErrorCb(nullptr)
   , mStartRecordingOnSuccessCb(nullptr)
   , mStartRecordingOnErrorCb(nullptr)
@@ -152,20 +167,22 @@ nsDOMCameraControl::nsDOMCameraControl(u
   , mSetConfigurationOnErrorCb(nullptr)
   , mOnShutterCb(nullptr)
   , mOnClosedCb(nullptr)
   , mOnRecorderStateChangeCb(nullptr)
   , mOnPreviewStateChangeCb(nullptr)
   , mOnAutoFocusMovingCb(nullptr)
   , mOnFacesDetectedCb(nullptr)
   , mWindow(aWindow)
+  , mPreviewState(CameraControlListener::kPreviewStopped)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   mInput = new CameraPreviewMediaStream(this);
 
+  BindToOwner(aWindow);
   SetIsDOMBinding();
 
   nsRefPtr<DOMCameraConfiguration> initialConfig =
     new DOMCameraConfiguration(aInitialConfig);
 
   // Create and initialize the underlying camera.
   ICameraControl::Configuration config;
   bool haveInitialConfig = false;
@@ -665,38 +682,40 @@ public:
   }
 
 protected:
   nsRefPtr<CameraErrorCallback> mCallback;
   nsString mMessage;
 };
 
 // Methods.
-void
+already_AddRefed<Promise>
 nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions,
                                    nsDOMDeviceStorage& aStorageArea,
                                    const nsAString& aFilename,
-                                   CameraStartRecordingCallback& aOnSuccess,
+                                   const Optional<OwningNonNull<CameraStartRecordingCallback> >& aOnSuccess,
                                    const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
                                    ErrorResult& aRv)
 {
   MOZ_ASSERT(mCameraControl);
 
+  nsRefPtr<Promise> promise = CreatePromise(aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
   nsRefPtr<CameraStartRecordingCallback> cb = mStartRecordingOnSuccessCb;
   if (cb) {
+    promise->MaybeReject(NS_ERROR_IN_PROGRESS);
     if (aOnError.WasPassed()) {
       DOM_CAMERA_LOGT("%s:onError WasPassed\n", __func__);
       NS_DispatchToMainThread(new ImmediateErrorCallback(&aOnError.Value(),
                               NS_LITERAL_STRING("StartRecordingInProgress")));
-    } else {
-      DOM_CAMERA_LOGT("%s:onError NS_ERROR_FAILURE\n", __func__);
-      // Only throw if no error callback was passed in.
-      aRv = NS_ERROR_FAILURE;
     }
-    return;
+    return promise.forget();
   }
 
   NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
 
 #ifdef MOZ_B2G
   if (!mAudioChannelAgent) {
     mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
     if (mAudioChannelAgent) {
@@ -709,29 +728,34 @@ nsDOMCameraControl::StartRecording(const
   }
 #endif
 
   nsCOMPtr<nsIDOMDOMRequest> request;
   mDSFileDescriptor = new DeviceStorageFileDescriptor();
   aRv = aStorageArea.CreateFileDescriptor(aFilename, mDSFileDescriptor.get(),
                                           getter_AddRefs(request));
   if (aRv.Failed()) {
-    return;
+    return nullptr;
   }
 
+  mStartRecordingPromise = promise;
   mOptions = aOptions;
-  mStartRecordingOnSuccessCb = &aOnSuccess;
+  mStartRecordingOnSuccessCb = nullptr;
+  if (aOnSuccess.WasPassed()) {
+    mStartRecordingOnSuccessCb = &aOnSuccess.Value();
+  }
   mStartRecordingOnErrorCb = nullptr;
   if (aOnError.WasPassed()) {
     mStartRecordingOnErrorCb = &aOnError.Value();
   }
 
   nsCOMPtr<nsIDOMEventListener> listener = new StartRecordingHelper(this);
   request->AddEventListener(NS_LITERAL_STRING("success"), listener, false);
   request->AddEventListener(NS_LITERAL_STRING("error"), listener, false);
+  return promise.forget();
 }
 
 void
 nsDOMCameraControl::OnCreatedFileDescriptor(bool aSucceeded)
 {
   nsresult rv = NS_ERROR_FAILURE;
 
   if (aSucceeded && mDSFileDescriptor->mFileDescriptor.IsValid()) {
@@ -776,118 +800,147 @@ nsDOMCameraControl::StopRecording(ErrorR
 
 void
 nsDOMCameraControl::ResumePreview(ErrorResult& aRv)
 {
   MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->StartPreview();
 }
 
-void
+already_AddRefed<Promise>
 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) {
+  nsRefPtr<Promise> promise = CreatePromise(aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  if (mTakePicturePromise) {
+    promise->MaybeReject(NS_ERROR_IN_PROGRESS);
     // We're busy taking a picture, can't change modes right now.
     if (aOnError.WasPassed()) {
       // 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;
     }
-    return;
+    return promise.forget();
   }
 
   ICameraControl::Configuration config;
   config.mRecorderProfile = aConfiguration.mRecorderProfile;
   config.mPreviewSize.width = aConfiguration.mPreviewSize.mWidth;
   config.mPreviewSize.height = aConfiguration.mPreviewSize.mHeight;
   config.mMode = ICameraControl::kPictureMode;
   if (aConfiguration.mMode == CameraMode::Video) {
     config.mMode = ICameraControl::kVideoMode;
   }
 
+  aRv = mCameraControl->SetConfiguration(config);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  mSetConfigurationPromise = promise;
   mSetConfigurationOnSuccessCb = nullptr;
   if (aOnSuccess.WasPassed()) {
     mSetConfigurationOnSuccessCb = &aOnSuccess.Value();
   }
   mSetConfigurationOnErrorCb = nullptr;
   if (aOnError.WasPassed()) {
     mSetConfigurationOnErrorCb = &aOnError.Value();
   }
-
-  aRv = mCameraControl->SetConfiguration(config);
+  return promise.forget();
 }
 
-void
-nsDOMCameraControl::AutoFocus(CameraAutoFocusCallback& aOnSuccess,
+already_AddRefed<Promise>
+nsDOMCameraControl::AutoFocus(const Optional<OwningNonNull<CameraAutoFocusCallback> >& aOnSuccess,
                               const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
                               ErrorResult& aRv)
 {
   MOZ_ASSERT(mCameraControl);
 
-  nsRefPtr<CameraErrorCallback> ecb = mAutoFocusOnErrorCb.forget();
-  if (ecb) {
+  nsRefPtr<Promise> promise = mAutoFocusPromise.forget();
+  if (promise) {
     // There is already a call to AutoFocus() in progress, cancel it and
     // invoke the error callback (if one was passed in).
-    NS_DispatchToMainThread(new ImmediateErrorCallback(ecb,
-                            NS_LITERAL_STRING("AutoFocusInterrupted")));
+    promise->MaybeReject(NS_ERROR_IN_PROGRESS);
+    mAutoFocusOnSuccessCb = nullptr;
+    nsRefPtr<CameraErrorCallback> ecb = mAutoFocusOnErrorCb.forget();
+    if (ecb) {
+      NS_DispatchToMainThread(new ImmediateErrorCallback(ecb,
+                              NS_LITERAL_STRING("AutoFocusInterrupted")));
+    }
   }
 
-  mAutoFocusOnSuccessCb = &aOnSuccess;
+  promise = CreatePromise(aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  aRv = mCameraControl->AutoFocus();
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("focusing"));
+
+  mAutoFocusPromise = promise;
+  mAutoFocusOnSuccessCb = nullptr;
+  if (aOnSuccess.WasPassed()) {
+    mAutoFocusOnSuccessCb = &aOnSuccess.Value();
+  }
   mAutoFocusOnErrorCb = nullptr;
   if (aOnError.WasPassed()) {
     mAutoFocusOnErrorCb = &aOnError.Value();
   }
-
-  aRv = mCameraControl->AutoFocus();
+  return promise.forget();
 }
 
 void
 nsDOMCameraControl::StartFaceDetection(ErrorResult& aRv)
 {
   MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->StartFaceDetection();
 }
 
 void
 nsDOMCameraControl::StopFaceDetection(ErrorResult& aRv)
 {
   MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->StopFaceDetection();
 }
 
-void
+already_AddRefed<Promise>
 nsDOMCameraControl::TakePicture(const CameraPictureOptions& aOptions,
-                                CameraTakePictureCallback& aOnSuccess,
+                                const Optional<OwningNonNull<CameraTakePictureCallback> >& aOnSuccess,
                                 const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
                                 ErrorResult& aRv)
 {
   MOZ_ASSERT(mCameraControl);
 
-  nsRefPtr<CameraTakePictureCallback> cb = mTakePictureOnSuccessCb;
-  if (cb) {
+  nsRefPtr<Promise> promise = CreatePromise(aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  if (mTakePicturePromise) {
+    // There is already a call to TakePicture() in progress, abort this new
+    // one and invoke the error callback (if one was passed in).
+    promise->MaybeReject(NS_ERROR_IN_PROGRESS);
     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;
     }
-    return;
+    return promise.forget();
   }
 
   {
     ICameraControlParameterSetAutoEnter batch(mCameraControl);
 
     // XXXmikeh - remove this: see bug 931155
     ICameraControl::Size s;
     s.width = aOptions.mPictureSize.mWidth;
@@ -903,42 +956,60 @@ nsDOMCameraControl::TakePicture(const Ca
       mCameraControl->Set(CAMERA_PARAM_PICTURE_SIZE, s);
     }
     mCameraControl->Set(CAMERA_PARAM_PICTURE_ROTATION, aOptions.mRotation);
     mCameraControl->Set(CAMERA_PARAM_PICTURE_FILEFORMAT, aOptions.mFileFormat);
     mCameraControl->Set(CAMERA_PARAM_PICTURE_DATETIME, aOptions.mDateTime);
     mCameraControl->SetLocation(p);
   }
 
-  mTakePictureOnSuccessCb = &aOnSuccess;
+  aRv = mCameraControl->TakePicture();
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  mTakePicturePromise = promise;
+  mTakePictureOnSuccessCb = nullptr;
+  if (aOnSuccess.WasPassed()) {
+    mTakePictureOnSuccessCb = &aOnSuccess.Value();
+  }
   mTakePictureOnErrorCb = nullptr;
   if (aOnError.WasPassed()) {
     mTakePictureOnErrorCb = &aOnError.Value();
   }
-
-  aRv = mCameraControl->TakePicture();
+  return promise.forget();
 }
 
-void
+already_AddRefed<Promise>
 nsDOMCameraControl::ReleaseHardware(const Optional<OwningNonNull<CameraReleaseCallback> >& aOnSuccess,
                                     const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
                                     ErrorResult& aRv)
 {
   MOZ_ASSERT(mCameraControl);
 
+  nsRefPtr<Promise> promise = CreatePromise(aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  aRv = mCameraControl->Stop();
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  mReleasePromise = promise;
   mReleaseOnSuccessCb = nullptr;
   if (aOnSuccess.WasPassed()) {
     mReleaseOnSuccessCb = &aOnSuccess.Value();
   }
   mReleaseOnErrorCb = nullptr;
   if (aOnError.WasPassed()) {
     mReleaseOnErrorCb = &aOnError.Value();
   }
-
-  aRv = mCameraControl->Stop();
+  return promise.forget();
 }
 
 void
 nsDOMCameraControl::ResumeContinuousFocus(ErrorResult& aRv)
 {
   MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->ResumeContinuousFocus();
 }
@@ -947,16 +1018,22 @@ void
 nsDOMCameraControl::Shutdown()
 {
   DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
   MOZ_ASSERT(mCameraControl);
 
   // Remove any pending solicited event handlers; these
   // reference our window object, which in turn references
   // us. If we don't remove them, we can leak DOM objects.
+  AbortPromise(mGetCameraPromise);
+  AbortPromise(mAutoFocusPromise);
+  AbortPromise(mTakePicturePromise);
+  AbortPromise(mStartRecordingPromise);
+  AbortPromise(mReleasePromise);
+  AbortPromise(mSetConfigurationPromise);
   mGetCameraOnSuccessCb = nullptr;
   mGetCameraOnErrorCb = nullptr;
   mAutoFocusOnSuccessCb = nullptr;
   mAutoFocusOnErrorCb = nullptr;
   mTakePictureOnSuccessCb = nullptr;
   mTakePictureOnErrorCb = nullptr;
   mStartRecordingOnSuccessCb = nullptr;
   mStartRecordingOnErrorCb = nullptr;
@@ -982,47 +1059,125 @@ nsDOMCameraControl::NotifyRecordingStatu
   NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE);
 
   return MediaManager::NotifyRecordingStatusChange(mWindow,
                                                    aMsg,
                                                    true /* aIsAudio */,
                                                    true /* aIsVideo */);
 }
 
+already_AddRefed<Promise>
+nsDOMCameraControl::CreatePromise(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+  return Promise::Create(global, aRv);
+}
+
+void
+nsDOMCameraControl::AbortPromise(nsRefPtr<Promise>& aPromise)
+{
+  nsRefPtr<Promise> promise = aPromise.forget();
+  if (promise) {
+    promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+    promise = nullptr;
+  }
+}
+
+void
+nsDOMCameraControl::EventListenerAdded(nsIAtom* aType)
+{
+  if (aType == nsGkAtoms::onpreviewstatechange) {
+    DispatchPreviewStateEvent(mPreviewState);
+  }
+}
+
+void
+nsDOMCameraControl::DispatchPreviewStateEvent(CameraControlListener::PreviewState aState)
+{
+  nsString state;
+  switch (aState) {
+    case CameraControlListener::kPreviewStarted:
+      state = NS_LITERAL_STRING("started");
+      break;
+
+    default:
+      state = NS_LITERAL_STRING("stopped");
+      break;
+  }
+
+  DispatchStateEvent(NS_LITERAL_STRING("previewstatechange"), state);
+}
+
+void
+nsDOMCameraControl::DispatchStateEvent(const nsString& aType, const nsString& aState)
+{
+  CameraStateChangeEventInit eventInit;
+  eventInit.mNewState = aState;
+
+  nsRefPtr<CameraStateChangeEvent> event =
+    CameraStateChangeEvent::Constructor(this, aType, eventInit);
+
+  DispatchTrustedEvent(event);
+}
+
 // Camera Control event handlers--must only be called from the Main Thread!
 void
 nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState aState)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ErrorResult ignored;
 
   DOM_CAMERA_LOGI("DOM OnHardwareStateChange(%d)\n", aState);
 
   switch (aState) {
     case CameraControlListener::kHardwareOpen:
-      // The hardware is open, so we can return a camera to JS, even if
-      // the preview hasn't started yet.
-      if (mGetCameraOnSuccessCb) {
+      {
+        // The hardware is open, so we can return a camera to JS, even if
+        // the preview hasn't started yet.
+        nsRefPtr<Promise> promise = mGetCameraPromise.forget();
+        if (promise) {
+          CameraGetPromiseData data;
+          data.mCamera = this;
+          data.mConfiguration = *mCurrentConfiguration;
+          promise->MaybeResolve(data);
+        }
         nsRefPtr<GetCameraCallback> cb = mGetCameraOnSuccessCb.forget();
-        ErrorResult ignored;
         mGetCameraOnErrorCb = nullptr;
-        cb->Call(*this, *mCurrentConfiguration, ignored);
+        if (cb) {
+          ErrorResult ignored;
+          cb->Call(*this, *mCurrentConfiguration, ignored);
+        }
       }
       break;
 
     case CameraControlListener::kHardwareClosed:
-      if (mReleaseOnSuccessCb) {
-        // If we have this event handler, this was a solicited hardware close.
-        nsRefPtr<CameraReleaseCallback> cb = mReleaseOnSuccessCb.forget();
-        mReleaseOnErrorCb = nullptr;
-        cb->Call(ignored);
-      } else if(mOnClosedCb) {
-        // If not, something else closed the hardware.
-        nsRefPtr<CameraClosedCallback> cb = mOnClosedCb;
-        cb->Call(ignored);
+      {
+        nsRefPtr<Promise> promise = mReleasePromise.forget();
+        if (promise || mReleaseOnSuccessCb) {
+          // If we have this event handler, this was a solicited hardware close.
+          if (promise) {
+            promise->MaybeResolve(JS::UndefinedHandleValue);
+          }
+          nsRefPtr<CameraReleaseCallback> cb = mReleaseOnSuccessCb.forget();
+          mReleaseOnErrorCb = nullptr;
+          if (cb) {
+            cb->Call(ignored);
+          }
+        } else {
+          // If not, something else closed the hardware.
+          nsRefPtr<CameraClosedCallback> cb = mOnClosedCb;
+          if (cb) {
+            cb->Call(ignored);
+          }
+        }
+        DispatchTrustedEvent(NS_LITERAL_STRING("close"));
       }
       break;
 
     default:
       MOZ_ASSERT_UNREACHABLE("Unanticipated camera hardware state");
   }
 }
 
@@ -1033,61 +1188,71 @@ nsDOMCameraControl::OnShutter()
 
   DOM_CAMERA_LOGI("DOM ** SNAP **\n");
 
   nsRefPtr<CameraShutterCallback> cb = mOnShutterCb;
   if (cb) {
     ErrorResult ignored;
     cb->Call(ignored);
   }
+
+  DispatchTrustedEvent(NS_LITERAL_STRING("shutter"));
 }
 
 void
 nsDOMCameraControl::OnPreviewStateChange(CameraControlListener::PreviewState aState)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!mOnPreviewStateChangeCb) {
-    return;
-  }
-
+  mPreviewState = aState;
   nsString state;
   switch (aState) {
     case CameraControlListener::kPreviewStarted:
       state = NS_LITERAL_STRING("started");
       break;
 
     default:
       state = NS_LITERAL_STRING("stopped");
       break;
   }
 
   nsRefPtr<CameraPreviewStateChange> cb = mOnPreviewStateChangeCb;
-  ErrorResult ignored;
-  cb->Call(state, ignored);
+  if (cb) {
+    ErrorResult ignored;
+    cb->Call(state, ignored);
+  }
+
+  DispatchPreviewStateEvent(aState);
 }
 
 void
 nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState aState,
                                           int32_t aArg, int32_t aTrackNum)
 {
   // For now, we do nothing with 'aStatus' and 'aTrackNum'.
   MOZ_ASSERT(NS_IsMainThread());
 
   ErrorResult ignored;
   nsString state;
 
   switch (aState) {
     case CameraControlListener::kRecorderStarted:
-      if (mStartRecordingOnSuccessCb) {
-        nsRefPtr<CameraStartRecordingCallback> cb = mStartRecordingOnSuccessCb.forget();
+      {
+        nsRefPtr<Promise> promise = mStartRecordingPromise.forget();
+        if (promise) {
+          promise->MaybeResolve(JS::UndefinedHandleValue);
+        }
+
+        nsRefPtr<CameraStartRecordingCallback> scb = mStartRecordingOnSuccessCb.forget();
         mStartRecordingOnErrorCb = nullptr;
-        cb->Call(ignored);
+        if (scb) {
+          scb->Call(ignored);
+        }
+        state = NS_LITERAL_STRING("Started");
       }
-      state = NS_LITERAL_STRING("Started");
       break;
 
     case CameraControlListener::kRecorderStopped:
       NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
       state = NS_LITERAL_STRING("Stopped");
       break;
 
 #ifdef MOZ_B2G_CAMERA
@@ -1120,150 +1285,213 @@ nsDOMCameraControl::OnRecorderStateChang
       MOZ_ASSERT_UNREACHABLE("Unanticipated video recorder error");
       return;
   }
 
   nsRefPtr<CameraRecorderStateChange> cb = mOnRecorderStateChangeCb;
   if (cb) {
     cb->Call(state, ignored);
   }
+
+  DispatchStateEvent(NS_LITERAL_STRING("recorderstatechange"), state);
 }
 
 void
 nsDOMCameraControl::OnConfigurationChange(DOMCameraConfiguration* aConfiguration)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aConfiguration != nullptr);
 
   // Update our record of the current camera configuration
   mCurrentConfiguration = aConfiguration;
 
   DOM_CAMERA_LOGI("DOM OnConfigurationChange: this=%p\n", this);
   DOM_CAMERA_LOGI("    mode                   : %s\n",
     mCurrentConfiguration->mMode == CameraMode::Video ? "video" : "picture");
   DOM_CAMERA_LOGI("    maximum focus areas    : %d\n",
     mCurrentConfiguration->mMaxFocusAreas);
   DOM_CAMERA_LOGI("    maximum metering areas : %d\n",
     mCurrentConfiguration->mMaxMeteringAreas);
   DOM_CAMERA_LOGI("    preview size (w x h)   : %d x %d\n",
     mCurrentConfiguration->mPreviewSize.mWidth, mCurrentConfiguration->mPreviewSize.mHeight);
   DOM_CAMERA_LOGI("    recorder profile       : %s\n",
     NS_ConvertUTF16toUTF8(mCurrentConfiguration->mRecorderProfile).get());
 
+  nsRefPtr<Promise> promise = mSetConfigurationPromise.forget();
+  if (promise) {
+    promise->MaybeResolve(*aConfiguration);
+  }
+
   nsRefPtr<CameraSetConfigurationCallback> cb = mSetConfigurationOnSuccessCb.forget();
   mSetConfigurationOnErrorCb = nullptr;
   if (cb) {
     ErrorResult ignored;
     cb->Call(*mCurrentConfiguration, ignored);
   }
+
+  CameraConfigurationEventInit eventInit;
+  eventInit.mMode = mCurrentConfiguration->mMode;
+  eventInit.mRecorderProfile = mCurrentConfiguration->mRecorderProfile;
+  eventInit.mPreviewSize = new DOMRect(this, 0, 0,
+                                       mCurrentConfiguration->mPreviewSize.mWidth,
+                                       mCurrentConfiguration->mPreviewSize.mHeight);
+
+  nsRefPtr<CameraConfigurationEvent> event =
+    CameraConfigurationEvent::Constructor(this,
+                                          NS_LITERAL_STRING("configurationchanged"),
+                                          eventInit);
+
+  DispatchTrustedEvent(event);
 }
 
 void
 nsDOMCameraControl::OnAutoFocusComplete(bool aAutoFocusSucceeded)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  nsRefPtr<Promise> promise = mAutoFocusPromise.forget();
+  if (promise) {
+    promise->MaybeResolve(aAutoFocusSucceeded);
+  }
+
   nsRefPtr<CameraAutoFocusCallback> cb = mAutoFocusOnSuccessCb.forget();
   mAutoFocusOnErrorCb = nullptr;
   if (cb) {
     ErrorResult ignored;
     cb->Call(aAutoFocusSucceeded, ignored);
   }
+
+  if (aAutoFocusSucceeded) {
+    DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("focused"));
+  } else {
+    DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("unfocused"));
+  }
 }
 
 void
 nsDOMCameraControl::OnAutoFocusMoving(bool aIsMoving)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsRefPtr<CameraAutoFocusMovingCallback> cb = mOnAutoFocusMovingCb;
   if (cb) {
     ErrorResult ignored;
     cb->Call(aIsMoving, ignored);
   }
+
+  if (aIsMoving) {
+    DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("focusing"));
+  }
 }
 
 void
 nsDOMCameraControl::OnFacesDetected(const nsTArray<ICameraControl::Face>& aFaces)
 {
   DOM_CAMERA_LOGI("DOM OnFacesDetected %u face(s)\n", aFaces.Length());
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsRefPtr<CameraFaceDetectionCallback> cb = mOnFacesDetectedCb;
-  if (!cb) {
-    return;
-  }
-
   Sequence<OwningNonNull<DOMCameraDetectedFace> > faces;
   uint32_t len = aFaces.Length();
 
   if (faces.SetCapacity(len)) {
     nsRefPtr<DOMCameraDetectedFace> f;
     for (uint32_t i = 0; i < len; ++i) {
       f = new DOMCameraDetectedFace(this, aFaces[i]);
       *faces.AppendElement() = f.forget().take();
     }
   }
 
-  ErrorResult ignored;
-  cb->Call(faces, ignored);
+  nsRefPtr<CameraFaceDetectionCallback> cb = mOnFacesDetectedCb;
+  if (cb) {
+    ErrorResult ignored;
+    cb->Call(faces, ignored);
+  }
+
+  CameraFacesDetectedEventInit eventInit;
+  eventInit.mFaces.SetValue(faces);
+
+  nsRefPtr<CameraFacesDetectedEvent> event =
+    CameraFacesDetectedEvent::Constructor(this,
+                                          NS_LITERAL_STRING("facesdetected"),
+                                          eventInit);
+
+  DispatchTrustedEvent(event);
 }
 
 void
 nsDOMCameraControl::OnTakePictureComplete(nsIDOMBlob* aPicture)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aPicture != nullptr);
+
+  nsRefPtr<Promise> promise = mTakePicturePromise.forget();
+  if (promise) {
+    nsCOMPtr<nsIDOMBlob> picture = aPicture;
+    promise->MaybeResolve(picture);
+  }
 
   nsRefPtr<CameraTakePictureCallback> cb = mTakePictureOnSuccessCb.forget();
   mTakePictureOnErrorCb = nullptr;
-  if (!cb) {
-    // Warn because it shouldn't be possible to get here without
-    // having passed a success callback into takePicture(), even
-    // though we guard against a nullptr dereference.
-    NS_WARNING("DOM Null success callback in OnTakePictureComplete()");
-    return;
+  if (cb) {
+    ErrorResult ignored;
+    cb->Call(aPicture, ignored);
   }
 
-  ErrorResult ignored;
-  cb->Call(aPicture, ignored);
+  BlobEventInit eventInit;
+  eventInit.mData = aPicture;
+
+  nsRefPtr<BlobEvent> event = BlobEvent::Constructor(this,
+                                                     NS_LITERAL_STRING("picture"),
+                                                     eventInit);
+
+  DispatchTrustedEvent(event);
 }
 
 void
 nsDOMCameraControl::OnUserError(CameraControlListener::UserContext aContext, nsresult aError)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  nsRefPtr<Promise> promise;
   nsRefPtr<CameraErrorCallback> errorCb;
 
   switch (aContext) {
     case CameraControlListener::kInStartCamera:
+      promise = mGetCameraPromise.forget();
       mGetCameraOnSuccessCb = nullptr;
       errorCb = mGetCameraOnErrorCb.forget();
       break;
 
     case CameraControlListener::kInStopCamera:
+      promise = mReleasePromise.forget();
       mReleaseOnSuccessCb = nullptr;
       errorCb = mReleaseOnErrorCb.forget();
       break;
 
     case CameraControlListener::kInSetConfiguration:
+      promise = mSetConfigurationPromise.forget();
       mSetConfigurationOnSuccessCb = nullptr;
       errorCb = mSetConfigurationOnErrorCb.forget();
       break;
 
     case CameraControlListener::kInAutoFocus:
+      promise = mAutoFocusPromise.forget();
       mAutoFocusOnSuccessCb = nullptr;
       errorCb = mAutoFocusOnErrorCb.forget();
+      DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("unfocused"));
       break;
 
     case CameraControlListener::kInTakePicture:
+      promise = mTakePicturePromise.forget();
       mTakePictureOnSuccessCb = nullptr;
       errorCb = mTakePictureOnErrorCb.forget();
       break;
 
     case CameraControlListener::kInStartRecording:
+      promise = mStartRecordingPromise.forget();
       mStartRecordingOnSuccessCb = nullptr;
       errorCb = mStartRecordingOnErrorCb.forget();
       break;
 
     case CameraControlListener::kInStartFaceDetection:
       // 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 start face detection");
@@ -1315,57 +1543,62 @@ nsDOMCameraControl::OnUserError(CameraCo
       {
         nsPrintfCString msg("Unhandled aContext=%u, aError=0x%x\n", aContext, aError);
         NS_WARNING(msg.get());
       }
       MOZ_ASSERT_UNREACHABLE("Unhandled user error");
       return;
   }
 
-  if (!errorCb) {
+  if (!promise && !errorCb) {
     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, aError=0x%x\n", aContext, aError);
+  if (promise) {
+    promise->MaybeReject(aError);
   }
 
-  DOM_CAMERA_LOGI("DOM OnUserError aContext=%u, error='%s'\n", aContext,
-    NS_ConvertUTF16toUTF8(error).get());
-  ErrorResult ignored;
-  errorCb->Call(error, ignored);
+  if (errorCb) {
+    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;
+    }
+
+    ErrorResult ignored;
+    errorCb->Call(error, ignored);
+  }
 }
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -4,16 +4,17 @@
 
 #ifndef DOM_CAMERA_DOMCAMERACONTROL_H
 #define DOM_CAMERA_DOMCAMERACONTROL_H
 
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/dom/CameraControlBinding.h"
+#include "mozilla/dom/Promise.h"
 #include "ICameraControl.h"
 #include "CameraCommon.h"
 #include "DOMMediaStream.h"
 #include "AudioChannelAgent.h"
 #include "nsProxyRelease.h"
 #include "nsHashPropertyBag.h"
 #include "DeviceStorage.h"
 #include "DOMCameraControlListener.h"
@@ -48,16 +49,17 @@ public:
   // 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);
 
   nsDOMCameraControl(uint32_t aCameraId,
                      const dom::CameraConfiguration& aInitialConfig,
                      dom::GetCameraCallback* aOnSuccess,
                      dom::CameraErrorCallback* aOnError,
+                     dom::Promise* aPromise,
                      nsPIDOMWindow* aWindow);
 
   void Shutdown();
 
   nsPIDOMWindow* GetParentObject() const { return mWindow; }
 
   // Attributes.
   void GetEffect(nsString& aEffect, ErrorResult& aRv);
@@ -95,52 +97,61 @@ public:
   dom::CameraPreviewStateChange* GetOnPreviewStateChange();
   void SetOnPreviewStateChange(dom::CameraPreviewStateChange* aCb);
   dom::CameraAutoFocusMovingCallback* GetOnAutoFocusMoving();
   void SetOnAutoFocusMoving(dom::CameraAutoFocusMovingCallback* aCb);
   dom::CameraFaceDetectionCallback* GetOnFacesDetected();
   void SetOnFacesDetected(dom::CameraFaceDetectionCallback* aCb);
 
   // Methods.
-  void SetConfiguration(const dom::CameraConfiguration& aConfiguration,
-                        const dom::Optional<dom::OwningNonNull<dom::CameraSetConfigurationCallback> >& aOnSuccess,
-                        const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
-                        ErrorResult& aRv);
+  already_AddRefed<dom::Promise> SetConfiguration(const dom::CameraConfiguration& aConfiguration,
+                                                  const dom::Optional<dom::OwningNonNull<dom::CameraSetConfigurationCallback> >& aOnSuccess,
+                                                  const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
+                                                  ErrorResult& aRv);
   void GetMeteringAreas(nsTArray<dom::CameraRegion>& aAreas, ErrorResult& aRv);
   void SetMeteringAreas(const dom::Optional<dom::Sequence<dom::CameraRegion> >& aAreas, ErrorResult& aRv);
   void GetFocusAreas(nsTArray<dom::CameraRegion>& aAreas, ErrorResult& aRv);
   void SetFocusAreas(const dom::Optional<dom::Sequence<dom::CameraRegion> >& aAreas, ErrorResult& aRv);
   void GetPictureSize(dom::CameraSize& aSize, ErrorResult& aRv);
   void SetPictureSize(const dom::CameraSize& aSize, ErrorResult& aRv);
   void GetThumbnailSize(dom::CameraSize& aSize, ErrorResult& aRv);
   void SetThumbnailSize(const dom::CameraSize& aSize, ErrorResult& aRv);
-  void AutoFocus(dom::CameraAutoFocusCallback& aOnSuccess,
-                 const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
-                 ErrorResult& aRv);
+  already_AddRefed<dom::Promise> AutoFocus(const dom::Optional<dom::OwningNonNull<dom::CameraAutoFocusCallback> >& aOnSuccess,
+                                           const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
+                                           ErrorResult& aRv);
   void StartFaceDetection(ErrorResult& aRv);
   void StopFaceDetection(ErrorResult& aRv);
-  void TakePicture(const dom::CameraPictureOptions& aOptions,
-                   dom::CameraTakePictureCallback& aOnSuccess,
-                   const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
-                   ErrorResult& aRv);
-  void StartRecording(const dom::CameraStartRecordingOptions& aOptions,
-                      nsDOMDeviceStorage& storageArea,
-                      const nsAString& filename,
-                      dom::CameraStartRecordingCallback& aOnSuccess,
-                      const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
-                      ErrorResult& aRv);
+  already_AddRefed<dom::Promise> TakePicture(const dom::CameraPictureOptions& aOptions,
+                                             const dom::Optional<dom::OwningNonNull<dom::CameraTakePictureCallback> >& aOnSuccess,
+                                             const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
+                                             ErrorResult& aRv);
+  already_AddRefed<dom::Promise> StartRecording(const dom::CameraStartRecordingOptions& aOptions,
+                                                nsDOMDeviceStorage& storageArea,
+                                                const nsAString& filename,
+                                                const dom::Optional<dom::OwningNonNull<dom::CameraStartRecordingCallback> >& aOnSuccess,
+                                                const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
+                                                ErrorResult& aRv);
   void StopRecording(ErrorResult& aRv);
   void ResumePreview(ErrorResult& aRv);
-  void ReleaseHardware(const dom::Optional<dom::OwningNonNull<dom::CameraReleaseCallback> >& aOnSuccess,
-                       const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
-                       ErrorResult& aRv);
+  already_AddRefed<dom::Promise> ReleaseHardware(const dom::Optional<dom::OwningNonNull<dom::CameraReleaseCallback> >& aOnSuccess,
+                                                 const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
+                                                 ErrorResult& aRv);
   void ResumeContinuousFocus(ErrorResult& aRv);
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
+  IMPL_EVENT_HANDLER(facesdetected)
+  IMPL_EVENT_HANDLER(shutter)
+  IMPL_EVENT_HANDLER(close)
+  IMPL_EVENT_HANDLER(recorderstatechange)
+  IMPL_EVENT_HANDLER(previewstatechange)
+  IMPL_EVENT_HANDLER(focus)
+  IMPL_EVENT_HANDLER(picture)
+  IMPL_EVENT_HANDLER(configurationchange)
+
 protected:
   virtual ~nsDOMCameraControl();
 
   class DOMCameraConfiguration MOZ_FINAL : public dom::CameraConfiguration
   {
   public:
     NS_INLINE_DECL_REFCOUNTING(DOMCameraConfiguration)
 
@@ -172,27 +183,41 @@ protected:
   void OnConfigurationChange(DOMCameraConfiguration* aConfiguration);
   void OnShutter();
   void OnUserError(CameraControlListener::UserContext aContext, nsresult aError);
 
   bool IsWindowStillActive();
 
   nsresult NotifyRecordingStatusChange(const nsString& aMsg);
 
+  already_AddRefed<dom::Promise> CreatePromise(ErrorResult& aRv);
+  void AbortPromise(nsRefPtr<dom::Promise>& aPromise);
+  virtual void EventListenerAdded(nsIAtom* aType) MOZ_OVERRIDE;
+  void DispatchPreviewStateEvent(DOMCameraControlListener::PreviewState aState);
+  void DispatchStateEvent(const nsString& aType, const nsString& aState);
+
   nsRefPtr<ICameraControl> mCameraControl; // non-DOM camera control
 
   // An agent used to join audio channel service.
   nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
 
   nsresult Set(uint32_t aKey, const dom::Optional<dom::Sequence<dom::CameraRegion> >& aValue, uint32_t aLimit);
   nsresult Get(uint32_t aKey, nsTArray<dom::CameraRegion>& aValue);
 
   nsRefPtr<DOMCameraConfiguration>              mCurrentConfiguration;
   nsRefPtr<dom::CameraCapabilities>             mCapabilities;
 
+  // camera control pending promises
+  nsRefPtr<dom::Promise>                        mGetCameraPromise;
+  nsRefPtr<dom::Promise>                        mAutoFocusPromise;
+  nsRefPtr<dom::Promise>                        mTakePicturePromise;
+  nsRefPtr<dom::Promise>                        mStartRecordingPromise;
+  nsRefPtr<dom::Promise>                        mReleasePromise;
+  nsRefPtr<dom::Promise>                        mSetConfigurationPromise;
+
   // solicited camera control event handlers
   nsRefPtr<dom::GetCameraCallback>              mGetCameraOnSuccessCb;
   nsRefPtr<dom::CameraErrorCallback>            mGetCameraOnErrorCb;
   nsRefPtr<dom::CameraAutoFocusCallback>        mAutoFocusOnSuccessCb;
   nsRefPtr<dom::CameraErrorCallback>            mAutoFocusOnErrorCb;
   nsRefPtr<dom::CameraTakePictureCallback>      mTakePictureOnSuccessCb;
   nsRefPtr<dom::CameraErrorCallback>            mTakePictureOnErrorCb;
   nsRefPtr<dom::CameraStartRecordingCallback>   mStartRecordingOnSuccessCb;
@@ -218,16 +243,17 @@ protected:
   // our viewfinder stream
   nsRefPtr<CameraPreviewMediaStream> mInput;
 
   // set once when this object is created
   nsCOMPtr<nsPIDOMWindow>   mWindow;
 
   dom::CameraStartRecordingOptions mOptions;
   nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
+  DOMCameraControlListener::PreviewState mPreviewState;
 
 private:
   nsDOMCameraControl(const nsDOMCameraControl&) MOZ_DELETE;
   nsDOMCameraControl& operator=(const nsDOMCameraControl&) MOZ_DELETE;
 };
 
 } // namespace mozilla
 
--- a/dom/camera/DOMCameraManager.cpp
+++ b/dom/camera/DOMCameraManager.cpp
@@ -141,24 +141,26 @@ public:
                                            nsIContentPermissionRequest)
 
   CameraPermissionRequest(nsIPrincipal* aPrincipal,
                           nsPIDOMWindow* aWindow,
                           nsRefPtr<nsDOMCameraManager> aManager,
                           uint32_t aCameraId,
                           const CameraConfiguration& aInitialConfig,
                           nsRefPtr<GetCameraCallback> aOnSuccess,
-                          nsRefPtr<CameraErrorCallback> aOnError)
+                          nsRefPtr<CameraErrorCallback> aOnError,
+                          nsRefPtr<Promise> aPromise)
     : mPrincipal(aPrincipal)
     , mWindow(aWindow)
     , mCameraManager(aManager)
     , mCameraId(aCameraId)
     , mInitialConfig(aInitialConfig)
     , mOnSuccess(aOnSuccess)
     , mOnError(aOnError)
+    , mPromise(aPromise)
   {
   }
 
 protected:
   virtual ~CameraPermissionRequest()
   {
   }
 
@@ -167,19 +169,23 @@ protected:
   void CallCancel();
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsPIDOMWindow> mWindow;
   nsRefPtr<nsDOMCameraManager> mCameraManager;
   uint32_t mCameraId;
   CameraConfiguration mInitialConfig;
   nsRefPtr<GetCameraCallback> mOnSuccess;
   nsRefPtr<CameraErrorCallback> mOnError;
+  nsRefPtr<Promise> mPromise;
 };
 
-NS_IMPL_CYCLE_COLLECTION(CameraPermissionRequest, mWindow, mOnSuccess, mOnError)
+NS_IMPL_CYCLE_COLLECTION(CameraPermissionRequest, mWindow,
+                                                  mOnSuccess,
+                                                  mOnError,
+                                                  mPromise)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CameraPermissionRequest)
   NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
   NS_INTERFACE_MAP_ENTRY(nsIRunnable)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(CameraPermissionRequest)
@@ -235,98 +241,118 @@ CameraPermissionRequest::DispatchCallbac
     callbackRunnable = NS_NewRunnableMethod(this, &CameraPermissionRequest::CallCancel);
   }
   return NS_DispatchToMainThread(callbackRunnable);
 }
 
 void
 CameraPermissionRequest::CallAllow()
 {
-  mCameraManager->PermissionAllowed(mCameraId, mInitialConfig, mOnSuccess, mOnError);
+  mCameraManager->PermissionAllowed(mCameraId, mInitialConfig, mOnSuccess, mOnError, mPromise);
 }
 
 void
 CameraPermissionRequest::CallCancel()
 {
-  mCameraManager->PermissionCancelled(mCameraId, mInitialConfig, mOnSuccess, mOnError);
+  mCameraManager->PermissionCancelled(mCameraId, mInitialConfig, mOnSuccess, mOnError, mPromise);
 }
 
 NS_IMETHODIMP
 CameraPermissionRequest::GetTypes(nsIArray** aTypes)
 {
   nsTArray<nsString> emptyOptions;
   return nsContentPermissionUtils::CreatePermissionArray(NS_LITERAL_CSTRING("camera"),
                                                          NS_LITERAL_CSTRING("unused"),
                                                          emptyOptions,
                                                          aTypes);
 }
 
-void
+already_AddRefed<Promise>
 nsDOMCameraManager::GetCamera(const nsAString& aCamera,
                               const CameraConfiguration& aInitialConfig,
-                              GetCameraCallback& aOnSuccess,
+                              const OptionalNonNullGetCameraCallback& aOnSuccess,
                               const OptionalNonNullCameraErrorCallback& aOnError,
                               ErrorResult& aRv)
 {
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 
   uint32_t cameraId = 0;  // back (or forward-facing) camera by default
   if (aCamera.EqualsLiteral("front")) {
     cameraId = 1;
   }
 
-  nsRefPtr<CameraErrorCallback> errorCallback = nullptr;
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<GetCameraCallback> successCallback;
+  if (aOnSuccess.WasPassed()) {
+    successCallback = &aOnSuccess.Value();
+  }
+
+  nsRefPtr<CameraErrorCallback> errorCallback;
   if (aOnError.WasPassed()) {
     errorCallback = &aOnError.Value();
   }
 
   if (mPermission == nsIPermissionManager::ALLOW_ACTION) {
-    PermissionAllowed(cameraId, aInitialConfig, &aOnSuccess, errorCallback);
-    return;
+    PermissionAllowed(cameraId, aInitialConfig, successCallback, errorCallback, promise);
+    return promise.forget();
   }
 
   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mWindow);
   if (!sop) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
-    return;
+    return nullptr;
   }
 
   nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
 
   nsCOMPtr<nsIRunnable> permissionRequest =
     new CameraPermissionRequest(principal, mWindow, this, cameraId, aInitialConfig,
-                                &aOnSuccess, errorCallback);
+                                successCallback, errorCallback, promise);
 
   NS_DispatchToMainThread(permissionRequest);
+  return promise.forget();
 }
 
 void
 nsDOMCameraManager::PermissionAllowed(uint32_t aCameraId,
                                       const CameraConfiguration& aInitialConfig,
                                       GetCameraCallback* aOnSuccess,
-                                      CameraErrorCallback* aOnError)
+                                      CameraErrorCallback* aOnError,
+                                      Promise* aPromise)
 {
   mPermission = nsIPermissionManager::ALLOW_ACTION;
 
   // Creating this object will trigger the aOnSuccess callback
   //  (or the aOnError one, if it fails).
   nsRefPtr<nsDOMCameraControl> cameraControl =
-    new nsDOMCameraControl(aCameraId, aInitialConfig, aOnSuccess, aOnError, mWindow);
+    new nsDOMCameraControl(aCameraId, aInitialConfig, aOnSuccess, aOnError, aPromise, mWindow);
 
   Register(cameraControl);
 }
 
 void
 nsDOMCameraManager::PermissionCancelled(uint32_t aCameraId,
                                         const CameraConfiguration& aInitialConfig,
                                         GetCameraCallback* aOnSuccess,
-                                        CameraErrorCallback* aOnError)
+                                        CameraErrorCallback* aOnError,
+                                        Promise* aPromise)
 {
   mPermission = nsIPermissionManager::DENY_ACTION;
 
+  aPromise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
   if (aOnError) {
     ErrorResult ignored;
     aOnError->Call(NS_LITERAL_STRING("Permission denied."), ignored);
   }
 }
 
 void
 nsDOMCameraManager::Register(nsDOMCameraControl* aDOMCameraControl)
--- a/dom/camera/DOMCameraManager.h
+++ b/dom/camera/DOMCameraManager.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DOM_CAMERA_DOMCAMERAMANAGER_H
 #define DOM_CAMERA_DOMCAMERAMANAGER_H
 
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Promise.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsIObserver.h"
 #include "nsHashKeys.h"
 #include "nsWrapperCache.h"
 #include "nsWeakReference.h"
 #include "nsClassHashtable.h"
 #include "nsCycleCollectionParticipant.h"
@@ -27,17 +28,19 @@ namespace mozilla {
     struct CameraConfiguration;
     class GetCameraCallback;
     class CameraErrorCallback;
   }
 }
 
 typedef nsTArray<nsRefPtr<mozilla::nsDOMCameraControl> > CameraControls;
 typedef nsClassHashtable<nsUint64HashKey, CameraControls> WindowTable;
-typedef mozilla::dom::Optional<mozilla::dom::OwningNonNull<mozilla::dom::CameraErrorCallback>>
+typedef mozilla::dom::Optional<mozilla::dom::OwningNonNull<mozilla::dom::GetCameraCallback> >
+          OptionalNonNullGetCameraCallback;
+typedef mozilla::dom::Optional<mozilla::dom::OwningNonNull<mozilla::dom::CameraErrorCallback> >
           OptionalNonNullCameraErrorCallback;
 
 class nsDOMCameraManager MOZ_FINAL
   : public nsIObserver
   , public nsSupportsWeakReference
   , public nsWrapperCache
 {
 public:
@@ -59,29 +62,32 @@ public:
   static bool IsWindowStillActive(uint64_t aWindowId);
 
   void Register(mozilla::nsDOMCameraControl* aDOMCameraControl);
   void OnNavigation(uint64_t aWindowId);
 
   void PermissionAllowed(uint32_t aCameraId,
                          const mozilla::dom::CameraConfiguration& aOptions,
                          mozilla::dom::GetCameraCallback* aOnSuccess,
-                         mozilla::dom::CameraErrorCallback* aOnError);
+                         mozilla::dom::CameraErrorCallback* aOnError,
+                         mozilla::dom::Promise* aPromise);
 
   void PermissionCancelled(uint32_t aCameraId,
                            const mozilla::dom::CameraConfiguration& aOptions,
                            mozilla::dom::GetCameraCallback* aOnSuccess,
-                           mozilla::dom::CameraErrorCallback* aOnError);
+                           mozilla::dom::CameraErrorCallback* aOnError,
+                           mozilla::dom::Promise* aPromise);
 
   // WebIDL
-  void GetCamera(const nsAString& aCamera,
-                 const mozilla::dom::CameraConfiguration& aOptions,
-                 mozilla::dom::GetCameraCallback& aOnSuccess,
-                 const OptionalNonNullCameraErrorCallback& aOnError,
-                 mozilla::ErrorResult& aRv);
+  already_AddRefed<mozilla::dom::Promise>
+  GetCamera(const nsAString& aCamera,
+            const mozilla::dom::CameraConfiguration& aOptions,
+            const OptionalNonNullGetCameraCallback& aOnSuccess,
+            const OptionalNonNullCameraErrorCallback& aOnError,
+            mozilla::ErrorResult& aRv);
   void GetListOfCameras(nsTArray<nsString>& aList, mozilla::ErrorResult& aRv);
 
   nsPIDOMWindow* GetParentObject() const { return mWindow; }
   virtual JSObject* WrapObject(JSContext* aCx)
     MOZ_OVERRIDE;
 
 protected:
   void XpComShutdown();
new file mode 100644
--- /dev/null
+++ b/dom/camera/test/callback/test_bug1022766.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for bug 1022766</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];
+var config = {
+  mode: 'picture',
+  recorderProfile: 'cif',
+  previewSize: {
+    width: 352,
+    height: 288
+  }
+};
+
+function onError(e) {
+  ok(false, "Error" + JSON.stringify(e));
+}
+
+var Camera = {
+  cameraObj: null,
+  _otherPictureSize: null,
+  get viewfinder() {
+    return document.getElementById('viewfinder');
+  },
+
+  firstCallFailed: false,
+  secondCallSucceeded: false,
+  checkForDone: function test_checkForDone() {
+    if (Camera.firstCallFailed && Camera.secondCallSucceeded) {
+      Camera.cameraObj.release();
+      Camera.cameraObj = null;
+      CameraTest.end();
+    }
+  },
+
+  successOne: function test_successOne(focused) {
+    ok(false, "First call to autoFocus() succeeded unexpectedly");
+  },
+  failureOne: function test_failureOne(error) {
+    ok(error == "AutoFocusInterrupted", "First call to autoFocus() failed with: "
+      + error);
+    Camera.firstCallFailed = true;
+    Camera.checkForDone();
+  },
+  successTwo: function test_successTwo(focused) {
+    ok(true, "Second call to autoFocus() succeeded");
+    Camera.secondCallSucceeded = true;
+    Camera.checkForDone();
+  },
+  failureTwo: function test_failureTwo(error) {
+    ok(false, "Second call to autoFocus() failed unexpectedly with: " + error);
+  },
+
+  start: function test_start() {
+    function onSuccess(camera, config) {
+      Camera.cameraObj = camera;
+      Camera.viewfinder.mozSrcObject = camera;
+      Camera.viewfinder.play();
+
+      // It doesn't matter if the emulator supports focus or not;
+      // this is just testing the sequencing.
+      camera.autoFocus(Camera.successOne, Camera.failureOne);
+      camera.autoFocus(Camera.successTwo, Camera.failureTwo);
+    };
+
+    navigator.mozCameras.getCamera(whichCamera, config, onSuccess, onError);
+  }
+}
+
+window.addEventListener('beforeunload', function() {
+  Camera.viewfinder.mozSrcObject = null;
+  if (Camera.cameraObj) {
+    Camera.cameraObj.release();
+    Camera.cameraObj = null;
+  }
+});
+
+CameraTest.begin("hardware", function(test) {
+  test.set("auto-focus-process-failure", function() {
+    Camera.start();
+  })
+});
+
+</script>
+</body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/camera/test/callback/test_bug975472.html
@@ -0,0 +1,223 @@
+<!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];
+var config = {
+  mode: 'picture',
+  recorderProfile: 'cif',
+  previewSize: {
+    width: 352,
+    height: 288
+  }
+};
+var options = {
+  rotation: 0,
+  position: {
+    latitude: 43.645687,
+    longitude: -79.393661
+  },
+  dateTime: Date.now()
+};
+
+function onError(e) {
+  ok(false, "Error" + JSON.stringify(e));
+}
+function next() {
+  Camera.nextTest();
+}
+
+// The array of tests
+var tests = [
+  {
+    key: "release-after-release",
+    func: function testAutoFocus(camera) {
+      function onSuccess(success) {
+        ok(true, "release() succeeded");
+        next();
+      }
+      function onError(error) {
+        ok(false, "release() failed with: " + error);
+      }
+      camera.release(onSuccess, onError);
+    }
+  },
+  {
+    key: "set-picture-size-after-release",
+    func: function testSetPictureSize(camera) {
+      camera.setPictureSize({ width: 0, height: 0 });
+      next();
+    }
+  },
+  {
+    key: "set-thumbnail-size-after-release",
+    func: function testSetThumbnailSize(camera) {
+      camera.setThumbnailSize({ width: 0, height: 0 });
+      next();
+    }
+  },
+  {
+    key: "get-sensor-angle-after-release",
+    func: function testGetSensorAngle(camera) {
+      ok(camera.sensorAngle == 0, "camera.sensorAngle = " + camera.sensorAngle);
+      next();
+    }
+  },
+  {
+    key: "resume-preview-after-release",
+    func: function testResumePreview(camera) {
+      camera.resumePreview();
+      next();
+    }
+  },
+  {
+    key: "auto-focus-after-release",
+    func: function testAutoFocus(camera) {
+      function onSuccess(success) {
+        ok(false, "autoFocus() succeeded incorrectly");
+      }
+      function onError(error) {
+        ok(error === "HardwareClosed", "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(error === "HardwareClosed", "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(error === "GeneralFailure", "startRecording() failed with: " + error);
+        next();
+      }
+      var recordingOptions = {
+        profile: 'cif',
+        rotation: 0
+      };
+      camera.startRecording(recordingOptions,
+                            navigator.getDeviceStorage('videos'),
+                            'bug975472.mp4',
+                            onSuccess, onError);
+    }
+  },
+  {
+    key: "stop-recording-after-release",
+    func: function testStopRecording(camera) {
+      camera.stopRecording();
+      next();
+    }
+  },
+  {
+    key: "set-configuration-after-release",
+    func: function testSetConfiguration(camera) {
+      function onSuccess(picture) {
+        ok(false, "setConfiguration() process succeeded incorrectly");
+      }
+      function onError(error) {
+        ok(error === "HardwareClosed", "setConfiguration() failed with: " + error);
+        next();
+      }
+      camera.setConfiguration(config, onSuccess, onError);
+    }
+  },
+];
+
+var testGenerator = function() {
+  for (var i = 0; i < tests.length; ++i ) {
+    yield tests[i];
+  }
+}();
+
+var Camera = {
+  cameraObj: null,
+  _otherPictureSize: null,
+  get viewfinder() {
+    return document.getElementById('viewfinder');
+  },
+  onCameraReady: function () {
+    Camera.nextTest = function() {
+      try {
+        var t = testGenerator.next();
+        info("test: " + t.key);
+        t.func(Camera.cameraObj);
+      } catch(e) {
+        if (e instanceof StopIteration) {
+          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();
+    next();
+  },
+  release: function release() {
+    cameraObj = null;
+  },
+  start: function run_test() {
+    function onSuccess(camera, config) {
+      Camera.cameraObj = camera;
+      Camera.viewfinder.mozSrcObject = camera;
+      Camera.viewfinder.play();
+      ok(camera.capabilities.pictureSizes.length > 0,
+        "capabilities.pictureSizes.length = " +
+        camera.capabilities.pictureSizes.length);
+      Camera._otherPictureSize = camera.capabilities.pictureSizes.slice(-1)[0];
+      camera.setPictureSize(camera.capabilities.pictureSizes[0]);
+      options.pictureSize = Camera._otherPictureSize;
+      options.fileFormat = camera.capabilities.fileFormats[0];
+      info("getCamera callback, setting pictureSize = " + options.pictureSize.toSource());
+      Camera.cameraObj.onPreviewStateChange = function(state) {
+        if (state === 'started') {
+          info("viewfinder is ready and playing");
+          Camera.cameraObj.onPreviewStateChange = null;
+          Camera.onCameraReady();
+        }
+      };
+    };
+    navigator.mozCameras.getCamera(whichCamera, config, 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/callback/test_camera.html
@@ -0,0 +1,242 @@
+<!DOCTYPE HTML>
+<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>
+<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
+  }
+};
+
+var config = {
+  dateTime: Date.now() / 1000,
+  pictureSize: null,
+  fileFormat: 'jpeg',
+  rotation: 90
+};
+
+function onError(e) {
+  ok(false, "Error" + JSON.stringify(e));
+}
+
+var capabilities = [ 'previewSizes', 'pictureSizes', 'fileFormats', 'maxFocusAreas', 'minExposureCompensation',
+                     'maxExposureCompensation', 'stepExposureCompensation', 'maxMeteringAreas', 'videoSizes',
+                     'recorderProfiles', 'zoomRatios', 'isoModes'];
+
+var Camera = {
+  cameraObj: null,
+  _recording: false,
+  _currentTest: null,
+  _autoFocusSupported: 0,
+  _manuallyFocused: false,
+  _flashmodes: null,
+  _pictureSizes: null,
+  _previewSizes: null,
+  _whiteBalanceModes: null,
+  _zoomRatios: null,
+  _sceneModes: null,
+  _focusModes: null,
+  _zoomRatios: null,
+  _testsCompleted: 0,
+  _shutter: 0,
+  _config: {
+    dateTime: Date.now() / 1000,
+    pictureSize: null,
+    fileFormat: 'jpeg',
+    rotation: 90
+  },
+  _tests: null,
+  get viewfinder() {
+    return document.getElementById('viewfinder');
+  },
+  setFlashMode: function camera_setFlash(mode) {
+    this.cameraObj.flashMode = mode;
+  },
+  setFocus: function camera_setfocus(mode) {
+    this.cameraObj.focus = mode;
+  },
+  setZoom: function camera_setZoom(zoom) {
+    this.cameraObj.zoom = zoom;
+  },
+  getZoom: function camera_getZoom() {
+    return this.cameraObj.zoom;
+  },
+  getFileFormats: function camera_formats() {
+    this._fileFormats = this.cameraObj.capabilities.fileFormats;
+  },
+  getFlashModes: function camera_getFlash() {
+    this._flashmodes = this.cameraObj.capabilities.flashModes;
+  },
+  getFocusModes: function camera_getFocus() {
+    this._focusModes = this.cameraObj.capabilities.focusModes;
+  },
+  getSceneModes: function camera_getScene() {
+    this._sceneModes = this.cameraObj.capabilities.sceneModes;
+  },
+  getZoomRatios: function camera_getZoom() {
+    this._zoomRatios = this.cameraObj.capabilities.zoomRatios;
+  },
+  getWhiteBalance: function camera_white() {
+    this._whitebalanceModes = this.cameraObj.capabilities.whiteBalanceModes;
+  },
+  getPictureSizes: function camera_sizes() {
+    this._pictureSizes = this.cameraObj.capabilities.pictureSizes;
+  },
+  getPreviewSizes: function camera_preview() {
+    this._previewSizes = this.cameraObj.capabilities.previewSizes;
+  },
+  getZoomRatios: function camera_preview() {
+    this._zoomRatios = this.cameraObj.capabilities.zoomRatios;
+  },
+  takePictureSuccess: function taken_foto(blob) {
+    var img = new Image();
+    var test = this._currentTest;
+    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");
+        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);
+  },
+  shutter: function onShutter () {
+    Camera._shutter++;
+    
+    ok(Camera._shutter == (Camera._testsCompleted + 1), "on Shutter has been called " +
+                           Camera._shutter + " times");
+
+  },
+  onReady: function onReady() {
+    var camcap = Camera.cameraObj.capabilities;
+    var tests = {};
+    for (var prop in capabilities) {
+      prop = capabilities[prop];
+      ok(camcap[prop] || isFinite(camcap[prop]) || camcap[prop] == null, "Camera Capability: " +
+                    prop + " is exposed, value = " + JSON.stringify(camcap[prop]));
+    }
+    ok(camcap.maxMeteringAreas >= 0, "maxMeteringAreas = " + camcap.maxMeteringAreas);
+    ok(camcap.maxFocusAreas >= 0, "maxFocusAreas = " + camcap.maxFocusAreas);
+    for (var prop in camcap) {
+      if(camcap[prop] && camcap[prop].length > 1)  {
+        tests[prop] = camcap[prop];
+      }
+    }
+    Camera.getPictureSizes();
+    Camera.getPreviewSizes();
+    Camera.getFileFormats();
+    Camera.getFocusModes();
+    Camera.getZoomRatios();
+    ok(Camera._previewSizes.length > 0, "previewSizes length = " + Camera._previewSizes.length);
+    ok(Camera._pictureSizes.length > 0, "picturesizes length = " + Camera._pictureSizes.length);
+    ok(Camera._fileFormats.length > 0, "file formats length = " + Camera._fileFormats.length);
+    ok(camcap.isoModes.length == 0, "ISO modes length = " + camcap.isoModes.length);
+
+    // The emulator doesn't support zoom, so these parameters will be very constrained
+    // For more ambitious tests, see test_camera_fake_parameters.html
+    ok(Camera._zoomRatios.length == 1, "zoom ratios length = " + Camera._zoomRatios.length);
+    ok(Camera.cameraObj.zoom == 1.0, "zoom = " + Camera.cameraObj.zoom);
+    // Test snapping to supported values
+    Camera.cameraObj.zoom = 0.9;
+    ok(Camera.cameraObj.zoom == 1.0, "zoom (lower limit) = " + Camera.cameraObj.zoom);
+    Camera.cameraObj.zoom = 1.1;
+    ok(Camera.cameraObj.zoom == 1.0, "zoom (upper limit) = " + Camera.cameraObj.zoom);
+
+    // Check image quality handling
+    Camera.cameraObj.pictureQuality = 0.0;
+    ok(Camera.cameraObj.pictureQuality == 0.0, "picture quality = " + Camera.cameraObj.pictureQuality);
+    Camera.cameraObj.pictureQuality = -0.1;
+    ok(Camera.cameraObj.pictureQuality == 0.0, "picture quality (minimum limit) = " + Camera.cameraObj.pictureQuality);
+    Camera.cameraObj.pictureQuality = -Math.pow(2, 80);
+    ok(Camera.cameraObj.pictureQuality == 0.0, "picture quality (BIG negative) = " + Camera.cameraObj.pictureQuality);
+    Camera.cameraObj.pictureQuality = 1.0;
+    ok(Camera.cameraObj.pictureQuality == 1.0, "picture quality = " + Camera.cameraObj.pictureQuality);
+    Camera.cameraObj.pictureQuality = 1.1;
+    ok(Camera.cameraObj.pictureQuality == 1.0, "picture quality (maximum limit) = " + Camera.cameraObj.pictureQuality);
+    Camera.cameraObj.pictureQuality = Math.pow(2, 80);
+    ok(Camera.cameraObj.pictureQuality == 1.0, "picture quality (BIG positive) = " + Camera.cameraObj.pictureQuality);
+
+    Camera._tests = new Array();
+    for (var i in Camera._pictureSizes) {
+      for (var l in Camera._fileFormats) {
+        var config = {
+          pictureSize: Camera._pictureSizes[i],
+          fileFormat: Camera._fileFormats[l]
+        };
+        Camera._tests.push(config);
+      }
+    }
+    Camera.runTests();
+  },
+  runTests: function run_tests() {
+    var test = this._tests[this._testsCompleted];
+    this._currentTest = test;
+    Camera.setFlashMode(test.flashMode);
+    config.fileFormat = test.fileFormat;
+    config.pictureSize = test.pictureSize;
+    ok(true, "testing picture size " + JSON.stringify(config.pictureSize));
+    Camera.cameraObj.takePicture(config, this.takePictureSuccess.bind(this), onError);
+  },
+  onConfigChange: function onConfigChange(config) {
+    ok(config.mode === options.mode, "configuration mode = " + config.mode);
+    ok(config.recorderProfile === options.recorderProfile, "recorder profile = " + config.recorderProfile);
+    ok(config.previewSize.width === options.previewSize.width &&
+      config.previewSize.height === options.previewSize.height,
+      "preview size (w x h) = " + config.previewSize.width + " x " + config.previewSize.height);
+  },
+  setUp: function setup_tests() {
+    function onSuccess(camera) {
+      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();
+        }
+      };
+      SimpleTest.expectAssertions(0);
+      ok(true, "Camera Control object has been successfully initialized");
+      Camera.cameraObj.setConfiguration(options, Camera.onConfigChange, onError);
+      Camera.cameraObj.onShutter = Camera.shutter;
+    };
+    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>
new file mode 100644
--- /dev/null
+++ b/dom/camera/test/callback/test_camera_2.html
@@ -0,0 +1,200 @@
+<!DOCTYPE HTML>
+<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>
+<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
+  }
+};
+
+var config = {
+  dateTime: Date.now() / 1000,
+  pictureSize: null,
+  fileFormat: 'jpeg',
+  rotation: 90
+};
+
+function onError(e) {
+  ok(false, "Error" + JSON.stringify(e));
+}
+
+var capabilities = [ 'previewSizes', 'pictureSizes', 'fileFormats', 'maxFocusAreas', 'minExposureCompensation',
+                     'maxExposureCompensation', 'stepExposureCompensation', 'maxMeteringAreas', 'videoSizes',
+                     'recorderProfiles'];
+
+var Camera = {
+  cameraObj: null,
+  _recording: false,
+  _currentTest: null,
+  _autoFocusSupported: 0,
+  _manuallyFocused: false,
+  _flashmodes: null,
+  _pictureSizes: null,
+  _previewSizes: null,
+  _whiteBalanceModes: null,
+  _zoomRatios: null,
+  _sceneModes: null,
+  _focusModes: null,
+  _testsCompleted: 0,
+  _shutter: 0,
+  _config: {
+    dateTime: Date.now() / 1000,
+    pictureSize: null,
+    fileFormat: 'jpeg',
+    rotation: 90
+  },
+  _tests: null,
+  get viewfinder() {
+    return document.getElementById('viewfinder');
+  },
+  setFlashMode: function camera_setFlash(mode) {
+    this.cameraObj.flashMode = mode;
+  },
+  setFocus: function camera_setfocus(mode) {
+    this.cameraObj.focus = mode;
+  },
+  getFileFormats: function camera_formats() {
+    this._fileFormats = this.cameraObj.capabilities.fileFormats;
+  },
+  getFlashModes: function camera_getFlash() {
+    this._flashmodes = this.cameraObj.capabilities.flashModes;
+  },
+  getFocusModes: function camera_getFocus() {
+    this._focusModes = this.cameraObj.capabilities.focusModes;
+  },
+  getSceneModes: function camera_getScene() {
+    this._sceneModes = this.cameraObj.capabilities.sceneModes;
+  },
+  getZoomRatios: function camera_getZoom() {
+    this._zoomRatios = this.cameraObj.capabilities.zoomRatios;
+  },
+  getWhiteBalance: function camera_white() {
+    this._whitebalanceModes = this.cameraObj.capabilities.whiteBalanceModes;
+  },
+  getPictureSizes: function camera_sizes() {
+    this._pictureSizes = this.cameraObj.capabilities.pictureSizes;
+  },
+  getPreviewSizes: function camera_preview() {
+    this._previewSizes = this.cameraObj.capabilities.previewSizes;
+  },
+  takePictureSuccess: function taken_foto(blob) {
+    var img = new Image();
+    var test = this._currentTest;
+    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");
+        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);
+  },
+  shutter: function onShutter () {
+    Camera._shutter++;
+    
+    ok(Camera._shutter == (Camera._testsCompleted + 1), "on Shutter has been called " +
+                           Camera._shutter + " times");
+
+  },
+  onReady: function onReady() {
+    var camcap = Camera.cameraObj.capabilities;
+    var tests = {};
+    for (var prop in capabilities) {
+      prop = capabilities[prop];
+      ok(camcap[prop] || isFinite(camcap[prop]) || camcap[prop] == null, "Camera Capability: " +
+                    prop + " is exposed, value = " + JSON.stringify(camcap[prop]));
+    } 
+    for (var prop in camcap) {
+      if(camcap[prop] && camcap[prop].length > 1)  {
+        tests[prop] = camcap[prop];
+      }
+    }
+    Camera.getPictureSizes();
+    Camera.getPreviewSizes();
+    Camera.getFileFormats();
+    Camera.getFocusModes();
+    ok(Camera._previewSizes.length > 0, "previewSizes length = " + Camera._previewSizes.length);
+    ok(Camera._pictureSizes.length > 0, "picturesizes length = " + Camera._pictureSizes.length);
+    ok(Camera._fileFormats.length > 0, "file formats length = " + Camera._fileFormats.length);
+    Camera._tests = new Array();
+    for (var i in Camera._pictureSizes) {
+      for (var l in Camera._fileFormats) {
+        var config = {
+          pictureSize: Camera._pictureSizes[i],
+          fileFormat: Camera._fileFormats[l]
+        };
+        Camera._tests.push(config);
+      }
+    }
+    Camera.runTests();
+  },
+  runTests: function run_tests() {
+    var test = this._tests[this._testsCompleted];
+    this._currentTest = test;
+    Camera.setFlashMode(test.flashMode);
+    config.fileFormat = test.fileFormat;
+    config.pictureSize = test.pictureSize;
+    ok(true, "testing picture size " + JSON.stringify(config.pictureSize));
+    Camera.cameraObj.takePicture(config, this.takePictureSuccess.bind(this), onError);
+  },
+  setUp: function setup_tests() {
+    function onSuccess(camera, config) {
+      ok(true, "Camera Control object has been successfully initialized");
+      ok(config.mode === options.mode, "configuration mode = " + config.mode);
+      ok(config.recorderProfile === options.recorderProfile, "recorder profile = " + config.recorderProfile);
+      ok(config.previewSize.width === options.previewSize.width &&
+        config.previewSize.height === options.previewSize.height,
+        "preview size (w x h) = " + config.previewSize.width + " x " + config.previewSize.height);
+      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();
+        }
+      };
+      SimpleTest.expectAssertions(0);
+      Camera.cameraObj.onShutter = Camera.shutter;
+    };
+    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/callback/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/callback/test_camera_hardware_auto_focus_moving_cb.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=965421
+-->
+<head>
+  <title>Bug 965421 - Test camera hardware API for Auto focus moving Callback</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=965421">Mozilla Bug 965421</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
+  }
+};
+
+const PREF_AUTOFOCUSCALLBACK_ENABLED = "camera.control.autofocus_moving_callback.enabled";
+
+var cameraObj;
+var oldPref;
+
+// Shorthand functions
+function end() {
+  function reallyEnd() {
+    CameraTest.end();
+  }
+  if (oldPref) {
+    SpecialPowers.pushPrefEnv(
+      {'set': [[PREF_AUTOFOCUSCALLBACK_ENABLED, oldPref]]}, reallyEnd);
+  } else {
+    SpecialPowers.pushPrefEnv(
+      {'clear': [[PREF_AUTOFOCUSCALLBACK_ENABLED]]}, reallyEnd);
+  }
+}
+function next() {
+  CameraTest.next();
+}
+
+var tests = [
+  {
+    key: "autofocus-moving-true",
+    func: function testAutoFocusMovingIsTrue(camera) {
+      camera.onAutoFocusMoving = function(aIsMoving) {
+        ok(aIsMoving == true,"onAutoFocusMoving callback received true correctly");
+        camera.focusMode = 'auto';
+        next();
+      }
+      camera.focusMode = 'continuous-picture';
+    }
+  },
+  {
+    key: "autofocus-moving-false",
+    func: function testAutoFocusMovingIsFalse(camera) {
+      camera.onAutoFocusMoving = function(aIsMoving) {
+        ok(aIsMoving == false, "onAutoFocusMoving callback received false correctly");
+        camera.focusMode = 'auto';
+        end();
+      }
+      camera.focusMode = 'continuous-video';
+    }
+  },
+];
+
+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;
+});
+
+// Must call CameraTest.begin() before any other async methods.
+CameraTest.begin("hardware", function(test) {
+  // If the pref doesn't exist, this get will fail; catch it and continue.
+  try {
+    oldPref = SpecialPowers.getBoolPref(PREF_AUTOFOCUSCALLBACK_ENABLED);
+  } catch(e) { }
+
+  SpecialPowers.pushPrefEnv({'set': [[PREF_AUTOFOCUSCALLBACK_ENABLED, true]]}, function() {
+    var enabled;
+    try {
+      enabled = SpecialPowers.getBoolPref(PREF_AUTOFOCUSCALLBACK_ENABLED);
+    } catch(e) { }
+    ok(enabled, PREF_AUTOFOCUSCALLBACK_ENABLED + " is " + enabled);
+
+    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/callback/test_camera_hardware_face_detection.html
@@ -0,0 +1,320 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=965420
+-->
+<head>
+  <title>Bug 965420 - Test camera hardware API for face detection</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=965420">Mozilla Bug 965420</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
+  }
+};
+
+const PREF_FACEDETECTION_ENABLED = "camera.control.face_detection.enabled";
+
+var cameraObj;
+var oldPref;
+
+// Shorthand functions
+function end() {
+  function reallyEnd() {
+    CameraTest.end();
+  }
+  if (oldPref) {
+    SpecialPowers.pushPrefEnv(
+      {'set': [[PREF_FACEDETECTION_ENABLED, oldPref]]}, reallyEnd);
+  } else {
+    SpecialPowers.pushPrefEnv(
+      {'clear': [[PREF_FACEDETECTION_ENABLED]]}, reallyEnd);
+  }
+}
+function next() {
+  CameraTest.next();
+}
+
+function compareFaces(aFaces, expected)
+{
+  ok(aFaces, "have detected faces object");
+  ok(aFaces.length == expected.faces.length,
+    "expected=" + expected.faces.length + ", got=" + aFaces.length);
+  aFaces.forEach(function (face, index) {
+    let result = compareFace(face, expected.faces[index]);
+    ok(result === "ok", "face check: " + result);
+    if (result !== "ok") {
+      return false;
+    }
+  });
+  return true;
+}
+
+function compareFace(aFace, expected)
+{
+  if (aFace.id != expected.id) {
+    return "expected face.id=" + expected.id + ", got=" + aFace.id;
+  }
+  if (aFace.score != expected.score) {
+    return "expected face.score=" + expected.score + ", got=" + aFace.score;
+  }
+  if (!aFace.bounds) {
+    return "face.bounds is missing";
+  }
+  if (aFace.bounds.left != expected.bounds.left ||
+      aFace.bounds.top != expected.bounds.top ||
+      aFace.bounds.right != expected.bounds.right ||
+      aFace.bounds.bottom != expected.bounds.bottom) {
+    return "expected face.bounds=" + expected.bounds.toSource() +
+      ", got=({left:" + aFace.bounds.left + ", top:" + aFace.bounds.top + ", right:" + aFace.bounds.right + ", bottom:" + aFace.bounds.bottom + "})";
+  }
+
+  if (aFace.leftEye && !expected.leftEye) {
+    return "expected null face.leftEye, got=({x:" + aFace.leftEye.x + ", y:" + aFace.leftEye.y + "})";
+  }
+  if (!aFace.leftEye && expected.leftEye) {
+    return "expected face.leftEye=" + expected.leftEye.toSource() + ", got null leftEye";
+  }
+  if (aFace.leftEye && expected.leftEye &&
+      (aFace.leftEye.x != expected.leftEye.x || aFace.leftEye.y != expected.leftEye.y)) {
+    return "expected face.leftEye=" + expected.leftEye.toSource() +
+      ", got=({x:" + aFace.leftEye.x + ", y:" + aFace.leftEye.y + "})";
+  }
+
+  if (aFace.rightEye && !expected.rightEye) {
+    return "expected null face.rightEye, got=({x:" + aFace.rightEye.x + ", y:" + aFace.rightEye.y + "})";
+  }
+  if (!aFace.rightEye && expected.rightEye) {
+    return "expected face.rightEye=" + expected.rightEye.toSource() + ", got null rightEye";
+  }
+  if (aFace.rightEye && expected.rightEye &&
+      (aFace.rightEye.x != expected.rightEye.x || aFace.rightEye.y != expected.rightEye.y)) {
+    return "expected face.rightEye=" + expected.rightEye.toSource() +
+      ", got=({x:" + aFace.rightEye.x + ", y:" + aFace.rightEye.y + "})";
+  }
+
+  if (aFace.mouth && !expected.mouth) {
+    return "expected null face.mouth, got=({x:" + aFace.mouth.x + ", y:" + aFace.mouth.y + "})";
+  }
+  if (!aFace.mouth && expected.mouth) {
+    return "expected face.mouth=" + expected.mouth.toSource() + ", got null mouth";
+  }
+  if (aFace.mouth && expected.mouth &&
+      (aFace.mouth.x != expected.mouth.x || aFace.mouth.y != expected.mouth.y)) {
+    return "expected face.mouth=" + expected.mouth.toSource() +
+      ", got=({x:" + aFace.mouth.x + ", y:" + aFace.mouth.y + "})";
+  }
+
+  return "ok";
+}
+
+var tests = [
+  {
+    key: "face-detection-detected-one-face",
+    func: function testFaceDetectionFoundOneFace(camera) {
+      var expected = {
+        faces: [ {
+          id:       1,
+          score:    2,
+          bounds: {
+            left:   3,
+            top:    4,
+            right:  5,
+            bottom: 6
+          },
+          leftEye: {
+            x:      7,
+            y:      8
+          },
+          rightEye: {
+            x:      9,
+            y:      10
+          },
+          mouth: {
+            x:      11,
+            y:      12
+          }
+        } ]
+      };
+      camera.onFacesDetected = function(aFaces) {
+        ok(compareFaces(aFaces, expected),
+          "onFaceDetected received the detected faces correctly");
+        camera.stopFaceDetection();
+        next();
+      }
+      camera.startFaceDetection();
+    }
+  },
+  {
+    key: "face-detection-detected-two-faces",
+    func: function testFaceDetectionFoundTwoFace(camera) {
+      var expected = {
+        faces: [ {
+          id:       1,
+          score:    2,
+          bounds: {
+            left:   3,
+            top:    4,
+            right:  5,
+            bottom: 6
+          },
+          leftEye: {
+            x:      7,
+            y:      8
+          },
+          rightEye: {
+            x:      9,
+            y:      10
+          },
+          mouth: {
+            x:      11,
+            y:      12
+          }
+        },
+        {
+          id:       13,
+          score:    14,
+          bounds: {
+            left:   15,
+            top:    16,
+            right:  17,
+            bottom: 18
+          },
+          leftEye: {
+            x:      19,
+            y:      20
+          },
+          rightEye: {
+            x:      21,
+            y:      22
+          },
+          mouth: {
+            x:      23,
+            y:      24
+          }
+        } ]
+      };
+      camera.onFacesDetected = function(aFaces) {
+        ok(compareFaces(aFaces, expected),
+          "onFaceDetected received the detected faces correctly");
+        camera.stopFaceDetection();
+        next();
+      }
+      camera.startFaceDetection();
+    }
+  },
+  {
+    key: "face-detection-detected-one-face-no-features",
+    func: function (camera) {
+      var expected = {
+        faces: [ {
+          id:       1,
+          score:    100,
+          bounds: {
+            left:   3,
+            top:    4,
+            right:  5,
+            bottom: 6
+          },
+          leftEye:  null,
+          rightEye: null,
+          mouth:    null
+        } ]
+      };
+      camera.onFacesDetected = function(aFaces) {
+        ok(compareFaces(aFaces, expected),
+          "onFaceDetected received the detected faces correctly");
+        camera.stopFaceDetection();
+        next();
+      }
+      camera.startFaceDetection();
+    }
+  },
+  {
+    key: "face-detection-no-faces-detected",
+    func: function (camera) {
+      var expected = {
+        faces: []
+      };
+      camera.onFacesDetected = function(aFaces) {
+        ok(compareFaces(aFaces, expected),
+          "onFaceDetected received the detected faces correctly");
+        camera.stopFaceDetection();
+        next();
+      }
+      camera.startFaceDetection();
+    }
+  },
+];
+
+var testGenerator = function() {
+  for (var i = 0; i < tests.length; ++i ) {
+    yield tests[i];
+  }
+}();
+
+window.addEventListener('beforeunload', function() {
+  document.getElementById('viewfinder').mozSrcObject = null;
+  if (cameraObj) {
+    cameraObj.release();
+    cameraObj = null;
+  }
+});
+
+// Must call CameraTest.begin() before any other async methods.
+CameraTest.begin("hardware", function(test) {
+  // If the pref doesn't exist, this get will fail; catch it and continue.
+  try {
+    oldPref = SpecialPowers.getBoolPref(PREF_FACEDETECTION_ENABLED);
+  } catch(e) { }
+
+  SpecialPowers.pushPrefEnv({'set': [[PREF_FACEDETECTION_ENABLED, true]]}, function() {
+    var enabled;
+    try {
+      enabled = SpecialPowers.getBoolPref(PREF_FACEDETECTION_ENABLED);
+    } catch(e) { }
+    ok(enabled, PREF_FACEDETECTION_ENABLED + " is " + enabled);
+
+    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/callback/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/callback/test_camera_hardware_init_failure.html
@@ -0,0 +1,96 @@
+<!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">
+
+SimpleTest.waitForExplicitFinish();
+
+var whichCamera = navigator.mozCameras.getListOfCameras()[0];
+var initialConfig = {
+  mode: 'picture',
+  recorderProfile: 'cif',
+  previewSize: {
+    width: 352,
+    height: 288
+  }
+};
+
+var tests = [
+  {
+    name: "init-failure",
+    key: "init-failure",
+    func: function testInitFailure(test) {
+      function onSuccess(camera, config) {
+        ok(false, "onSuccess called incorrectly");
+        camera.release();
+        test.next();
+      }
+      function onError(error) {
+        ok(true, "onError called correctly on init failure");
+        test.next();
+      }
+      info("Running test: init-failure");
+      navigator.mozCameras.getCamera(whichCamera, initialConfig, onSuccess, onError);
+    }
+  },
+  /* This test case (init-success) *must* follow the preceeding test case
+     (init-failure) in order for the desired condition to be verified */
+  {
+    name: "init-success",
+    key: "",
+    func: function(test) {
+      function onSuccess(camera, config) {
+        ok(true, "onSuccess called correctly");
+        camera.release();
+        test.next();
+      }
+      function onError(error) {
+        ok(false, "onError called incorrectly: " + error);
+        test.next();
+      }
+      info("Running test: init-success");
+      navigator.mozCameras.getCamera(whichCamera, initialConfig, onSuccess, onError)
+    }
+  }
+];
+
+var testGenerator = function() {
+  for (var i = 0; i < tests.length; ++i ) {
+    yield tests[i];
+  }
+}();
+
+CameraTest.begin("hardware", function(test) {
+  CameraTest.next = function() {
+    try {
+      var t = testGenerator.next();
+      test.set(t.key, t.func.bind(undefined, CameraTest));
+    } catch(e) {
+      if (e instanceof StopIteration) {
+        CameraTest.end();
+      } else {
+        throw e;
+      }
+    }
+  };
+  CameraTest.next();
+});
+
+</script>
+</body>
+
+</html>
--- a/dom/camera/test/camera_common.js
+++ b/dom/camera/test/camera_common.js
@@ -117,16 +117,17 @@ var CameraTest = (function() {
   function testEnd(callback) {
     // A chain of clean-up functions....
     function allCleanedUp() {
       SimpleTest.finish();
       if (callback) {
         callback();
       }
     }
+
     function cleanUpTestEnabled() {
       var next = allCleanedUp;
       if (oldTestEnabled) {
         SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_ENABLED, oldTestEnabled]]}, next);
       } else {
         SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_ENABLED]]}, next);
       }
     }
--- a/dom/camera/test/mochitest.ini
+++ b/dom/camera/test/mochitest.ini
@@ -1,11 +1,20 @@
 [DEFAULT]
 support-files = camera_common.js
 
+[callback/test_camera.html]
+[callback/test_camera_2.html]
+[callback/test_camera_3.html]
+[callback/test_camera_hardware_init_failure.html]
+[callback/test_camera_hardware_failures.html]
+[callback/test_bug975472.html]
+[callback/test_camera_hardware_face_detection.html]
+[callback/test_camera_hardware_auto_focus_moving_cb.html]
+[callback/test_bug1022766.html]
 [test_camera.html]
 [test_camera_2.html]
 [test_camera_3.html]
 [test_camera_hardware_init_failure.html]
 [test_camera_hardware_failures.html]
 [test_bug975472.html]
 [test_camera_fake_parameters.html]
 [test_camera_hardware_face_detection.html]
--- a/dom/camera/test/test_bug1022766.html
+++ b/dom/camera/test/test_bug1022766.html
@@ -18,17 +18,17 @@ var config = {
   recorderProfile: 'cif',
   previewSize: {
     width: 352,
     height: 288
   }
 };
 
 function onError(e) {
-  ok(false, "Error" + JSON.stringify(e));
+  ok(false, "Error " + e);
 }
 
 var Camera = {
   cameraObj: null,
   _otherPictureSize: null,
   get viewfinder() {
     return document.getElementById('viewfinder');
   },
@@ -42,43 +42,44 @@ var Camera = {
       CameraTest.end();
     }
   },
 
   successOne: function test_successOne(focused) {
     ok(false, "First call to autoFocus() succeeded unexpectedly");
   },
   failureOne: function test_failureOne(error) {
-    ok(error == "AutoFocusInterrupted", "First call to autoFocus() failed with: "
+    ok(error.name == "NS_ERROR_IN_PROGRESS", "First call to autoFocus() failed with: "
       + error);
     Camera.firstCallFailed = true;
     Camera.checkForDone();
   },
   successTwo: function test_successTwo(focused) {
     ok(true, "Second call to autoFocus() succeeded");
     Camera.secondCallSucceeded = true;
     Camera.checkForDone();
   },
   failureTwo: function test_failureTwo(error) {
     ok(false, "Second call to autoFocus() failed unexpectedly with: " + error);
   },
 
   start: function test_start() {
-    function onSuccess(camera, config) {
+    function onSuccess(d) {
+      var camera = d.camera;
       Camera.cameraObj = camera;
       Camera.viewfinder.mozSrcObject = camera;
       Camera.viewfinder.play();
 
       // It doesn't matter if the emulator supports focus or not;
       // this is just testing the sequencing.
-      camera.autoFocus(Camera.successOne, Camera.failureOne);
-      camera.autoFocus(Camera.successTwo, Camera.failureTwo);
+      camera.autoFocus().then(Camera.successOne, Camera.failureOne);
+      camera.autoFocus().then(Camera.successTwo, Camera.failureTwo);
     };
 
-    navigator.mozCameras.getCamera(whichCamera, config, onSuccess, onError);
+    navigator.mozCameras.getCamera(whichCamera, config).then(onSuccess, onError);
   }
 }
 
 window.addEventListener('beforeunload', function() {
   Camera.viewfinder.mozSrcObject = null;
   if (Camera.cameraObj) {
     Camera.cameraObj.release();
     Camera.cameraObj = null;
--- a/dom/camera/test/test_bug1037322.html
+++ b/dom/camera/test/test_bug1037322.html
@@ -41,33 +41,35 @@ var Camera = {
          cfg.previewSize.height === config.previewSize.height,
          "Configured preview size = " + cfg.previewSize.width + "x" + cfg.previewSize.height);
       ok(cfg.recorderProfile === config.recorderProfile,
          "Configured recorder profile = '" + cfg.recorderProfile + "'");
 
       SimpleTest.finish();
     }
 
-    function getCamera_onSuccess(camera, cfg) {
+    function getCamera_onSuccess(d) {
+      var camera = d.camera;
+      var cfg = d.configuration;
       Camera.cameraObj = camera;
       Camera.viewfinder.mozSrcObject = camera;
       Camera.viewfinder.play();
 
       // Check the default configuration
       ok(cfg.mode === "unspecified", "Initial mode = " + cfg.mode);
       ok(cfg.previewSize.width === 0 && cfg.previewSize.height === 0,
          "Initial preview size = " + cfg.previewSize.width + "x" + cfg.previewSize.height);
       ok(cfg.recorderProfile === "",
          "Initial recorder profile = '" + cfg.recorderProfile + "'");
 
       // Apply our specific configuration
-      camera.setConfiguration(config, setConfig_onSuccess, onError);
+      camera.setConfiguration(config).then(setConfig_onSuccess, onError);
     }
 
-    navigator.mozCameras.getCamera(whichCamera, {}, getCamera_onSuccess, onError);
+    navigator.mozCameras.getCamera(whichCamera, {}).then(getCamera_onSuccess, onError);
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 
 window.addEventListener('beforeunload', function() {
   Camera.viewfinder.mozSrcObject = null;
   if (Camera.cameraObj) {
--- a/dom/camera/test/test_bug975472.html
+++ b/dom/camera/test/test_bug975472.html
@@ -26,17 +26,17 @@ var options = {
   position: {
     latitude: 43.645687,
     longitude: -79.393661
   },
   dateTime: Date.now()
 };
 
 function onError(e) {
-  ok(false, "Error" + JSON.stringify(e));
+  ok(false, "Error " + e);
 }
 function next() {
   Camera.nextTest();
 }
 
 // The array of tests
 var tests = [
   {
@@ -44,17 +44,17 @@ var tests = [
     func: function testAutoFocus(camera) {
       function onSuccess(success) {
         ok(true, "release() succeeded");
         next();
       }
       function onError(error) {
         ok(false, "release() failed with: " + error);
       }
-      camera.release(onSuccess, onError);
+      camera.release().then(onSuccess, onError);
     }
   },
   {
     key: "set-picture-size-after-release",
     func: function testSetPictureSize(camera) {
       camera.setPictureSize({ width: 0, height: 0 });
       next();
     }
@@ -82,73 +82,72 @@ var tests = [
   },
   {
     key: "auto-focus-after-release",
     func: function testAutoFocus(camera) {
       function onSuccess(success) {
         ok(false, "autoFocus() succeeded incorrectly");
       }
       function onError(error) {
-        ok(error === "HardwareClosed", "autoFocus() failed with: " + error);
+        ok(error.name === "NS_ERROR_NOT_INITIALIZED", "autoFocus() failed with: " + error);
         next();
       }
-      camera.autoFocus(onSuccess, onError);
+      camera.autoFocus().then(onSuccess, onError);
     }
   },
   {
     key: "take-picture-after-release",
     func: function testTakePicture(camera) {
       function onSuccess(picture) {
         ok(false, "takePicture() succeeded incorrectly");
       }
       function onError(error) {
-        ok(error === "HardwareClosed", "takePicture() failed with: " + error);
+        ok(error.name === "NS_ERROR_NOT_INITIALIZED", "takePicture() failed with: " + error);
         next();
       }
-      camera.takePicture(null, onSuccess, onError);
+      camera.takePicture(null).then(onSuccess, onError);
     }
   },
   {
     key: "start-recording-after-release",
     func: function testStartRecording(camera) {
       function onSuccess(picture) {
         ok(false, "startRecording() process succeeded incorrectly");
       }
       function onError(error) {
-        ok(error === "GeneralFailure", "startRecording() failed with: " + error);
+        ok(error.name === "NS_ERROR_FAILURE", "startRecording() failed with: " + error);
         next();
       }
       var recordingOptions = {
         profile: 'cif',
         rotation: 0
       };
       camera.startRecording(recordingOptions,
                             navigator.getDeviceStorage('videos'),
-                            'bug975472.mp4',
-                            onSuccess, onError);
+                            'bug975472.mp4').then(onSuccess, onError);
     }
   },
   {
     key: "stop-recording-after-release",
     func: function testStopRecording(camera) {
       camera.stopRecording();
       next();
     }
   },
   {
     key: "set-configuration-after-release",
     func: function testSetConfiguration(camera) {
       function onSuccess(picture) {
         ok(false, "setConfiguration() process succeeded incorrectly");
       }
       function onError(error) {
-        ok(error === "HardwareClosed", "setConfiguration() failed with: " + error);
+        ok(error.name === "NS_ERROR_NOT_INITIALIZED", "setConfiguration() failed with: " + error);
         next();
       }
-      camera.setConfiguration(config, onSuccess, onError);
+      camera.setConfiguration(config).then(onSuccess, onError);
     }
   },
 ];
 
 var testGenerator = function() {
   for (var i = 0; i < tests.length; ++i ) {
     yield tests[i];
   }
@@ -178,37 +177,39 @@ var Camera = {
     // to make sure they properly handle being in this state.
     Camera.cameraObj.release();
     next();
   },
   release: function release() {
     cameraObj = null;
   },
   start: function run_test() {
-    function onSuccess(camera, config) {
+    function onSuccess(d) {
+      var camera = d.camera;
       Camera.cameraObj = camera;
+      var onPreviewStateChange = function(e) {
+        if (e.newState === 'started') {
+          info("viewfinder is ready and playing");
+          Camera.cameraObj.removeEventListener('previewstatechange', onPreviewStateChange);
+          Camera.onCameraReady();
+        }
+      };
+      camera.addEventListener('previewstatechange', onPreviewStateChange);
       Camera.viewfinder.mozSrcObject = camera;
       Camera.viewfinder.play();
       ok(camera.capabilities.pictureSizes.length > 0,
         "capabilities.pictureSizes.length = " +
         camera.capabilities.pictureSizes.length);
       Camera._otherPictureSize = camera.capabilities.pictureSizes.slice(-1)[0];
       camera.setPictureSize(camera.capabilities.pictureSizes[0]);
       options.pictureSize = Camera._otherPictureSize;
       options.fileFormat = camera.capabilities.fileFormats[0];
       info("getCamera callback, setting pictureSize = " + options.pictureSize.toSource());
-      Camera.cameraObj.onPreviewStateChange = function(state) {
-        if (state === 'started') {
-          info("viewfinder is ready and playing");
-          Camera.cameraObj.onPreviewStateChange = null;
-          Camera.onCameraReady();
-        }
-      };
     };
-    navigator.mozCameras.getCamera(whichCamera, config, onSuccess, onError);
+    navigator.mozCameras.getCamera(whichCamera, config).then(onSuccess, onError);
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 
 window.addEventListener('beforeunload', function() {
   Camera.viewfinder.mozSrcObject = null;
   Camera.cameraObj.release();
--- a/dom/camera/test/test_camera.html
+++ b/dom/camera/test/test_camera.html
@@ -24,17 +24,17 @@ var options = {
 var config = {
   dateTime: Date.now() / 1000,
   pictureSize: null,
   fileFormat: 'jpeg',
   rotation: 90
 };
 
 function onError(e) {
-  ok(false, "Error" + JSON.stringify(e));
+  ok(false, "Error " + e);
 }
 
 var capabilities = [ 'previewSizes', 'pictureSizes', 'fileFormats', 'maxFocusAreas', 'minExposureCompensation',
                      'maxExposureCompensation', 'stepExposureCompensation', 'maxMeteringAreas', 'videoSizes',
                      'recorderProfiles', 'zoomRatios', 'isoModes'];
 
 var Camera = {
   cameraObj: null,
@@ -97,22 +97,27 @@ var Camera = {
   },
   getPreviewSizes: function camera_preview() {
     this._previewSizes = this.cameraObj.capabilities.previewSizes;
   },
   getZoomRatios: function camera_preview() {
     this._zoomRatios = this.cameraObj.capabilities.zoomRatios;
   },
   takePictureSuccess: function taken_foto(blob) {
+    ok(blob.size > 100 , "Blob Size Gathered = " + blob.size);
+    ok("image/" + test.fileFormat ==  blob.type, "Blob Type = " + blob.type);
+  },
+  takePictureEvent: function taken_foto_evt(e) {
+    var blob = e.data;
     var img = new Image();
     var test = this._currentTest;
     img.onload = function Imgsize() {
-      ok(this.width == test.pictureSize.width, "The image taken has the width " + 
+      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 " + 
+      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");
         SimpleTest.finish();
       } else {
         Camera.runTests();
       }
@@ -191,43 +196,46 @@ var Camera = {
   },
   runTests: function run_tests() {
     var test = this._tests[this._testsCompleted];
     this._currentTest = test;
     Camera.setFlashMode(test.flashMode);
     config.fileFormat = test.fileFormat;
     config.pictureSize = test.pictureSize;
     ok(true, "testing picture size " + JSON.stringify(config.pictureSize));
-    Camera.cameraObj.takePicture(config, this.takePictureSuccess.bind(this), onError);
+    Camera.cameraObj.takePicture(config).then(this.takePictureSuccess.bind(this), onError);
   },
   onConfigChange: function onConfigChange(config) {
     ok(config.mode === options.mode, "configuration mode = " + config.mode);
     ok(config.recorderProfile === options.recorderProfile, "recorder profile = " + config.recorderProfile);
     ok(config.previewSize.width === options.previewSize.width &&
       config.previewSize.height === options.previewSize.height,
       "preview size (w x h) = " + config.previewSize.width + " x " + config.previewSize.height);
   },
+  onPreviewStateChange: function onPreviewStateChange(e) {
+    if (e.newState === 'started') {
+      ok(true, "viewfinder is ready and playing");
+      Camera.cameraObj.removeEventListener('previewstatechange', Camera.onPreviewStateChange);
+      Camera.onReady();
+    }
+  },
   setUp: function setup_tests() {
-    function onSuccess(camera) {
-      Camera.cameraObj = camera;
-      Camera.viewfinder.mozSrcObject = camera;
+    function onSuccess(d) {
+      Camera.cameraObj = d.camera;
+      Camera.cameraObj.addEventListener('previewstatechange', Camera.onPreviewStateChange);
+      Camera.cameraObj.addEventListener('configurationchanged', Camera.onConfigChange);
+      Camera.cameraObj.addEventListener('shutter', Camera.shutter);
+      Camera.cameraObj.addEventListener('picture', Camera.takePictureEvent.bind(Camera));
+      Camera.viewfinder.mozSrcObject = d.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();
-        }
-      };
       SimpleTest.expectAssertions(0);
       ok(true, "Camera Control object has been successfully initialized");
-      Camera.cameraObj.setConfiguration(options, Camera.onConfigChange, onError);
-      Camera.cameraObj.onShutter = Camera.shutter;
+      Camera.cameraObj.setConfiguration(options).then(Camera.onConfigChange, onError);
     };
-    navigator.mozCameras.getCamera(whichCamera, null, onSuccess, onError);
+    navigator.mozCameras.getCamera(whichCamera, null).then(onSuccess, onError);
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 
 window.addEventListener('beforeunload', function() {
   Camera.viewfinder.mozSrcObject = null;
   Camera.cameraObj.release();
--- a/dom/camera/test/test_camera_2.html
+++ b/dom/camera/test/test_camera_2.html
@@ -24,17 +24,17 @@ var options = {
 var config = {
   dateTime: Date.now() / 1000,
   pictureSize: null,
   fileFormat: 'jpeg',
   rotation: 90
 };
 
 function onError(e) {
-  ok(false, "Error" + JSON.stringify(e));
+  ok(false, "Error " + e);
 }
 
 var capabilities = [ 'previewSizes', 'pictureSizes', 'fileFormats', 'maxFocusAreas', 'minExposureCompensation',
                      'maxExposureCompensation', 'stepExposureCompensation', 'maxMeteringAreas', 'videoSizes',
                      'recorderProfiles'];
 
 var Camera = {
   cameraObj: null,
@@ -108,29 +108,37 @@ var Camera = {
       }
     }
     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);
   },
   shutter: function onShutter () {
     Camera._shutter++;
-    
+
     ok(Camera._shutter == (Camera._testsCompleted + 1), "on Shutter has been called " +
                            Camera._shutter + " times");
 
   },
+  onPreviewStateChange: function onPreviewStateChange(e) {
+      ok(true, "viewfinder state change " + e);
+    if (e.newState === 'started') {
+      ok(true, "viewfinder is ready and playing");
+      Camera.cameraObj.removeEventListener('previewstatechange', Camera.onPreviewStateChange);
+      Camera.onReady();
+    }
+  },
   onReady: function onReady() {
     var camcap = Camera.cameraObj.capabilities;
     var tests = {};
     for (var prop in capabilities) {
       prop = capabilities[prop];
       ok(camcap[prop] || isFinite(camcap[prop]) || camcap[prop] == null, "Camera Capability: " +
                     prop + " is exposed, value = " + JSON.stringify(camcap[prop]));
-    } 
+    }
     for (var prop in camcap) {
       if(camcap[prop] && camcap[prop].length > 1)  {
         tests[prop] = camcap[prop];
       }
     }
     Camera.getPictureSizes();
     Camera.getPreviewSizes();
     Camera.getFileFormats();
@@ -152,40 +160,35 @@ var Camera = {
   },
   runTests: function run_tests() {
     var test = this._tests[this._testsCompleted];
     this._currentTest = test;
     Camera.setFlashMode(test.flashMode);
     config.fileFormat = test.fileFormat;
     config.pictureSize = test.pictureSize;
     ok(true, "testing picture size " + JSON.stringify(config.pictureSize));
-    Camera.cameraObj.takePicture(config, this.takePictureSuccess.bind(this), onError);
+    Camera.cameraObj.takePicture(config).then(this.takePictureSuccess.bind(this), onError);
   },
   setUp: function setup_tests() {
-    function onSuccess(camera, config) {
+    function onSuccess(d) {
+      var config = d.configuration;
       ok(true, "Camera Control object has been successfully initialized");
       ok(config.mode === options.mode, "configuration mode = " + config.mode);
       ok(config.recorderProfile === options.recorderProfile, "recorder profile = " + config.recorderProfile);
       ok(config.previewSize.width === options.previewSize.width &&
         config.previewSize.height === options.previewSize.height,
         "preview size (w x h) = " + config.previewSize.width + " x " + config.previewSize.height);
-      Camera.cameraObj = camera;
-      Camera.viewfinder.mozSrcObject = camera;
+      Camera.cameraObj = d.camera;
+      Camera.cameraObj.addEventListener('previewstatechange', Camera.onPreviewStateChange);
+      Camera.cameraObj.addEventListener('shutter', Camera.shutter);
+      Camera.viewfinder.mozSrcObject = d.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();
-        }
-      };
       SimpleTest.expectAssertions(0);
-      Camera.cameraObj.onShutter = Camera.shutter;
     };
-    navigator.mozCameras.getCamera(whichCamera, options, onSuccess, onError);
+    navigator.mozCameras.getCamera(whichCamera, options).then(onSuccess, onError);
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 
 window.addEventListener('beforeunload', function() {
   Camera.viewfinder.mozSrcObject = null;
   Camera.cameraObj.release();
--- a/dom/camera/test/test_camera_3.html
+++ b/dom/camera/test/test_camera_3.html
@@ -17,51 +17,52 @@ var options = {
   recorderProfile: 'cif',
   previewSize: {
     width: 352,
     height: 288
   }
 };
 
 function onError(e) {
-  ok(false, "Error" + JSON.stringify(e));
+  ok(false, "Error " + 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 onSuccess(d) {
+      ok(false, "Unexpectedly got second camera instance: " + d.config.toSource);
     }
     function onFailure(error) {
       ok(true, "Correctly failed to get camera again");
       SimpleTest.finish();
     }
-    navigator.mozCameras.getCamera(whichCamera, options, onSuccess, onFailure);
+    navigator.mozCameras.getCamera(whichCamera, options).then(onSuccess, onFailure);
+  },
+  onPreviewStateChange: function onPreviewStateChange(e) {
+    if (e.newState === 'started') {
+      ok(true, "viewfinder is ready and playing");
+      Camera.cameraObj.removeEventListener('previewstatechange', Camera.onPreviewStateChange);
+      Camera.onReady();
+    }
   },
   release: function release() {
     cameraObj = null;
   },
   start: function run_test() {
-    function onSuccess(camera, config) {
-      Camera.cameraObj = camera;
-      Camera.viewfinder.mozSrcObject = camera;
+    function onSuccess(d) {
+      Camera.cameraObj = d.camera;
+      Camera.cameraObj.addEventListener('previewstatechange', Camera.onPreviewStateChange);
+      Camera.viewfinder.mozSrcObject = d.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);
+    navigator.mozCameras.getCamera(whichCamera, options).then(onSuccess, onError);
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 
 window.addEventListener('beforeunload', function() {
   Camera.viewfinder.mozSrcObject = null;
   Camera.cameraObj.release();
--- a/dom/camera/test/test_camera_fake_parameters.html
+++ b/dom/camera/test/test_camera_fake_parameters.html
@@ -23,17 +23,17 @@ var initialConfig = {
     height: 288
   }
 };
 
 var cameraObj = null;
 
 // Shorthand functions
 function onError(e) {
-  ok(false, "Error" + JSON.stringify(e));
+  ok(false, "Error " + e);
 }
 
 function end() {
   CameraTest.end();
 }
 function next() {
   if (cameraObj) {
     cameraObj.release(
@@ -422,30 +422,29 @@ CameraTest.begin("hardware", function(te
     ok(false, "getCamera() failed with: " + error);
     end();
   }
 
   CameraTest.next = function() {
     try {
       var t = testGenerator.next();
       info("test: " + t.key);
-      function onSuccess(camera, config) {
-        cameraObj = camera;
-        document.getElementById('viewfinder').mozSrcObject = camera;
-        camera.onPreviewStateChange = function (state) {
-          if (state === "started") {
-            t.test(camera, camera.capabilities);
-          } else {
-            ok(false, "preview started (state = '" + state + "')");
+      function onSuccess(d) {
+        cameraObj = d.camera;
+        document.getElementById('viewfinder').mozSrcObject = d.camera;
+        var onPreviewStateChange = function (evt) {
+          if (evt.newState === "started") {
+            cameraObj.removeEventListener('previewstatechange', onPreviewStateChange);
+            t.test(cameraObj, cameraObj.capabilities);
           }
-          camera.onPreviewStateChange = null;
         };
+        cameraObj.addEventListener('previewstatechange', onPreviewStateChange);
       }
       CameraTest.run = function() {
-        navigator.mozCameras.getCamera(whichCamera, initialConfig, onSuccess, onError);
+        navigator.mozCameras.getCamera(whichCamera, initialConfig).then(onSuccess, onError);
       };
       t.prep(test);
     } catch(e) {
       if (e instanceof StopIteration) {
         end();
       } else {
         throw e;
       }
--- a/dom/camera/test/test_camera_hardware_auto_focus_moving_cb.html
+++ b/dom/camera/test/test_camera_hardware_auto_focus_moving_cb.html
@@ -48,35 +48,26 @@ function end() {
 function next() {
   CameraTest.next();
 }
 
 var tests = [
   {
     key: "autofocus-moving-true",
     func: function testAutoFocusMovingIsTrue(camera) {
-      camera.onAutoFocusMoving = function(aIsMoving) {
-        ok(aIsMoving == true,"onAutoFocusMoving callback received true correctly");
+      var handler = function(evt) {
+        camera.removeEventListener("focus", handler);
+        ok(evt.newState == "focusing", "autofocus event state focusing == " + evt.newState);
         camera.focusMode = 'auto';
         next();
       }
+      camera.addEventListener("focus", handler);
       camera.focusMode = 'continuous-picture';
     }
   },
-  {
-    key: "autofocus-moving-false",
-    func: function testAutoFocusMovingIsFalse(camera) {
-      camera.onAutoFocusMoving = function(aIsMoving) {
-        ok(aIsMoving == false, "onAutoFocusMoving callback received false correctly");
-        camera.focusMode = 'auto';
-        end();
-      }
-      camera.focusMode = 'continuous-video';
-    }
-  },
 ];
 
 var testGenerator = function() {
   for (var i = 0; i < tests.length; ++i ) {
     yield tests[i];
   }
 }();
 
@@ -95,37 +86,37 @@ CameraTest.begin("hardware", function(te
 
   SpecialPowers.pushPrefEnv({'set': [[PREF_AUTOFOCUSCALLBACK_ENABLED, true]]}, function() {
     var enabled;
     try {
       enabled = SpecialPowers.getBoolPref(PREF_AUTOFOCUSCALLBACK_ENABLED);
     } catch(e) { }
     ok(enabled, PREF_AUTOFOCUSCALLBACK_ENABLED + " is " + enabled);
 
-    function onSuccess(camera, config) {
-      document.getElementById('viewfinder').mozSrcObject = camera;
-      cameraObj = camera;
+    function onSuccess(d) {
+      document.getElementById('viewfinder').mozSrcObject = d.camera;
+      cameraObj = d.camera;
       CameraTest.next = function() {
         try {
           var t = testGenerator.next();
-          test.set(t.key, t.func.bind(undefined, camera));
+          test.set(t.key, t.func.bind(undefined, d.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);
+    navigator.mozCameras.getCamera(whichCamera, initialConfig).then(onSuccess, onError);
   })
 });
 
 </script>
 </body>
 
 </html>
--- a/dom/camera/test/test_camera_hardware_face_detection.html
+++ b/dom/camera/test/test_camera_hardware_face_detection.html
@@ -145,22 +145,26 @@ var tests = [
             y:      10
           },
           mouth: {
             x:      11,
             y:      12
           }
         } ]
       };
-      camera.onFacesDetected = function(aFaces) {
-        ok(compareFaces(aFaces, expected),
-          "onFaceDetected received the detected faces correctly");
+
+      var handler = function(evt) {
+        ok(compareFaces(evt.faces, expected),
+          "facedetected event received the detected faces correctly");
         camera.stopFaceDetection();
+        camera.removeEventListener('facesdetected', handler);
         next();
-      }
+      };
+
+      camera.addEventListener('facesdetected', handler);
       camera.startFaceDetection();
     }
   },
   {
     key: "face-detection-detected-two-faces",
     func: function testFaceDetectionFoundTwoFace(camera) {
       var expected = {
         faces: [ {
@@ -203,22 +207,26 @@ var tests = [
             y:      22
           },
           mouth: {
             x:      23,
             y:      24
           }
         } ]
       };
-      camera.onFacesDetected = function(aFaces) {
-        ok(compareFaces(aFaces, expected),
-          "onFaceDetected received the detected faces correctly");
+
+      var handler = function(evt) {
+        ok(compareFaces(evt.faces, expected),
+          "facedetected event received the detected faces correctly");
         camera.stopFaceDetection();
+        camera.removeEventListener('facesdetected', handler);
         next();
-      }
+      };
+
+      camera.addEventListener('facesdetected', handler);
       camera.startFaceDetection();
     }
   },
   {
     key: "face-detection-detected-one-face-no-features",
     func: function (camera) {
       var expected = {
         faces: [ {
@@ -230,37 +238,45 @@ var tests = [
             right:  5,
             bottom: 6
           },
           leftEye:  null,
           rightEye: null,
           mouth:    null
         } ]
       };
-      camera.onFacesDetected = function(aFaces) {
-        ok(compareFaces(aFaces, expected),
-          "onFaceDetected received the detected faces correctly");
+
+      var handler = function(evt) {
+        ok(compareFaces(evt.faces, expected),
+          "facedetected event received the detected faces correctly");
         camera.stopFaceDetection();
+        camera.removeEventListener('facesdetected', handler);
         next();
-      }
+      };
+
+      camera.addEventListener('facesdetected', handler);
       camera.startFaceDetection();
     }
   },
   {
     key: "face-detection-no-faces-detected",
     func: function (camera) {
       var expected = {
         faces: []
       };
-      camera.onFacesDetected = function(aFaces) {
-        ok(compareFaces(aFaces, expected),
-          "onFaceDetected received the detected faces correctly");
+
+      var handler = function(evt) {
+        ok(compareFaces(evt.faces, expected),
+          "facedetected event received the detected faces correctly");
         camera.stopFaceDetection();
+        camera.removeEventListener('facesdetected', handler);
         next();
-      }
+      };
+
+      camera.addEventListener('facesdetected', handler);
       camera.startFaceDetection();
     }
   },
 ];
 
 var testGenerator = function() {
   for (var i = 0; i < tests.length; ++i ) {
     yield tests[i];
@@ -284,37 +300,37 @@ CameraTest.begin("hardware", function(te
 
   SpecialPowers.pushPrefEnv({'set': [[PREF_FACEDETECTION_ENABLED, true]]}, function() {
     var enabled;
     try {
       enabled = SpecialPowers.getBoolPref(PREF_FACEDETECTION_ENABLED);
     } catch(e) { }
     ok(enabled, PREF_FACEDETECTION_ENABLED + " is " + enabled);
 
-    function onSuccess(camera, config) {
-      document.getElementById('viewfinder').mozSrcObject = camera;
-      cameraObj = camera;
+    function onSuccess(d) {
+      cameraObj = d.camera;
+      document.getElementById('viewfinder').mozSrcObject = cameraObj;
       CameraTest.next = function() {
         try {
           var t = testGenerator.next();
-          test.set(t.key, t.func.bind(undefined, camera));
+          test.set(t.key, t.func.bind(undefined, cameraObj));
         } 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);
+    navigator.mozCameras.getCamera(whichCamera, initialConfig).then(onSuccess, onError);
   })
 });
 
 </script>
 </body>
 
 </html>
--- a/dom/camera/test/test_camera_hardware_failures.html
+++ b/dom/camera/test/test_camera_hardware_failures.html
@@ -45,17 +45,17 @@ var tests = [
       function onSuccess(success) {
         ok(false, "autoFocus() succeeded incorrectly");
         end();
       }
       function onError(error) {
         ok(true, "autoFocus() failed correctly with: " + error);
         next();
       }
-      camera.autoFocus(onSuccess, onError);
+      camera.autoFocus().then(onSuccess, onError);
     }
   },
   {
     key: "auto-focus-process-failure",
     func: function testAutoFocusProcessFailure(camera) {
       function onSuccess(success) {
         if (success) {
           ok(false, "autoFocus() process succeeded incorrectly");
@@ -64,45 +64,45 @@ var tests = [
           ok(true, "autoFocus() process failed correctly");
           next();
         }
       }
       function onError(error) {
         ok(false, "autoFocus() process failed incorrectly with: " + error);
         end();
       }
-      camera.autoFocus(onSuccess, onError);
+      camera.autoFocus().then(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);
+      camera.takePicture(null).then(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);
+      camera.takePicture(null).then(onSuccess, onError);
     }
   },
 ];
 
 var testGenerator = function() {
   for (var i = 0; i < tests.length; ++i ) {
     yield tests[i];
   }
@@ -110,36 +110,36 @@ var testGenerator = function() {
 
 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;
+  function onSuccess(d) {
+    cameraObj = d.camera;
+    document.getElementById('viewfinder').mozSrcObject = cameraObj;
     CameraTest.next = function() {
       try {
         var t = testGenerator.next();
-        test.set(t.key, t.func.bind(undefined, camera));
+        test.set(t.key, t.func.bind(undefined, cameraObj));
       } 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);
+  navigator.mozCameras.getCamera(whichCamera, initialConfig).then(onSuccess, onError);
 });
 
 </script>
 </body>
 
 </html>
--- a/dom/camera/test/test_camera_hardware_init_failure.html
+++ b/dom/camera/test/test_camera_hardware_init_failure.html
@@ -29,46 +29,46 @@ var initialConfig = {
   }
 };
 
 var tests = [
   {
     name: "init-failure",
     key: "init-failure",
     func: function testInitFailure(test) {
-      function onSuccess(camera, config) {
+      function onSuccess(d) {
         ok(false, "onSuccess called incorrectly");
-        camera.release();
+        d.camera.release();
         test.next();
       }
       function onError(error) {
         ok(true, "onError called correctly on init failure");
         test.next();
       }
       info("Running test: init-failure");
-      navigator.mozCameras.getCamera(whichCamera, initialConfig, onSuccess, onError);
+      navigator.mozCameras.getCamera(whichCamera, initialConfig).then(onSuccess, onError);
     }
   },
   /* This test case (init-success) *must* follow the preceeding test case
      (init-failure) in order for the desired condition to be verified */
   {
     name: "init-success",
     key: "",
     func: function(test) {
-      function onSuccess(camera, config) {
+      function onSuccess(d) {
         ok(true, "onSuccess called correctly");
-        camera.release();
+        d.camera.release();
         test.next();
       }
       function onError(error) {
         ok(false, "onError called incorrectly: " + error);
         test.next();
       }
       info("Running test: init-success");
-      navigator.mozCameras.getCamera(whichCamera, initialConfig, onSuccess, onError)
+      navigator.mozCameras.getCamera(whichCamera, initialConfig).then(onSuccess, onError)
     }
   }
 ];
 
 var testGenerator = function() {
   for (var i = 0; i < tests.length; ++i ) {
     yield tests[i];
   }
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -71,16 +71,28 @@ const kEventConstructors = {
   CallEvent:                                 { create: function (aName, aProps) {
                                                           return new CallEvent(aName, aProps);
                                                        },
                                              },
   CallGroupErrorEvent:                       { create: function (aName, aProps) {
                                                           return new CallGroupErrorEvent(aName, aProps);
                                                        },
                                              },
+  CameraConfigurationEvent:                  { create: function (aName, aProps) {
+                                                          return new CameraConfigurationEvent(aName, aProps);
+                                                       },
+                                             },
+  CameraFacesDetectedEvent:                  { create: function (aName, aProps) {
+                                                          return new CameraFacesDetectedEvent(aName, aProps);
+                                                       },
+                                             },
+  CameraStateChangeEvent:                    { create: function (aName, aProps) {
+                                                          return new CameraStateChangeEvent(aName, aProps);
+                                                       },
+                                             },
   CFStateChangeEvent:                        { create: function (aName, aProps) {
                                                           return new CFStateChangeEvent(aName, aProps);
                                                        },
                                              },
   CloseEvent:                                { create: function (aName, aProps) {
                                                          return new CloseEvent(aName, aProps);
                                                        },
                                              },
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -196,22 +196,28 @@ var interfaceNamesInGlobalScope =
     {name: "BoxObject", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CallEvent", b2g: true, pref: "dom.telephony.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CallGroupErrorEvent", b2g: true, pref: "dom.telephony.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CameraCapabilities", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "CameraConfigurationEvent", b2g: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CameraControl", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CameraDetectedFace", b2g: true, pref: "camera.control.face_detection.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "CameraFacesDetectedEvent", b2g: true, pref: "camera.control.face_detection.enabled"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CameraManager", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "CameraStateChangeEvent", b2g: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "CanvasGradient",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "CanvasPattern",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "CanvasRenderingContext2D",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "CaretPosition",
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/webidl/CameraConfigurationEvent.webidl
@@ -0,0 +1,20 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+[Func="Navigator::HasCameraSupport",
+ Constructor(DOMString type, optional CameraConfigurationEventInit eventInitDict)]
+interface CameraConfigurationEvent : Event
+{
+  readonly attribute CameraMode mode;
+  readonly attribute DOMString recorderProfile;
+  readonly attribute DOMRectReadOnly? previewSize;
+};
+
+dictionary CameraConfigurationEventInit : EventInit
+{
+  CameraMode mode = "picture";
+  DOMString recorderProfile = "cif";
+  DOMRectReadOnly? previewSize = null;
+};
--- a/dom/webidl/CameraControl.webidl
+++ b/dom/webidl/CameraControl.webidl
@@ -230,30 +230,58 @@ interface CameraControl : MediaStream
      value is "auto" if supported. */
   [Throws]
   attribute DOMString       isoMode;
 
   /* the function to call on the camera's shutter event, to trigger
      a shutter sound and/or a visual shutter indicator. */
   attribute CameraShutterCallback? onShutter;
 
+  /* the event dispatched on the camera's shutter event, to trigger
+     a shutter sound and/or a visual shutter indicator.
+
+     contains no event-specific data. */
+  attribute EventHandler onshutter;
+
   /* the function to call when the camera hardware is closed
      by the underlying framework, e.g. when another app makes a more
      recent call to get the camera. */
   attribute CameraClosedCallback? onClosed;
 
+  /* the event dispatched when the camera hardware is closed
+     by the underlying framework, e.g. when another app makes a more
+     recent call to get the camera.
+
+     contains no event-specific data. */
+  attribute EventHandler onclose;
+
   /* the function to call when the recorder changes state, either because
      the recording process encountered an error, or because one of the
      recording limits (see CameraStartRecordingOptions) was reached. */
   attribute CameraRecorderStateChange? onRecorderStateChange;
 
+  /* the event dispatched when the recorder changes state, either because
+     the recording process encountered an error, or because one of the
+     recording limits (see CameraStartRecordingOptions) was reached.
+
+     event type is CameraStateChangeEvent where:
+         'newState' is the new recorder state */
+  attribute EventHandler onrecorderstatechange;
+
   /* the function to call when the viewfinder stops or starts,
      useful for synchronizing other UI elements. */
   attribute CameraPreviewStateChange? onPreviewStateChange;
 
+  /* the event dispatched when the viewfinder stops or starts,
+     useful for synchronizing other UI elements.
+
+     event type is CameraStateChangeEvent where:
+         'newState' is the new preview state */
+  attribute EventHandler onpreviewstatechange;
+
   /* the size of the picture to be returned by a call to takePicture();
      an object with 'height' and 'width' properties that corresponds to
      one of the options returned by capabilities.pictureSizes. */
   [Throws]
   CameraSize getPictureSize();
   [Throws]
   void setPictureSize(optional CameraSize size);
 
@@ -282,17 +310,35 @@ interface CameraControl : MediaStream
   /* the angle, in degrees, that the image sensor is mounted relative
      to the display; e.g. if 'sensorAngle' is 270 degrees (or -90 degrees),
      then the preview stream needs to be rotated +90 degrees to have the
      same orientation as the real world. */
   readonly attribute long   sensorAngle;
 
   /* tell the camera to attempt to focus the image */
   [Throws]
-  void autoFocus(CameraAutoFocusCallback onSuccess, optional CameraErrorCallback onError);
+  Promise<boolean> autoFocus(optional CameraAutoFocusCallback onSuccess,
+                             optional CameraErrorCallback onError);
+
+  /* the event dispatched whenever the focus state changes due to calling
+     autoFocus or due to continuous autofocus.
+
+     if continuous autofocus is supported and focusMode is set to enable it,
+     then this event is dispatched whenever the camera decides to start and
+     stop moving the focus position; it can be used to update a UI element to
+     indicate that the camera is still trying to focus, or has finished. Some
+     platforms do not support this event, in which case the callback is never
+     invoked.
+
+     event type is CameraStateChangeEvent where:
+         'newState' is one of the following states:
+             'focused' if the focus is now set
+             'focusing' if the focus is moving
+             'unfocused' if last attempt to focus failed */
+  attribute EventHandler onfocus;
 
   /* if continuous autofocus is supported and focusMode is set to enable it,
      then this function is called whenever the camera decides to start and
      stop moving the focus position; it can be used to update a UI element to
      indicate that the camera is still trying to focus, or has finished. Some
      platforms do not support this event, in which case the callback is never
      invoked. */
   [Pref="camera.control.autofocus_moving_callback.enabled"]
@@ -300,28 +346,33 @@ interface CameraControl : MediaStream
 
   /* capture an image and return it as a blob to the 'onSuccess' callback;
      if the camera supports it, this may be invoked while the camera is
      already recording video.
 
      invoking this function will stop the preview stream, which must be
      manually restarted (e.g. by calling .play() on it). */
   [Throws]
-  void takePicture(CameraPictureOptions aOptions,
-                   CameraTakePictureCallback onSuccess,
-                   optional CameraErrorCallback onError);
+  Promise<Blob> takePicture(optional CameraPictureOptions aOptions,
+                            optional CameraTakePictureCallback onSuccess,
+                            optional CameraErrorCallback onError);
+
+  /* the event dispatched when a picture is successfully taken; it is of the
+     type BlobEvent, where the data attribute contains the picture. */
+  attribute EventHandler onpicture;
 
-  /* start recording video; 'aOptions' is a
-     CameraStartRecordingOptions object. */
+  /* start recording video; 'aOptions' is a CameraStartRecordingOptions object.
+     If the success/error callbacks are not used, one may determine success by
+     waiting for the recorderstatechange event. */
   [Throws]
-  void startRecording(CameraStartRecordingOptions aOptions,
-                      DeviceStorage storageArea,
-                      DOMString filename,
-                      CameraStartRecordingCallback onSuccess,
-                      optional CameraErrorCallback onError);
+  Promise<void> startRecording(CameraStartRecordingOptions aOptions,
+                               DeviceStorage storageArea,
+                               DOMString filename,
+                               optional CameraStartRecordingCallback onSuccess,
+                               optional CameraErrorCallback onError);
 
   /* stop precording video. */
   [Throws]
   void stopRecording();
 
   /* call in or after the takePicture() onSuccess callback to
      resume the camera preview stream. */
   [Throws]
@@ -332,31 +383,40 @@ interface CameraControl : MediaStream
      (depending on your usage model).
 
      the callbacks are optional, unless you really need to know when
      the hardware is ultimately released.
 
      once this is called, the camera control object is to be considered
      defunct; a new instance will need to be created to access the camera. */
   [Throws]
-  void release(optional CameraReleaseCallback onSuccess,
-               optional CameraErrorCallback onError);
+  Promise<void> release(optional CameraReleaseCallback onSuccess,
+                        optional CameraErrorCallback onError);
 
   /* changes the camera configuration on the fly;
      'configuration' is of type CameraConfiguration.
 
      XXXmikeh the 'configuration' argument needs to be optional, else
      the WebIDL compiler throws: "WebIDL.WebIDLError: error: Dictionary
      argument or union argument containing a dictionary not followed by
      a required argument must be optional"
   */
   [Throws]
-  void setConfiguration(optional CameraConfiguration configuration,
-                        optional CameraSetConfigurationCallback onSuccess,
-                        optional CameraErrorCallback onError);
+  Promise<CameraConfiguration> setConfiguration(optional CameraConfiguration configuration,
+                                                optional CameraSetConfigurationCallback onSuccess,
+                                                optional CameraErrorCallback onError);
+
+  /* the event dispatched when the camera is successfully configured.
+
+     event type is CameraConfigurationEvent where:
+         'mode' is the selected camera mode
+         'recorderProfile' is the selected profile
+         'width' contains the preview width
+         'height' contains the preview height */
+  attribute EventHandler onconfigurationchange;
 
   /* if focusMode is set to either 'continuous-picture' or 'continuous-video',
      then calling autoFocus() will trigger its onSuccess callback immediately
      if the camera was either successfully focused, or if no focus could be
      acquired; if the focus acquisition is still in progress, the onSuccess
      callback will be invoked later, its argument indicating success or
      failure.
 
@@ -448,9 +508,13 @@ partial interface CameraControl
   */
   [Throws, Pref="camera.control.face_detection.enabled"]
   void stopFaceDetection();
 
   /* Callback for faces detected in the preview frame. If no faces are
      detected, the callback is invoked with an empty sequence. */
   [Pref="camera.control.face_detection.enabled"]
   attribute CameraFaceDetectionCallback? onFacesDetected;
+
+  /* CameraFacesDetectedEvent */
+  [Pref="camera.control.face_detection.enabled"]
+  attribute EventHandler onfacesdetected;
 };
new file mode 100644
--- /dev/null
+++ b/dom/webidl/CameraFacesDetectedEvent.webidl
@@ -0,0 +1,18 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+[Pref="camera.control.face_detection.enabled",
+ Func="Navigator::HasCameraSupport",
+ Constructor(DOMString type, optional CameraFacesDetectedEventInit eventInitDict)]
+interface CameraFacesDetectedEvent : Event
+{
+  [Pure, Cached]
+  readonly attribute sequence<CameraDetectedFace>? faces;
+};
+
+dictionary CameraFacesDetectedEventInit : EventInit
+{
+  sequence<CameraDetectedFace>? faces = null;
+};
--- a/dom/webidl/CameraManager.webidl
+++ b/dom/webidl/CameraManager.webidl
@@ -31,19 +31,19 @@ callback GetCameraCallback = void (Camer
 
 [Func="nsDOMCameraManager::HasSupport"]
 interface CameraManager
 {
   /* get a camera instance; 'camera' is one of the camera
      identifiers returned by getListOfCameras() below.
   */
   [Throws]
-  void getCamera(DOMString camera,
-                 CameraConfiguration initialConfiguration,
-                 GetCameraCallback callback,
-                 optional CameraErrorCallback errorCallback);
+  Promise<CameraGetPromiseData> getCamera(DOMString camera,
+                                          optional CameraConfiguration initialConfiguration,
+                                          optional GetCameraCallback callback,
+                                          optional CameraErrorCallback errorCallback);
 
   /* return an array of camera identifiers, e.g.
        [ "front", "back" ]
    */
   [Throws]
   sequence<DOMString> getListOfCameras();
 };
new file mode 100644
--- /dev/null
+++ b/dom/webidl/CameraStateChangeEvent.webidl
@@ -0,0 +1,16 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+[Func="Navigator::HasCameraSupport",
+ Constructor(DOMString type, optional CameraStateChangeEventInit eventInitDict)]
+interface CameraStateChangeEvent : Event
+{
+  readonly attribute DOMString newState;
+};
+
+dictionary CameraStateChangeEventInit : EventInit
+{
+  DOMString newState = "";
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/CameraUtil.webidl
@@ -0,0 +1,12 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+dictionary CameraGetPromiseData
+{
+  CameraControl? camera = null;
+  CameraConfiguration configuration;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -52,16 +52,17 @@ WEBIDL_FILES = [
     'BeforeUnloadEvent.webidl',
     'BiquadFilterNode.webidl',
     'Blob.webidl',
     'BrowserElementDictionaries.webidl',
     'CallsList.webidl',
     'CameraCapabilities.webidl',
     'CameraControl.webidl',
     'CameraManager.webidl',
+    'CameraUtil.webidl',
     'CanvasRenderingContext2D.webidl',
     'CaretPosition.webidl',
     'CDATASection.webidl',
     'ChannelMergerNode.webidl',
     'ChannelSplitterNode.webidl',
     'CharacterData.webidl',
     'ChildNode.webidl',
     'ChromeNotifications.webidl',
@@ -638,16 +639,19 @@ if CONFIG['MOZ_B2G_FM']:
         'FMRadio.webidl',
     ]
 
 GENERATED_EVENTS_WEBIDL_FILES = [
     'AutocompleteErrorEvent.webidl',
     'BlobEvent.webidl',
     'CallEvent.webidl',
     'CallGroupErrorEvent.webidl',
+    'CameraConfigurationEvent.webidl',
+    'CameraFacesDetectedEvent.webidl',
+    'CameraStateChangeEvent.webidl',
     'CFStateChangeEvent.webidl',
     'CloseEvent.webidl',
     'DataErrorEvent.webidl',
     'DataStoreChangeEvent.webidl',
     'DeviceLightEvent.webidl',
     'DeviceOrientationEvent.webidl',
     'DeviceProximityEvent.webidl',
     'DeviceStorageChangeEvent.webidl',