Bug 740997 - ICS camera support, r=jst,gal,roc
authorMike Habicher <mhabicher@mozilla.com>
Mon, 30 Jul 2012 17:59:05 -0400
changeset 100950 d59f932deea97bb8b99a41bef1f06e3020f7cf4b
parent 100949 06c2dd7e702ded27cc2ab25eacb5d5042b877dbc
child 100951 8051eeb12d0c2050ceab0ee24d804e5990e49b98
push id12759
push usermwu@mozilla.com
push dateTue, 31 Jul 2012 04:25:56 +0000
treeherdermozilla-inbound@d59f932deea9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst, gal, roc
bugs740997
milestone17.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 740997 - ICS camera support, r=jst,gal,roc
b2g/installer/package-manifest.in
browser/installer/package-manifest.in
config/autoconf.mk.in
configure.in
dom/Makefile.in
dom/base/Navigator.cpp
dom/base/Navigator.h
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/camera/CameraCapabilities.h
dom/camera/CameraCommon.h
dom/camera/CameraControl.cpp
dom/camera/CameraControl.h
dom/camera/CameraPreview.cpp
dom/camera/CameraPreview.h
dom/camera/DOMCameraManager.cpp
dom/camera/DOMCameraManager.h
dom/camera/FallbackCameraCapabilities.cpp
dom/camera/FallbackCameraControl.cpp
dom/camera/FallbackCameraManager.cpp
dom/camera/GonkCameraCapabilities.cpp
dom/camera/GonkCameraControl.cpp
dom/camera/GonkCameraControl.h
dom/camera/GonkCameraHwMgr.cpp
dom/camera/GonkCameraHwMgr.h
dom/camera/GonkCameraManager.cpp
dom/camera/GonkCameraPreview.cpp
dom/camera/GonkCameraPreview.h
dom/camera/GonkNativeWindow.cpp
dom/camera/GonkNativeWindow.h
dom/camera/Makefile.in
dom/camera/nsIDOMCameraManager.idl
dom/camera/nsIDOMNavigatorCamera.idl
dom/dom-config.mk
dom/tests/mochitest/general/test_interfaces.html
js/xpconnect/src/dictionary_helper_gen.conf
layout/build/Makefile.in
mobile/xul/installer/package-manifest.in
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -160,16 +160,17 @@
 @BINPATH@/components/dom_telephony.xpt
 @BINPATH@/components/dom_wifi.xpt
 @BINPATH@/components/dom_system_gonk.xpt
 #endif
 @BINPATH@/components/dom_battery.xpt
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #endif
+@BINPATH@/components/dom_camera.xpt
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_contacts.xpt
 @BINPATH@/components/dom_alarm.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_devicestorage.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_file.xpt
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -169,16 +169,17 @@
 @BINPATH@/components/dom_telephony.xpt
 @BINPATH@/components/dom_wifi.xpt
 @BINPATH@/components/dom_system_gonk.xpt
 #endif
 @BINPATH@/components/dom_battery.xpt
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #endif
+@BINPATH@/components/dom_camera.xpt
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_contacts.xpt
 @BINPATH@/components/dom_alarm.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_devicestorage.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_file.xpt
--- a/config/autoconf.mk.in
+++ b/config/autoconf.mk.in
@@ -268,16 +268,17 @@ MOZ_ENABLE_GIO = @MOZ_ENABLE_GIO@
 MOZ_GIO_CFLAGS = @MOZ_GIO_CFLAGS@
 MOZ_GIO_LIBS = @MOZ_GIO_LIBS@
 
 MOZ_NATIVE_NSPR = @MOZ_NATIVE_NSPR@
 MOZ_NATIVE_NSS = @MOZ_NATIVE_NSS@
 
 MOZ_B2G_RIL = @MOZ_B2G_RIL@
 MOZ_B2G_BT = @MOZ_B2G_BT@
+MOZ_B2G_CAMERA = @MOZ_B2G_CAMERA@
 
 MOZ_SYS_MSG = @MOZ_SYS_MSG@
 
 MOZ_ASAN = @MOZ_ASAN@
 MOZ_CFLAGS_NSS = @MOZ_CFLAGS_NSS@
 MOZ_NO_WLZDEFS = @MOZ_NO_WLZDEFS@
 
 BUILD_CTYPES = @BUILD_CTYPES@
--- a/configure.in
+++ b/configure.in
@@ -187,17 +187,17 @@ if test -n "$gonkdir" ; then
     arm)
         ARCH_DIR=arch-arm
         ;;
     i?86)
         ARCH_DIR=arch-x86
         ;;
     esac
 
-    CPPFLAGS="-DANDROID -isystem $gonkdir/bionic/libc/$ARCH_DIR/include -isystem $gonkdir/bionic/libc/include/ -isystem $gonkdir/bionic/libc/kernel/common -isystem $gonkdir/bionic/libc/kernel/$ARCH_DIR -isystem $gonkdir/bionic/libm/include -I$gonkdir/frameworks/base/opengl/include -I$gonkdir/frameworks/base/native/include -I$gonkdir/hardware/libhardware/include -I$gonkdir/hardware/libhardware_legacy/include -I$gonkdir/system -I$gonkdir/system/core/include -isystem $gonkdir/bionic -I$gonkdir/frameworks/base/include -I$gonkdir/external/dbus $CPPFLAGS -I$gonkdir/frameworks/base/services/sensorservice"
+    CPPFLAGS="-DANDROID -isystem $gonkdir/bionic/libc/$ARCH_DIR/include -isystem $gonkdir/bionic/libc/include/ -isystem $gonkdir/bionic/libc/kernel/common -isystem $gonkdir/bionic/libc/kernel/$ARCH_DIR -isystem $gonkdir/bionic/libm/include -I$gonkdir/frameworks/base/opengl/include -I$gonkdir/frameworks/base/native/include -I$gonkdir/hardware/libhardware/include -I$gonkdir/hardware/libhardware_legacy/include -I$gonkdir/system -I$gonkdir/system/core/include -isystem $gonkdir/bionic -I$gonkdir/frameworks/base/include -I$gonkdir/external/dbus $CPPFLAGS -I$gonkdir/frameworks/base/services/sensorservice -I$gonkdir/frameworks/base/services/camera"
     CFLAGS="-mandroid -fno-short-enums -fno-exceptions $CFLAGS"
     CXXFLAGS="-mandroid -fno-short-enums -fno-exceptions $CXXFLAGS $STLPORT_CPPFLAGS"
     LIBS="$LIBS $STLPORT_LIBS"
 
     dnl Add -llog by default, since we use it all over the place.
     LDFLAGS="-mandroid -L$gonkdir/out/target/product/$GONK_PRODUCT/obj/lib -Wl,-rpath-link=$gonkdir/out/target/product/$GONK_PRODUCT/obj/lib --sysroot=$gonkdir/out/target/product/$GONK_PRODUCT/obj/ -llog $LDFLAGS"
 
     dnl prevent cross compile section from using these flags as host flags
@@ -7347,16 +7347,28 @@ dnl ====================================
 dnl = Enable Support for System Messages API
 dnl ========================================================
 if test -n "$MOZ_SYS_MSG"; then
     AC_DEFINE(MOZ_SYS_MSG)
 fi
 AC_SUBST(MOZ_SYS_MSG)
 
 dnl ========================================================
+dnl = Enable Camera Interface for B2G (Gonk usually)
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(b2g-camera,
+[  --enable-b2g-camera      Set compile flags necessary for compiling camera API for B2G ],
+    MOZ_B2G_CAMERA=1,
+    MOZ_B2G_CAMERA= )
+if test -n "$MOZ_B2G_CAMERA"; then
+   AC_DEFINE(MOZ_B2G_CAMERA)
+fi
+AC_SUBST(MOZ_B2G_CAMERA)
+
+dnl ========================================================
 dnl = Support for demangling undefined symbols
 dnl ========================================================
 if test -z "$SKIP_LIBRARY_CHECKS"; then
     AC_LANG_SAVE
     AC_LANG_CPLUSPLUS
     AC_CHECK_FUNCS(__cxa_demangle, HAVE_DEMANGLE=1, HAVE_DEMANGLE=)
     AC_LANG_RESTORE
 fi
--- a/dom/Makefile.in
+++ b/dom/Makefile.in
@@ -65,16 +65,17 @@ DIRS += \
   network \
   plugins/base \
   plugins/ipc \
   indexedDB \
   system \
   ipc \
   identity \
   workers \
+  camera \
   $(NULL)
 
 ifdef MOZ_B2G_RIL
 DIRS += \
   telephony \
   wifi \
   $(NULL)
 endif
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -45,16 +45,18 @@
 #endif
 #ifdef MOZ_B2G_RIL
 #include "TelephonyFactory.h"
 #endif
 #ifdef MOZ_B2G_BT
 #include "nsIDOMBluetoothManager.h"
 #include "BluetoothManager.h"
 #endif
+#include "nsIDOMCameraManager.h"
+#include "DOMCameraManager.h"
 
 #include "nsIDOMGlobalPropertyInitializer.h"
 
 // This should not be in the namespace.
 DOMCI_DATA(Navigator, mozilla::dom::Navigator)
 
 namespace mozilla {
 namespace dom {
@@ -107,16 +109,17 @@ NS_INTERFACE_MAP_BEGIN(Navigator)
 #endif
 #ifdef MOZ_B2G_RIL
   NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorTelephony)
 #endif
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozNavigatorNetwork)
 #ifdef MOZ_B2G_BT
   NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorBluetooth)
 #endif
+  NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorCamera)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorSystemMessages)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Navigator)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(Navigator)
 NS_IMPL_RELEASE(Navigator)
 
 void
@@ -176,16 +179,18 @@ Navigator::Invalidate()
   }
 
 #ifdef MOZ_B2G_BT
   if (mBluetooth) {
     mBluetooth = nullptr;
   }
 #endif
 
+  mCameraManager = nullptr;
+
 #ifdef MOZ_SYS_MSG
   if (mMessagesManager) {
     mMessagesManager = nullptr;
   }
 #endif
 
 }
 
@@ -1315,16 +1320,40 @@ Navigator::MozSetMessageHandler(const ns
   NS_ENSURE_SUCCESS(rv, rv);
 
   return mMessagesManager->MozSetMessageHandler(aType, aCallback);
 #else
   return NS_ERROR_NOT_IMPLEMENTED;
 #endif
 }
 
+//*****************************************************************************
+//    nsNavigator::nsIDOMNavigatorCamera
+//*****************************************************************************
+
+NS_IMETHODIMP
+Navigator::GetMozCameras(nsIDOMCameraManager** aCameraManager)
+{
+  if (!mCameraManager) {
+    nsCOMPtr<nsPIDOMWindow> win = do_QueryReferent(mWindow);
+    NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
+
+    if (!win->GetOuterWindow() || win->GetOuterWindow()->GetCurrentInnerWindow() != win) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+
+    mCameraManager = nsDOMCameraManager::Create(win->WindowID());
+  }
+
+  nsRefPtr<nsDOMCameraManager> cameraManager = mCameraManager;
+  cameraManager.forget(aCameraManager);
+
+  return NS_OK;
+}
+
 size_t
 Navigator::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const
 {
   size_t n = aMallocSizeOf(this);
 
   // TODO: add SizeOfIncludingThis() to nsMimeTypeArray, bug 674113.
   // TODO: add SizeOfIncludingThis() to nsPluginArray, bug 674114.
   // TODO: add SizeOfIncludingThis() to nsGeolocation, bug 674115.
@@ -1339,22 +1368,29 @@ Navigator::SetWindow(nsPIDOMWindow *aInn
   NS_ASSERTION(aInnerWindow->IsInnerWindow(),
                "Navigator must get an inner window!");
   mWindow = do_GetWeakReference(aInnerWindow);
 }
 
 void
 Navigator::OnNavigation()
 {
+  nsCOMPtr<nsPIDOMWindow> win = do_QueryReferent(mWindow);
+  if (!win) {
+    return;
+  }
+
+#ifdef MOZ_MEDIA_NAVIGATOR
   // Inform MediaManager in case there are live streams or pending callbacks.
-#ifdef MOZ_MEDIA_NAVIGATOR
   MediaManager *manager = MediaManager::Get();
-  nsCOMPtr<nsPIDOMWindow> win = do_QueryReferent(mWindow);
-  return manager->OnNavigation(win->WindowID());
+  manager->OnNavigation(win->WindowID());
 #endif
+  if (mCameraManager) {
+    mCameraManager->OnNavigation(win->WindowID());
+  }
 }
 
 } // namespace dom
 } // namespace mozilla
 
 nsresult
 NS_GetNavigatorUserAgent(nsAString& aUserAgent)
 {
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -36,16 +36,19 @@ class nsIDOMMozVoicemail;
 #endif
 
 #ifdef MOZ_B2G_BT
 #include "nsIDOMNavigatorBluetooth.h"
 #endif
 
 #include "nsIDOMNavigatorSystemMessages.h"
 
+#include "nsIDOMNavigatorCamera.h"
+#include "DOMCameraManager.h"
+
 //*****************************************************************************
 // Navigator: Script "navigator" object
 //*****************************************************************************
 
 namespace mozilla {
 namespace dom {
 
 namespace battery {
@@ -77,16 +80,17 @@ class Navigator : public nsIDOMNavigator
 #endif
 #ifdef MOZ_B2G_RIL
                 , public nsIDOMNavigatorTelephony
 #endif
                 , public nsIDOMMozNavigatorNetwork
 #ifdef MOZ_B2G_BT
                 , public nsIDOMNavigatorBluetooth
 #endif
+                , public nsIDOMNavigatorCamera
                 , public nsIDOMNavigatorSystemMessages
 {
 public:
   Navigator(nsPIDOMWindow *aInnerWindow);
   virtual ~Navigator();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMNAVIGATOR
@@ -129,16 +133,17 @@ public:
    * Called when the inner window navigates to a new page.
    */
   void OnNavigation();
 
 #ifdef MOZ_SYS_MSG
   // Helper to initialize mMessagesManager.
   nsresult EnsureMessagesManager();
 #endif
+  NS_DECL_NSIDOMNAVIGATORCAMERA
 
 private:
   bool IsSmsAllowed() const;
   bool IsSmsSupported() const;
 
   nsRefPtr<nsMimeTypeArray> mMimeTypes;
   nsRefPtr<nsPluginArray> mPlugins;
   nsRefPtr<nsGeolocation> mGeolocation;
@@ -150,16 +155,17 @@ private:
   nsCOMPtr<nsIDOMTelephony> mTelephony;
   nsCOMPtr<nsIDOMMozVoicemail> mVoicemail;
 #endif
   nsRefPtr<network::Connection> mConnection;
   nsRefPtr<network::MobileConnection> mMobileConnection;
 #ifdef MOZ_B2G_BT
   nsCOMPtr<nsIDOMBluetoothManager> mBluetooth;
 #endif
+  nsRefPtr<nsDOMCameraManager> mCameraManager;
   nsCOMPtr<nsIDOMNavigatorSystemMessages> mMessagesManager;
   nsWeakPtr mWindow;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 nsresult NS_GetNavigatorUserAgent(nsAString& aUserAgent);
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -517,16 +517,20 @@ using mozilla::dom::indexedDB::IDBWrappe
 #include "BluetoothDevice.h"
 #include "BluetoothDeviceEvent.h"
 #endif
 
 #include "nsIDOMNavigatorSystemMessages.h"
 
 #include "mozilla/dom/Activity.h"
 
+#include "DOMCameraManager.h"
+#include "CameraControl.h"
+#include "CameraCapabilities.h"
+
 #include "DOMError.h"
 #include "DOMRequest.h"
 #include "nsIOpenWindowEventDetail.h"
 #include "nsIDOMGlobalObjectConstructor.h"
 
 #include "DOMFileHandle.h"
 #include "FileRequest.h"
 #include "LockedFile.h"
@@ -1663,16 +1667,23 @@ static nsDOMClassInfoData sClassInfoData
   NS_DEFINE_CLASSINFO_DATA(BluetoothAdapter, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)  
   NS_DEFINE_CLASSINFO_DATA(BluetoothDevice, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(BluetoothDeviceEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 #endif
 
+  NS_DEFINE_CLASSINFO_DATA(CameraManager, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+  NS_DEFINE_CLASSINFO_DATA(CameraControl, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+  NS_DEFINE_CLASSINFO_DATA(CameraCapabilities, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
   NS_DEFINE_CLASSINFO_DATA(DOMError, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(DOMRequest, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(OpenWindowEventDetail, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
@@ -2461,16 +2472,17 @@ nsDOMClassInfo::Init()
 #ifdef MOZ_B2G_RIL
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorTelephony)
 #endif
     DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(nsIDOMMozNavigatorNetwork,
                                         network::IsAPIEnabled())
 #ifdef MOZ_B2G_BT
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorBluetooth)
 #endif
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorCamera)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorSystemMessages)
 
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(Plugin, nsIDOMPlugin)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMPlugin)
   DOM_CLASSINFO_MAP_END
 
@@ -4452,16 +4464,28 @@ nsDOMClassInfo::Init()
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(BluetoothDeviceEvent, nsIDOMBluetoothDeviceEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothDeviceEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEvent)
   DOM_CLASSINFO_MAP_END
 #endif
 
+  DOM_CLASSINFO_MAP_BEGIN(CameraManager, nsIDOMCameraManager)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMCameraManager)
+  DOM_CLASSINFO_MAP_END
+
+  DOM_CLASSINFO_MAP_BEGIN(CameraControl, nsICameraControl)
+    DOM_CLASSINFO_MAP_ENTRY(nsICameraControl)
+  DOM_CLASSINFO_MAP_END
+
+  DOM_CLASSINFO_MAP_BEGIN(CameraCapabilities, nsICameraCapabilities)
+    DOM_CLASSINFO_MAP_ENTRY(nsICameraCapabilities)
+  DOM_CLASSINFO_MAP_END
+
   DOM_CLASSINFO_MAP_BEGIN(DOMError, nsIDOMDOMError)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMError)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(DOMRequest, nsIDOMDOMRequest)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMRequest)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
   DOM_CLASSINFO_MAP_END
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -521,16 +521,20 @@ DOMCI_CLASS(MozVoicemailEvent)
 
 #ifdef MOZ_B2G_BT
 DOMCI_CLASS(BluetoothManager)
 DOMCI_CLASS(BluetoothAdapter)
 DOMCI_CLASS(BluetoothDevice)
 DOMCI_CLASS(BluetoothDeviceEvent)
 #endif
 
+DOMCI_CLASS(CameraManager)
+DOMCI_CLASS(CameraControl)
+DOMCI_CLASS(CameraCapabilities)
+
 DOMCI_CLASS(DOMError)
 DOMCI_CLASS(DOMRequest)
 DOMCI_CLASS(OpenWindowEventDetail)
 
 DOMCI_CLASS(DOMFileHandle)
 DOMCI_CLASS(FileRequest)
 DOMCI_CLASS(LockedFile)
 
new file mode 100644
--- /dev/null
+++ b/dom/camera/CameraCapabilities.h
@@ -0,0 +1,44 @@
+/* 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_NSCAMERACAPABILITIES_H
+#define DOM_CAMERA_NSCAMERACAPABILITIES_H
+
+#include "CameraControl.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+
+typedef nsresult (*ParseItemAndAddFunc)(JSContext* aCx, JSObject* aArray, PRUint32 aIndex, const char* aStart, char** aEnd);
+
+class nsCameraCapabilities : public nsICameraCapabilities
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICAMERACAPABILITIES
+
+  nsCameraCapabilities(nsCameraControl* aCamera);
+
+  nsresult ParameterListToNewArray(
+    JSContext* cx,
+    JSObject** aArray,
+    PRUint32 aKey,
+    ParseItemAndAddFunc aParseItemAndAdd
+  );
+  nsresult StringListToNewObject(JSContext* aCx, JS::Value* aArray, PRUint32 aKey);
+  nsresult DimensionListToNewObject(JSContext* aCx, JS::Value* aArray, PRUint32 aKey);
+
+private:
+  nsCameraCapabilities(const nsCameraCapabilities&) MOZ_DELETE;
+  nsCameraCapabilities& operator=(const nsCameraCapabilities&) MOZ_DELETE;
+
+protected:
+  /* additional members */
+  ~nsCameraCapabilities();
+  nsCOMPtr<nsCameraControl> mCamera;
+};
+
+} // namespace mozilla
+
+#endif // DOM_CAMERA_NSCAMERACAPABILITIES_H
new file mode 100644
--- /dev/null
+++ b/dom/camera/CameraCommon.h
@@ -0,0 +1,68 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* 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_CAMERACOMMON_H
+#define DOM_CAMERA_CAMERACOMMON_H
+
+#ifndef __func__
+#ifdef __FUNCTION__
+#define __func__ __FUNCTION__
+#else
+#define __func__ __FILE__
+#endif
+#endif
+
+#ifndef NAN
+#define NAN std::numeric_limits<double>::quiet_NaN()
+#endif
+
+#include "nsThreadUtils.h"
+#include "nsIDOMCameraManager.h"
+
+#define DOM_CAMERA_LOG( l, ... )          \
+  do {                                    \
+    if ( DOM_CAMERA_LOG_LEVEL >= (l) ) {  \
+      printf_stderr (__VA_ARGS__);        \
+    }                                     \
+  } while (0)
+
+#define DOM_CAMERA_LOGA( ... )        DOM_CAMERA_LOG( 0, __VA_ARGS__ )
+
+enum {
+  DOM_CAMERA_LOG_NOTHING,
+  DOM_CAMERA_LOG_ERROR,
+  DOM_CAMERA_LOG_WARNING,
+  DOM_CAMERA_LOG_INFO
+};
+
+#define DOM_CAMERA_LOGI( ... )        DOM_CAMERA_LOG( DOM_CAMERA_LOG_INFO,  __VA_ARGS__ )
+#define DOM_CAMERA_LOGW( ... )        DOM_CAMERA_LOG( DOM_CAMERA_LOG_WARNING, __VA_ARGS__ )
+#define DOM_CAMERA_LOGE( ... )        DOM_CAMERA_LOG( DOM_CAMERA_LOG_ERROR, __VA_ARGS__ )
+
+class CameraErrorResult : public nsRunnable
+{
+public:
+  CameraErrorResult(nsICameraErrorCallback* onError, const nsString& aErrorMsg)
+    : mOnErrorCb(onError)
+    , mErrorMsg(aErrorMsg)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (mOnErrorCb) {
+      mOnErrorCb->HandleEvent(mErrorMsg);
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
+  const nsString mErrorMsg;
+};
+
+#endif // DOM_CAMERA_CAMERACOMMON_H
new file mode 100644
--- /dev/null
+++ b/dom/camera/CameraControl.cpp
@@ -0,0 +1,486 @@
+/* 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 "nsCOMPtr.h"
+#include "nsDOMClassInfo.h"
+#include "jsapi.h"
+#include "nsThread.h"
+#include "DOMCameraManager.h"
+#include "CameraControl.h"
+#include "CameraCapabilities.h"
+#include "CameraControl.h"
+
+#define DOM_CAMERA_LOG_LEVEL  3
+#include "CameraCommon.h"
+
+using namespace mozilla;
+using namespace dom;
+
+DOMCI_DATA(CameraControl, nsICameraControl)
+
+NS_INTERFACE_MAP_BEGIN(nsCameraControl)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRY(nsICameraControl)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraControl)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsCameraControl)
+NS_IMPL_RELEASE(nsCameraControl)
+
+// Helpers for string properties.
+nsresult
+nsCameraControl::SetHelper(PRUint32 aKey, const nsAString& aValue)
+{
+  SetParameter(aKey, NS_ConvertUTF16toUTF8(aValue).get());
+  return NS_OK;
+}
+
+nsresult
+nsCameraControl::GetHelper(PRUint32 aKey, nsAString& aValue)
+{
+  const char* value = GetParameterConstChar(aKey);
+  if (!value) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aValue.AssignASCII(value);
+  return NS_OK;
+}
+
+// Helpers for doubles.
+nsresult
+nsCameraControl::SetHelper(PRUint32 aKey, double aValue)
+{
+  SetParameter(aKey, aValue);
+  return NS_OK;
+}
+
+nsresult
+nsCameraControl::GetHelper(PRUint32 aKey, double* aValue)
+{
+  MOZ_ASSERT(aValue);
+  *aValue = GetParameterDouble(aKey);
+  return NS_OK;
+}
+
+// Helper for weighted regions.
+nsresult
+nsCameraControl::SetHelper(JSContext* aCx, PRUint32 aKey, const JS::Value& aValue, PRUint32 aLimit)
+{
+  if (aLimit == 0) {
+    DOM_CAMERA_LOGI("%s:%d : aLimit = 0, nothing to do\n", __func__, __LINE__);
+    return NS_OK;
+  }
+
+  if (!aValue.isObject()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  uint32_t length = 0;
+
+  JSObject* regions = &aValue.toObject();
+  if (!JS_GetArrayLength(aCx, regions, &length)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  DOM_CAMERA_LOGI("%s:%d : got %d regions (limited to %d)\n", __func__, __LINE__, length, aLimit);
+  if (length > aLimit) {
+    length = aLimit;
+  }
+    
+  nsTArray<CameraRegion> regionArray;
+  regionArray.SetCapacity(length);
+
+  for (PRUint32 i = 0; i < length; ++i) {
+    JS::Value v;
+
+    if (!JS_GetElement(aCx, regions, i, &v)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    CameraRegion* r = regionArray.AppendElement();
+    /**
+     * These are the default values.  We can remove these when the xpidl
+     * dictionary parser gains the ability to grok default values.
+     */
+    r->top = -1000;
+    r->left = -1000;
+    r->bottom = 1000;
+    r->right = 1000;
+    r->weight = 1000;
+
+    nsresult rv = r->Init(aCx, &v);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    DOM_CAMERA_LOGI("region %d: top=%d, left=%d, bottom=%d, right=%d, weight=%d\n",
+      i,
+      r->top,
+      r->left,
+      r->bottom,
+      r->right,
+      r->weight
+    );
+  }
+  SetParameter(aKey, regionArray);
+  return NS_OK;
+}
+
+nsresult
+nsCameraControl::GetHelper(JSContext* aCx, PRUint32 aKey, JS::Value* aValue)
+{
+  nsTArray<CameraRegion> regionArray;
+
+  GetParameter(aKey, regionArray);
+
+  JSObject* array = JS_NewArrayObject(aCx, 0, nullptr);
+  if (!array) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  PRUint32 length = regionArray.Length();
+  DOM_CAMERA_LOGI("%s:%d : got %d regions\n", __func__, __LINE__, length);
+
+  for (PRUint32 i = 0; i < length; ++i) {
+    CameraRegion* r = &regionArray[i];
+    JS::Value v;
+
+    JSObject* o = JS_NewObject(aCx, nullptr, nullptr, nullptr);
+    if (!o) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    DOM_CAMERA_LOGI("top=%d\n", r->top);
+    v = INT_TO_JSVAL(r->top);
+    if (!JS_SetProperty(aCx, o, "top", &v)) {
+      return NS_ERROR_FAILURE;
+    }
+    DOM_CAMERA_LOGI("left=%d\n", r->left);
+    v = INT_TO_JSVAL(r->left);
+    if (!JS_SetProperty(aCx, o, "left", &v)) {
+      return NS_ERROR_FAILURE;
+    }
+    DOM_CAMERA_LOGI("bottom=%d\n", r->bottom);
+    v = INT_TO_JSVAL(r->bottom);
+    if (!JS_SetProperty(aCx, o, "bottom", &v)) {
+      return NS_ERROR_FAILURE;
+    }
+    DOM_CAMERA_LOGI("right=%d\n", r->right);
+    v = INT_TO_JSVAL(r->right);
+    if (!JS_SetProperty(aCx, o, "right", &v)) {
+      return NS_ERROR_FAILURE;
+    }
+    DOM_CAMERA_LOGI("weight=%d\n", r->weight);
+    v = INT_TO_JSVAL(r->weight);
+    if (!JS_SetProperty(aCx, o, "weight", &v)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    v = OBJECT_TO_JSVAL(o);
+    if (!JS_SetElement(aCx, array, i, &v)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  *aValue = JS::ObjectValue(*array);
+  return NS_OK;
+}
+
+/* readonly attribute nsICameraCapabilities capabilities; */
+NS_IMETHODIMP
+nsCameraControl::GetCapabilities(nsICameraCapabilities** aCapabilities)
+{
+  if (!mCapabilities) {
+    mCapabilities = new nsCameraCapabilities(this);
+  }
+
+  nsCOMPtr<nsICameraCapabilities> capabilities = mCapabilities;
+  capabilities.forget(aCapabilities);
+  return NS_OK;
+}
+
+/* attribute DOMString effect; */
+NS_IMETHODIMP
+nsCameraControl::GetEffect(nsAString& aEffect)
+{
+  return GetHelper(CAMERA_PARAM_EFFECT, aEffect);
+}
+NS_IMETHODIMP
+nsCameraControl::SetEffect(const nsAString& aEffect)
+{
+  return SetHelper(CAMERA_PARAM_EFFECT, aEffect);
+}
+
+/* attribute DOMString whiteBalanceMode; */
+NS_IMETHODIMP
+nsCameraControl::GetWhiteBalanceMode(nsAString& aWhiteBalanceMode)
+{
+  return GetHelper(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode);
+}
+NS_IMETHODIMP
+nsCameraControl::SetWhiteBalanceMode(const nsAString& aWhiteBalanceMode)
+{
+  return SetHelper(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode);
+}
+
+/* attribute DOMString sceneMode; */
+NS_IMETHODIMP
+nsCameraControl::GetSceneMode(nsAString& aSceneMode)
+{
+  return GetHelper(CAMERA_PARAM_SCENEMODE, aSceneMode);
+}
+NS_IMETHODIMP
+nsCameraControl::SetSceneMode(const nsAString& aSceneMode)
+{
+  return SetHelper(CAMERA_PARAM_SCENEMODE, aSceneMode);
+}
+
+/* attribute DOMString flashMode; */
+NS_IMETHODIMP
+nsCameraControl::GetFlashMode(nsAString& aFlashMode)
+{
+  return GetHelper(CAMERA_PARAM_FLASHMODE, aFlashMode);
+}
+NS_IMETHODIMP
+nsCameraControl::SetFlashMode(const nsAString& aFlashMode)
+{
+  return SetHelper(CAMERA_PARAM_FLASHMODE, aFlashMode);
+}
+
+/* attribute DOMString focusMode; */
+NS_IMETHODIMP
+nsCameraControl::GetFocusMode(nsAString& aFocusMode)
+{
+  return GetHelper(CAMERA_PARAM_FOCUSMODE, aFocusMode);
+}
+NS_IMETHODIMP
+nsCameraControl::SetFocusMode(const nsAString& aFocusMode)
+{
+  return SetHelper(CAMERA_PARAM_FOCUSMODE, aFocusMode);
+}
+
+/* attribute double zoom; */
+NS_IMETHODIMP
+nsCameraControl::GetZoom(double* aZoom)
+{
+  return GetHelper(CAMERA_PARAM_ZOOM, aZoom);
+}
+NS_IMETHODIMP
+nsCameraControl::SetZoom(double aZoom)
+{
+  return SetHelper(CAMERA_PARAM_ZOOM, aZoom);
+}
+
+/* attribute jsval meteringAreas; */
+NS_IMETHODIMP
+nsCameraControl::GetMeteringAreas(JSContext* cx, JS::Value* aMeteringAreas)
+{
+  return GetHelper(cx, CAMERA_PARAM_METERINGAREAS, aMeteringAreas);
+}
+NS_IMETHODIMP
+nsCameraControl::SetMeteringAreas(JSContext* cx, const JS::Value& aMeteringAreas)
+{
+  return SetHelper(cx, CAMERA_PARAM_METERINGAREAS, aMeteringAreas, mMaxMeteringAreas);
+}
+
+/* attribute jsval focusAreas; */
+NS_IMETHODIMP
+nsCameraControl::GetFocusAreas(JSContext* cx, JS::Value* aFocusAreas)
+{
+  return GetHelper(cx, CAMERA_PARAM_FOCUSAREAS, aFocusAreas);
+}
+NS_IMETHODIMP
+nsCameraControl::SetFocusAreas(JSContext* cx, const JS::Value& aFocusAreas)
+{
+  return SetHelper(cx, CAMERA_PARAM_FOCUSAREAS, aFocusAreas, mMaxFocusAreas);
+}
+
+/* readonly attribute double focalLength; */
+NS_IMETHODIMP
+nsCameraControl::GetFocalLength(double* aFocalLength)
+{
+  return GetHelper(CAMERA_PARAM_FOCALLENGTH, aFocalLength);
+}
+
+/* readonly attribute double focusDistanceNear; */
+NS_IMETHODIMP
+nsCameraControl::GetFocusDistanceNear(double* aFocusDistanceNear)
+{
+  return GetHelper(CAMERA_PARAM_FOCUSDISTANCENEAR, aFocusDistanceNear);
+}
+
+/* readonly attribute double focusDistanceOptimum; */
+NS_IMETHODIMP
+nsCameraControl::GetFocusDistanceOptimum(double* aFocusDistanceOptimum)
+{
+  return GetHelper(CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, aFocusDistanceOptimum);
+}
+
+/* readonly attribute double focusDistanceFar; */
+NS_IMETHODIMP
+nsCameraControl::GetFocusDistanceFar(double* aFocusDistanceFar)
+{
+  return GetHelper(CAMERA_PARAM_FOCUSDISTANCEFAR, aFocusDistanceFar);
+}
+
+/* void setExposureCompensation (const JS::Value& aCompensation, JSContext* cx); */
+NS_IMETHODIMP
+nsCameraControl::SetExposureCompensation(const JS::Value& aCompensation, JSContext* cx)
+{
+  if (aCompensation.isNullOrUndefined()) {
+    // use NaN to switch the camera back into auto mode
+    return SetHelper(CAMERA_PARAM_EXPOSURECOMPENSATION, NAN);
+  }
+
+  double compensation;
+  if (!JS_ValueToNumber(cx, aCompensation, &compensation)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  return SetHelper(CAMERA_PARAM_EXPOSURECOMPENSATION, compensation);
+}
+
+/* readonly attribute double exposureCompensation; */
+NS_IMETHODIMP
+nsCameraControl::GetExposureCompensation(double* aExposureCompensation)
+{
+  return GetHelper(CAMERA_PARAM_EXPOSURECOMPENSATION, aExposureCompensation);
+}
+
+/* attribute nsICameraShutterCallback onShutter; */
+NS_IMETHODIMP
+nsCameraControl::GetOnShutter(nsICameraShutterCallback** aOnShutter)
+{
+  *aOnShutter = mOnShutterCb;
+  return NS_OK;
+}
+NS_IMETHODIMP
+nsCameraControl::SetOnShutter(nsICameraShutterCallback* aOnShutter)
+{
+  mOnShutterCb = aOnShutter;
+  return NS_OK;
+}
+
+/* void startRecording (in jsval aOptions, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
+NS_IMETHODIMP
+nsCameraControl::StartRecording(const JS::Value& aOptions, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx)
+{
+  NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG);
+
+  CameraSize size;
+  nsresult rv = size.Init(cx, &aOptions);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIRunnable> startRecordingTask = new StartRecordingTask(this, size, onSuccess, onError);
+  mCameraThread->Dispatch(startRecordingTask, NS_DISPATCH_NORMAL);
+
+  return NS_OK;
+}
+
+/* void stopRecording (); */
+NS_IMETHODIMP
+nsCameraControl::StopRecording()
+{
+  nsCOMPtr<nsIRunnable> stopRecordingTask = new StopRecordingTask(this);
+  mCameraThread->Dispatch(stopRecordingTask, NS_DISPATCH_NORMAL);
+
+  return NS_OK;
+}
+
+/* [implicit_jscontext] void getPreviewStream (in jsval aOptions, in nsICameraPreviewStreamCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
+NS_IMETHODIMP
+nsCameraControl::GetPreviewStream(const JS::Value& aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx)
+{
+  NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG);
+
+  CameraSize size;
+  nsresult rv = size.Init(cx, &aOptions);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIRunnable> getPreviewStreamTask = new GetPreviewStreamTask(this, size, onSuccess, onError);
+  mCameraThread->Dispatch(getPreviewStreamTask, NS_DISPATCH_NORMAL);
+
+  return NS_OK;
+}
+
+/* void autoFocus (in nsICameraAutoFocusCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
+NS_IMETHODIMP
+nsCameraControl::AutoFocus(nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError)
+{
+  NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG);
+
+  nsCOMPtr<nsIRunnable> autoFocusTask = new AutoFocusTask(this, onSuccess, onError);
+  mCameraThread->Dispatch(autoFocusTask, NS_DISPATCH_NORMAL);
+
+  return NS_OK;
+}
+
+/* void takePicture (in jsval aOptions, in nsICameraTakePictureCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
+NS_IMETHODIMP nsCameraControl::TakePicture(const JS::Value& aOptions, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx)
+{
+  NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG);
+
+  CameraPictureOptions  options;
+  CameraSize            size;
+  CameraPosition        pos;
+
+  nsresult rv = options.Init(cx, &aOptions);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = size.Init(cx, &options.pictureSize);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  /**
+   * Default values, until the dictionary parser can handle them.
+   * NaN indicates no value provided.
+   */
+  pos.latitude = NAN;
+  pos.longitude = NAN;
+  pos.altitude = NAN;
+  pos.timestamp = NAN;
+  rv = pos.Init(cx, &options.position);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIRunnable> takePictureTask = new TakePictureTask(this, size, options.rotation, options.fileFormat, pos, onSuccess, onError);
+  mCameraThread->Dispatch(takePictureTask, NS_DISPATCH_NORMAL);
+
+  return NS_OK;
+}
+
+void
+nsCameraControl::AutoFocusComplete(bool aSuccess)
+{
+  /**
+   * Auto focusing can change some of the camera's parameters, so
+   * we need to pull a new set before sending the result to the
+   * main thread.
+   */
+  PullParametersImpl(nullptr);
+
+  nsCOMPtr<nsIRunnable> autoFocusResult = new AutoFocusResult(aSuccess, mAutoFocusOnSuccessCb);
+
+  nsresult rv = NS_DispatchToMainThread(autoFocusResult);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to dispatch autoFocus() onSuccess callback to main thread!");
+  }
+}
+
+void
+nsCameraControl::TakePictureComplete(PRUint8* aData, PRUint32 aLength)
+{
+  PRUint8* data = new PRUint8[aLength];
+
+  memcpy(data, aData, aLength);
+
+  /**
+   * TODO: pick up the actual specified picture format for the MIME type;
+   * for now, assume we'll be using JPEGs.
+   */
+  nsIDOMBlob* blob = new nsDOMMemoryFile(static_cast<void*>(data), static_cast<PRUint64>(aLength), NS_LITERAL_STRING("image/jpeg"));
+  nsCOMPtr<nsIRunnable> takePictureResult = new TakePictureResult(blob, mTakePictureOnSuccessCb);
+
+  nsresult rv = NS_DispatchToMainThread(takePictureResult);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to dispatch takePicture() onSuccess callback to main thread!");
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/CameraControl.h
@@ -0,0 +1,438 @@
+/* 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_NSCAMERACONTROL_H
+#define DOM_CAMERA_NSCAMERACONTROL_H
+
+#include "prtypes.h"
+#include "nsCOMPtr.h"
+#include "nsThread.h"
+#include "nsDOMFile.h"
+#include "DictionaryHelpers.h"
+#include "CameraPreview.h"
+#include "nsIDOMCameraManager.h"
+
+#define DOM_CAMERA_LOG_LEVEL 3
+#include "CameraCommon.h"
+
+namespace mozilla {
+
+using namespace dom;
+
+class GetPreviewStreamTask;
+class AutoFocusTask;
+class TakePictureTask;
+class StartRecordingTask;
+class StopRecordingTask;
+class SetParameterTask;
+class GetParameterTask;
+class PushParametersTask;
+class PullParametersTask;
+
+// Main camera control.
+class nsCameraControl : public nsICameraControl
+{
+  friend class GetPreviewStreamTask;
+  friend class AutoFocusTask;
+  friend class TakePictureTask;
+  friend class StartRecordingTask;
+  friend class StopRecordingTask;
+  friend class SetParameterTask;
+  friend class GetParameterTask;
+  friend class PushParametersTask;
+  friend class PullParametersTask;
+
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICAMERACONTROL
+
+  enum {
+    CAMERA_PARAM_EFFECT,
+    CAMERA_PARAM_WHITEBALANCE,
+    CAMERA_PARAM_SCENEMODE,
+    CAMERA_PARAM_FLASHMODE,
+    CAMERA_PARAM_FOCUSMODE,
+    CAMERA_PARAM_ZOOM,
+    CAMERA_PARAM_METERINGAREAS,
+    CAMERA_PARAM_FOCUSAREAS,
+    CAMERA_PARAM_FOCALLENGTH,
+    CAMERA_PARAM_FOCUSDISTANCENEAR,
+    CAMERA_PARAM_FOCUSDISTANCEOPTIMUM,
+    CAMERA_PARAM_FOCUSDISTANCEFAR,
+    CAMERA_PARAM_EXPOSURECOMPENSATION,
+
+    CAMERA_PARAM_SUPPORTED_PREVIEWSIZES,
+    CAMERA_PARAM_SUPPORTED_VIDEOSIZES,
+    CAMERA_PARAM_SUPPORTED_PICTURESIZES,
+    CAMERA_PARAM_SUPPORTED_PICTUREFORMATS,
+    CAMERA_PARAM_SUPPORTED_WHITEBALANCES,
+    CAMERA_PARAM_SUPPORTED_SCENEMODES,
+    CAMERA_PARAM_SUPPORTED_EFFECTS,
+    CAMERA_PARAM_SUPPORTED_FLASHMODES,
+    CAMERA_PARAM_SUPPORTED_FOCUSMODES,
+    CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS,
+    CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS,
+    CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION,
+    CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION,
+    CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP,
+    CAMERA_PARAM_SUPPORTED_ZOOM,
+    CAMERA_PARAM_SUPPORTED_ZOOMRATIOS
+  };
+  virtual const char* GetParameter(const char* aKey) = 0;
+  virtual const char* GetParameterConstChar(PRUint32 aKey) = 0;
+  virtual double GetParameterDouble(PRUint32 aKey) = 0;
+  virtual void GetParameter(PRUint32 aKey, nsTArray<CameraRegion>& aRegions) = 0;
+  virtual void SetParameter(const char* aKey, const char* aValue) = 0;
+  virtual void SetParameter(PRUint32 aKey, const char* aValue) = 0;
+  virtual void SetParameter(PRUint32 aKey, double aValue) = 0;
+  virtual void SetParameter(PRUint32 aKey, const nsTArray<CameraRegion>& aRegions) = 0;
+  virtual void PushParameters() = 0;
+
+  nsCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread)
+    : mCameraId(aCameraId)
+    , mCameraThread(aCameraThread)
+    , mCapabilities(nullptr)
+    , mPreview(nullptr)
+    , mFileFormat()
+    , mMaxMeteringAreas(0)
+    , mMaxFocusAreas(0)
+    , mAutoFocusOnSuccessCb(nullptr)
+    , mAutoFocusOnErrorCb(nullptr)
+    , mTakePictureOnSuccessCb(nullptr)
+    , mTakePictureOnErrorCb(nullptr)
+    , mStartRecordingOnSuccessCb(nullptr)
+    , mStartRecordingOnErrorCb(nullptr)
+    , mOnShutterCb(nullptr)
+  { }
+
+  void TakePictureComplete(PRUint8 *aData, PRUint32 aLength);
+  void AutoFocusComplete(bool aSuccess);
+
+protected:
+  virtual ~nsCameraControl() { }
+
+  nsresult SetHelper(PRUint32 aKey, const nsAString& aValue);
+  nsresult GetHelper(PRUint32 aKey, nsAString& aValue);
+  nsresult SetHelper(PRUint32 aKey, double aValue);
+  nsresult GetHelper(PRUint32 aKey, double* aValue);
+  nsresult SetHelper(JSContext* aCx, PRUint32 aKey, const JS::Value& aValue, PRUint32 aLimit);
+  nsresult GetHelper(JSContext* aCx, PRUint32 aKey, JS::Value* aValue);
+
+  virtual nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream) = 0;
+  virtual nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus) = 0;
+  virtual nsresult TakePictureImpl(TakePictureTask* aTakePicture) = 0;
+  virtual nsresult StartRecordingImpl(StartRecordingTask* aStartRecording) = 0;
+  virtual nsresult StopRecordingImpl(StopRecordingTask* aStopRecording) = 0;
+  virtual nsresult PushParametersImpl(PushParametersTask* aPushParameters) = 0;
+  virtual nsresult PullParametersImpl(PullParametersTask* aPullParameters) = 0;
+
+private:
+  nsCameraControl(const nsCameraControl&) MOZ_DELETE;
+  nsCameraControl& operator=(const nsCameraControl&) MOZ_DELETE;
+
+protected:
+  /* additional members */
+  PRUint32                        mCameraId;
+  nsCOMPtr<nsIThread>             mCameraThread;
+  nsCOMPtr<nsICameraCapabilities> mCapabilities;
+  PRUint32                        mPreviewWidth;
+  PRUint32                        mPreviewHeight;
+  nsCOMPtr<CameraPreview>         mPreview;
+  nsString                        mFileFormat;
+  PRUint32                        mMaxMeteringAreas;
+  PRUint32                        mMaxFocusAreas;
+
+  nsCOMPtr<nsICameraAutoFocusCallback>      mAutoFocusOnSuccessCb;
+  nsCOMPtr<nsICameraErrorCallback>          mAutoFocusOnErrorCb;
+  nsCOMPtr<nsICameraTakePictureCallback>    mTakePictureOnSuccessCb;
+  nsCOMPtr<nsICameraErrorCallback>          mTakePictureOnErrorCb;
+  nsCOMPtr<nsICameraStartRecordingCallback> mStartRecordingOnSuccessCb;
+  nsCOMPtr<nsICameraErrorCallback>          mStartRecordingOnErrorCb;
+  nsCOMPtr<nsICameraShutterCallback>        mOnShutterCb;
+};
+
+// Return the resulting preview stream to JS.  Runs on the main thread.
+class GetPreviewStreamResult : public nsRunnable
+{
+public:
+  GetPreviewStreamResult(nsIDOMMediaStream* aStream, nsICameraPreviewStreamCallback* onSuccess)
+    : mStream(aStream)
+    , mOnSuccessCb(onSuccess)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (mOnSuccessCb) {
+      mOnSuccessCb->HandleEvent(mStream);
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsCOMPtr<nsIDOMMediaStream> mStream;
+  nsCOMPtr<nsICameraPreviewStreamCallback> mOnSuccessCb;
+};
+
+// Get the desired preview stream.
+class GetPreviewStreamTask : public nsRunnable
+{
+public:
+  GetPreviewStreamTask(nsCameraControl* aCameraControl, CameraSize aSize, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError)
+    : mSize(aSize)
+    , mCameraControl(aCameraControl)
+    , mOnSuccessCb(onSuccess)
+    , mOnErrorCb(onError)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    nsresult rv = mCameraControl->GetPreviewStreamImpl(this);
+
+    if (NS_FAILED(rv) && mOnErrorCb) {
+      rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE")));
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    return rv;
+  }
+
+  CameraSize mSize;
+  nsCOMPtr<nsCameraControl> mCameraControl;
+  nsCOMPtr<nsICameraPreviewStreamCallback> mOnSuccessCb;
+  nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
+};
+
+// Return the autofocus status to JS.  Runs on the main thread.
+class AutoFocusResult : public nsRunnable
+{
+public:
+  AutoFocusResult(bool aSuccess, nsICameraAutoFocusCallback* onSuccess)
+    : mSuccess(aSuccess)
+    , mOnSuccessCb(onSuccess)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (mOnSuccessCb) {
+      mOnSuccessCb->HandleEvent(mSuccess);
+    }
+    return NS_OK;
+  }
+
+protected:
+  bool mSuccess;
+  nsCOMPtr<nsICameraAutoFocusCallback> mOnSuccessCb;
+};
+
+// Autofocus the camera.
+class AutoFocusTask : public nsRunnable
+{
+public:
+  AutoFocusTask(nsCameraControl* aCameraControl, nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError)
+    : mCameraControl(aCameraControl)
+    , mOnSuccessCb(onSuccess)
+    , mOnErrorCb(onError)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+    nsresult rv = mCameraControl->AutoFocusImpl(this);
+    DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+
+    if (NS_FAILED(rv) && mOnErrorCb) {
+      rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE")));
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    return rv;
+  }
+
+  nsCOMPtr<nsCameraControl> mCameraControl;
+  nsCOMPtr<nsICameraAutoFocusCallback> mOnSuccessCb;
+  nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
+};
+
+// Return the captured picture to JS.  Runs on the main thread.
+class TakePictureResult : public nsRunnable
+{
+public:
+  TakePictureResult(nsIDOMBlob* aImage, nsICameraTakePictureCallback* onSuccess)
+    : mImage(aImage)
+    , mOnSuccessCb(onSuccess)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (mOnSuccessCb) {
+      mOnSuccessCb->HandleEvent(mImage);
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsCOMPtr<nsIDOMBlob> mImage;
+  nsCOMPtr<nsICameraTakePictureCallback> mOnSuccessCb;
+};
+
+// Capture a still image with the camera.
+class TakePictureTask : public nsRunnable
+{
+public:
+  TakePictureTask(nsCameraControl* aCameraControl, CameraSize aSize, PRInt32 aRotation, const nsAString& aFileFormat, CameraPosition aPosition, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError)
+    : mCameraControl(aCameraControl)
+    , mSize(aSize)
+    , mRotation(aRotation)
+    , mFileFormat(aFileFormat)
+    , mPosition(aPosition)
+    , mOnSuccessCb(onSuccess)
+    , mOnErrorCb(onError)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+    nsresult rv = mCameraControl->TakePictureImpl(this);
+    DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+
+    if (NS_FAILED(rv) && mOnErrorCb) {
+      rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE")));
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    return rv;
+  }
+
+  nsCOMPtr<nsCameraControl> mCameraControl;
+  CameraSize mSize;
+  PRInt32 mRotation;
+  nsString mFileFormat;
+  CameraPosition mPosition;
+  nsCOMPtr<nsICameraTakePictureCallback> mOnSuccessCb;
+  nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
+};
+
+// Return the captured video to JS.  Runs on the main thread.
+class StartRecordingResult : public nsRunnable
+{
+public:
+  StartRecordingResult(nsIDOMMediaStream* aStream, nsICameraStartRecordingCallback* onSuccess)
+    : mStream(aStream)
+    , mOnSuccessCb(onSuccess)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (mOnSuccessCb) {
+      mOnSuccessCb->HandleEvent(mStream);
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsCOMPtr<nsIDOMMediaStream> mStream;
+  nsCOMPtr<nsICameraStartRecordingCallback> mOnSuccessCb;
+};
+
+// Start video recording.
+class StartRecordingTask : public nsRunnable
+{
+public:
+  StartRecordingTask(nsCameraControl* aCameraControl, CameraSize aSize, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError)
+    : mSize(aSize)
+    , mCameraControl(aCameraControl)
+    , mOnSuccessCb(onSuccess)
+    , mOnErrorCb(onError)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+    nsresult rv = mCameraControl->StartRecordingImpl(this);
+    DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+
+    if (NS_FAILED(rv) && mOnErrorCb) {
+      rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE")));
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    return rv;
+  }
+
+  CameraSize mSize;
+  nsCOMPtr<nsCameraControl> mCameraControl;
+  nsCOMPtr<nsICameraStartRecordingCallback> mOnSuccessCb;
+  nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
+};
+
+// Stop video recording.
+class StopRecordingTask : public nsRunnable
+{
+public:
+  StopRecordingTask(nsCameraControl* aCameraControl)
+    : mCameraControl(aCameraControl)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+    nsresult rv = mCameraControl->StopRecordingImpl(this);
+    DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+
+    NS_ENSURE_SUCCESS(rv, rv);
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsCameraControl> mCameraControl;
+};
+
+// Pushes all camera parameters to the camera.
+class PushParametersTask : public nsRunnable
+{
+public:
+  PushParametersTask(nsCameraControl* aCameraControl)
+    : mCameraControl(aCameraControl)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+    nsresult rv = mCameraControl->PushParametersImpl(this);
+    DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+
+    NS_ENSURE_SUCCESS(rv, rv);
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsCameraControl> mCameraControl;
+};
+
+// Get all camera parameters from the camera.
+class PullParametersTask : public nsRunnable
+{
+public:
+  PullParametersTask(nsCameraControl* aCameraControl)
+    : mCameraControl(aCameraControl)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+    nsresult rv = mCameraControl->PullParametersImpl(this);
+    DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+
+    NS_ENSURE_SUCCESS(rv, rv);
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsCameraControl> mCameraControl;
+};
+
+} // namespace mozilla
+
+#endif // DOM_CAMERA_NSCAMERACONTROL_H
new file mode 100644
--- /dev/null
+++ b/dom/camera/CameraPreview.cpp
@@ -0,0 +1,98 @@
+/* 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 "CameraPreview.h"
+
+#define DOM_CAMERA_LOG_LEVEL  3
+#include "CameraCommon.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS1(CameraPreview, CameraPreview)
+
+class CameraPreviewListener : public MediaStreamListener
+{
+public:
+  CameraPreviewListener(CameraPreview* aPreview) :
+    mPreview(aPreview)
+  {
+    DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this);
+  }
+
+  ~CameraPreviewListener()
+  {
+    DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this);
+  }
+
+  void NotifyConsumptionChanged(MediaStreamGraph* aGraph, Consumption aConsuming)
+  {
+    const char* state;
+
+    DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this);
+
+    switch (aConsuming) {
+      case NOT_CONSUMED:
+        state = "not consuming";
+        break;
+
+      case CONSUMED:
+        state = "consuming";
+        break;
+
+      default:
+        state = "unknown";
+        break;
+    }
+
+    DOM_CAMERA_LOGA("camera viewfinder is %s\n", state);
+
+    switch (aConsuming) {
+      case NOT_CONSUMED:
+        mPreview->Stop();
+        break;
+
+      case CONSUMED:
+        mPreview->Start();
+        break;
+    }
+  }
+
+protected:
+  nsCOMPtr<CameraPreview> mPreview;
+};
+
+CameraPreview::CameraPreview(PRUint32 aWidth, PRUint32 aHeight)
+  : nsDOMMediaStream()
+  , mWidth(aWidth)
+  , mHeight(aHeight)
+  , mFramesPerSecond(0)
+  , mFrameCount(0)
+{
+  DOM_CAMERA_LOGI("%s:%d : mWidth=%d, mHeight=%d : this=%p\n", __func__, __LINE__, mWidth, mHeight, this);
+
+  mImageContainer = LayerManager::CreateImageContainer();
+  MediaStreamGraph* gm = MediaStreamGraph::GetInstance();
+  mStream = gm->CreateInputStream(this);
+  mInput = GetStream()->AsSourceStream();
+  mInput->AddListener(new CameraPreviewListener(this));
+}
+
+void
+CameraPreview::SetFrameRate(PRUint32 aFramesPerSecond)
+{
+  mFramesPerSecond = aFramesPerSecond;
+  mInput->AddTrack(TRACK_VIDEO, mFramesPerSecond, 0, new VideoSegment());
+  mInput->AdvanceKnownTracksTime(MEDIA_TIME_MAX);
+}
+
+CameraPreview::~CameraPreview()
+{
+  DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this);
+
+  /**
+   * We _must_ remember to call RemoveListener on this before destroying this,
+   * else the media framework will trigger a double-free.
+   */
+  DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/CameraPreview.h
@@ -0,0 +1,58 @@
+/* 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_CAMERAPREVIEW_H
+#define DOM_CAMERA_CAMERAPREVIEW_H
+
+#include "MediaStreamGraph.h"
+#include "StreamBuffer.h"
+#include "nsDOMMediaStream.h"
+
+#define DOM_CAMERA_LOG_LEVEL  3
+#include "CameraCommon.h"
+
+using namespace mozilla;
+using namespace mozilla::layers;
+
+namespace mozilla {
+
+class CameraPreview : public nsDOMMediaStream
+                    , public MediaStreamListener
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  CameraPreview(PRUint32 aWidth, PRUint32 aHeight);
+
+  void SetFrameRate(PRUint32 aFramesPerSecond);
+
+  NS_IMETHODIMP
+  GetCurrentTime(double* aCurrentTime) {
+    return nsDOMMediaStream::GetCurrentTime(aCurrentTime);
+  }
+
+  virtual void Start() = 0;
+  virtual void Stop() = 0;
+
+protected:
+  virtual ~CameraPreview();
+
+  PRUint32 mWidth;
+  PRUint32 mHeight;
+  PRUint32 mFramesPerSecond;
+  SourceMediaStream* mInput;
+  nsRefPtr<mozilla::layers::ImageContainer> mImageContainer;
+  VideoSegment mVideoSegment;
+  PRUint32 mFrameCount;
+
+  enum { TRACK_VIDEO = 1 };
+
+private:
+  CameraPreview(const CameraPreview&) MOZ_DELETE;
+  CameraPreview& operator=(const CameraPreview&) MOZ_DELETE;
+};
+
+} // namespace mozilla
+
+#endif // DOM_CAMERA_CAMERAPREVIEW_H
new file mode 100644
--- /dev/null
+++ b/dom/camera/DOMCameraManager.cpp
@@ -0,0 +1,89 @@
+/* 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 "CameraControl.h"
+#include "DOMCameraManager.h"
+#include "nsDOMClassInfo.h"
+#include "DictionaryHelpers.h"
+
+#define DOM_CAMERA_LOG_LEVEL  3
+#include "CameraCommon.h"
+
+using namespace mozilla;
+
+DOMCI_DATA(CameraManager, nsIDOMCameraManager)
+
+NS_INTERFACE_MAP_BEGIN(nsDOMCameraManager)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMCameraManager)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraManager)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsDOMCameraManager)
+NS_IMPL_RELEASE(nsDOMCameraManager)
+
+/**
+ * nsDOMCameraManager::GetListOfCameras
+ * is implementation-specific, and can be found in (e.g.)
+ * GonkCameraManager.cpp and FallbackCameraManager.cpp.
+ */
+
+nsDOMCameraManager::nsDOMCameraManager(PRUint64 aWindowId)
+  : mWindowId(aWindowId)
+{
+  /* member initializers and constructor code */
+  DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+}
+
+nsDOMCameraManager::~nsDOMCameraManager()
+{
+  /* destructor code */
+  DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+}
+
+void
+nsDOMCameraManager::OnNavigation(PRUint64 aWindowId)
+{
+  // TODO: implement -- see getUserMedia() implementation
+}
+
+// static creator
+already_AddRefed<nsDOMCameraManager>
+nsDOMCameraManager::Create(PRUint64 aWindowId)
+{
+  // TODO: check for permissions here to access cameras
+
+  nsRefPtr<nsDOMCameraManager> cameraManager = new nsDOMCameraManager(aWindowId);
+  return cameraManager.forget();
+}
+
+/* [implicit_jscontext] void getCamera ([optional] in jsval aOptions, in nsICameraGetCameraCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
+NS_IMETHODIMP
+nsDOMCameraManager::GetCamera(const JS::Value& aOptions, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx)
+{
+  NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG);
+
+  PRUint32 cameraId = 0;  // back (or forward-facing) camera by default
+  CameraSelector selector;
+
+  nsresult rv = selector.Init(cx, &aOptions);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (selector.camera.EqualsASCII("front")) {
+    cameraId = 1;
+  }
+
+  // reuse the same camera thread to conserve resources
+  if (!mCameraThread) {
+    rv = NS_NewThread(getter_AddRefs(mCameraThread));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+
+  nsCOMPtr<nsIRunnable> getCameraTask = new GetCameraTask(cameraId, onSuccess, onError, mCameraThread);
+  mCameraThread->Dispatch(getCameraTask, NS_DISPATCH_NORMAL);
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/DOMCameraManager.h
@@ -0,0 +1,82 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* 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 "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "nsIDOMCameraManager.h"
+
+class nsDOMCameraManager : public nsIDOMCameraManager
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMCAMERAMANAGER
+
+  static already_AddRefed<nsDOMCameraManager> Create(PRUint64 aWindowId);
+
+  void OnNavigation(PRUint64 aWindowId);
+
+private:
+  nsDOMCameraManager();
+  nsDOMCameraManager(PRUint64 aWindowId);
+  nsDOMCameraManager(const nsDOMCameraManager&) MOZ_DELETE;
+  nsDOMCameraManager& operator=(const nsDOMCameraManager&) MOZ_DELETE;
+  ~nsDOMCameraManager();
+
+protected:
+  PRUint64 mWindowId;
+  nsCOMPtr<nsIThread> mCameraThread;
+};
+
+
+class GetCameraTask : public nsRunnable
+{
+public:
+  GetCameraTask(PRUint32 aCameraId, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, nsIThread* aCameraThread)
+    : mCameraId(aCameraId)
+    , mOnSuccessCb(onSuccess)
+    , mOnErrorCb(onError)
+    , mCameraThread(aCameraThread)
+  { }
+
+  NS_IMETHOD Run();
+
+protected:
+  PRUint32 mCameraId;
+  nsCOMPtr<nsICameraGetCameraCallback> mOnSuccessCb;
+  nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
+  nsCOMPtr<nsIThread> mCameraThread;
+};
+
+class GetCameraResult : public nsRunnable
+{
+public:
+  GetCameraResult(nsICameraControl* aCameraControl, nsICameraGetCameraCallback* onSuccess)
+    : mCameraControl(aCameraControl)
+    , mOnSuccessCb(onSuccess)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // TODO: window management stuff
+    if (mOnSuccessCb) {
+      mOnSuccessCb->HandleEvent(mCameraControl);
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsCOMPtr<nsICameraControl> mCameraControl;
+  nsCOMPtr<nsICameraGetCameraCallback> mOnSuccessCb;
+};
+
+#endif // DOM_CAMERA_DOMCAMERAMANAGER_H
new file mode 100644
--- /dev/null
+++ b/dom/camera/FallbackCameraCapabilities.cpp
@@ -0,0 +1,140 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDOMClassInfoID.h"
+#include "CameraControl.h"
+#include "CameraCapabilities.h"
+
+#define DOM_CAMERA_LOG_LEVEL  3
+#include "CameraCommon.h"
+
+using namespace mozilla;
+
+DOMCI_DATA(CameraCapabilities, nsICameraCapabilities)
+
+NS_INTERFACE_MAP_BEGIN(nsCameraCapabilities)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRY(nsICameraCapabilities)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraCapabilities)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsCameraCapabilities)
+NS_IMPL_RELEASE(nsCameraCapabilities)
+
+nsCameraCapabilities::nsCameraCapabilities(nsCameraControl* aCamera)
+  : mCamera(aCamera)
+{
+  /* member initializers and constructor code */
+  DOM_CAMERA_LOGI("%s:%d : FALLBACK CAMERA CAPABILITIES\n", __func__, __LINE__);
+}
+
+nsCameraCapabilities::~nsCameraCapabilities()
+{
+  /* destructor code */
+}
+
+/* [implicit_jscontext] readonly attribute jsval previewSizes; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetPreviewSizes(JSContext* cx, JS::Value* aPreviewSizes)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [implicit_jscontext] readonly attribute jsval pictureSizes; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetPictureSizes(JSContext* cx, JS::Value* aPictureSizes)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [implicit_jscontext] readonly attribute jsval fileFormats; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetFileFormats(JSContext* cx, JS::Value* aFileFormats)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [implicit_jscontext] readonly attribute jsval whiteBalanceModes; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetWhiteBalanceModes(JSContext* cx, JS::Value* aWhiteBalanceModes)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [implicit_jscontext] readonly attribute jsval sceneModes; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetSceneModes(JSContext* cx, JS::Value* aSceneModes)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [implicit_jscontext] readonly attribute jsval effects; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetEffects(JSContext* cx, JS::Value* aEffects)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [implicit_jscontext] readonly attribute jsval flashModes; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetFlashModes(JSContext* cx, JS::Value* aFlashModes)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [implicit_jscontext] readonly attribute jsval focusModes; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetFocusModes(JSContext* cx, JS::Value* aFocusModes)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [implicit_jscontext] readonly attribute long maxFocusAreas; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetMaxFocusAreas(JSContext* cx, PRInt32* aMaxFocusAreas)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [implicit_jscontext] readonly attribute double minExposureCompensation; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetMinExposureCompensation(JSContext* cx, double* aMinExposureCompensation)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [implicit_jscontext] readonly attribute double maxExposureCompensation; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetMaxExposureCompensation(JSContext* cx, double* aMaxExposureCompensation)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [implicit_jscontext] readonly attribute double stepExposureCompensation; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetStepExposureCompensation(JSContext* cx, double* aStepExposureCompensation)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [implicit_jscontext] readonly attribute long maxMeteringAreas; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetMaxMeteringAreas(JSContext* cx, PRInt32* aMaxMeteringAreas)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [implicit_jscontext] readonly attribute jsval zoomRatios; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetZoomRatios(JSContext* cx, JS::Value* aZoomRatios)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [implicit_jscontext] readonly attribute jsval videoSizes; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetVideoSizes(JSContext* cx, JS::Value* aVideoSizes)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/FallbackCameraControl.cpp
@@ -0,0 +1,147 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDOMClassInfoID.h"
+#include "DOMCameraManager.h"
+#include "CameraControl.h"
+
+using namespace mozilla;
+
+/**
+ * Fallback camera control subclass.  Can be used as a template for the
+ * definition of new camera support classes.
+ */
+class nsFallbackCameraControl : public nsCameraControl
+{
+public:
+  nsFallbackCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread);
+
+  const char* GetParameter(const char* aKey);
+  const char* GetParameterConstChar(PRUint32 aKey);
+  double GetParameterDouble(PRUint32 aKey);
+  void GetParameter(PRUint32 aKey, nsTArray<dom::CameraRegion>& aRegions);
+  void SetParameter(const char* aKey, const char* aValue);
+  void SetParameter(PRUint32 aKey, const char* aValue);
+  void SetParameter(PRUint32 aKey, double aValue);
+  void SetParameter(PRUint32 aKey, const nsTArray<dom::CameraRegion>& aRegions);
+  void PushParameters();
+
+protected:
+  ~nsFallbackCameraControl();
+
+  nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream);
+  nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus);
+  nsresult TakePictureImpl(TakePictureTask* aTakePicture);
+  nsresult StartRecordingImpl(StartRecordingTask* aStartRecording);
+  nsresult StopRecordingImpl(StopRecordingTask* aStopRecording);
+  nsresult PushParametersImpl(PushParametersTask* aPushParameters);
+  nsresult PullParametersImpl(PullParametersTask* aPullParameters);
+
+private:
+  nsFallbackCameraControl(const nsFallbackCameraControl&) MOZ_DELETE;
+  nsFallbackCameraControl& operator=(const nsFallbackCameraControl&) MOZ_DELETE;
+};
+
+/**
+ * Stub implemetations of the fallback camera control.
+ *
+ * None of these should ever get called--they exist to keep the linker happy,
+ * and may be used as templates for new camera support classes.
+ */
+nsFallbackCameraControl::nsFallbackCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread)
+  : nsCameraControl(aCameraId, aCameraThread)
+{ }
+
+nsFallbackCameraControl::~nsFallbackCameraControl()
+{ }
+
+const char*
+nsFallbackCameraControl::GetParameter(const char* aKey)
+{
+  return nullptr;
+}
+
+const char*
+nsFallbackCameraControl::GetParameterConstChar(PRUint32 aKey)
+{
+  return nullptr;
+}
+
+double
+nsFallbackCameraControl::GetParameterDouble(PRUint32 aKey)
+{
+  return NAN;
+}
+
+void
+nsFallbackCameraControl::GetParameter(PRUint32 aKey, nsTArray<dom::CameraRegion>& aRegions)
+{
+}
+
+void
+nsFallbackCameraControl::SetParameter(const char* aKey, const char* aValue)
+{
+}
+
+void
+nsFallbackCameraControl::SetParameter(PRUint32 aKey, const char* aValue)
+{
+}
+
+void
+nsFallbackCameraControl::SetParameter(PRUint32 aKey, double aValue)
+{
+}
+
+void
+nsFallbackCameraControl::SetParameter(PRUint32 aKey, const nsTArray<dom::CameraRegion>& aRegions)
+{
+}
+
+void
+nsFallbackCameraControl::PushParameters()
+{
+}
+
+nsresult
+nsFallbackCameraControl::GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsFallbackCameraControl::AutoFocusImpl(AutoFocusTask* aAutoFocus)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsFallbackCameraControl::TakePictureImpl(TakePictureTask* aTakePicture)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsFallbackCameraControl::StartRecordingImpl(StartRecordingTask* aStartRecording)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsFallbackCameraControl::StopRecordingImpl(StopRecordingTask* aStopRecording)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsFallbackCameraControl::PushParametersImpl(PushParametersTask* aPushParameters)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsFallbackCameraControl::PullParametersImpl(PullParametersTask* aPullParameters)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/FallbackCameraManager.cpp
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DOMCameraManager.h"
+
+// From nsDOMCameraManager.
+
+/* [implicit_jscontext] jsval getListOfCameras (); */
+NS_IMETHODIMP
+nsDOMCameraManager::GetListOfCameras(JSContext* cx, JS::Value* _retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+using namespace mozilla;
+
+NS_IMETHODIMP
+GetCameraTask::Run()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/GonkCameraCapabilities.cpp
@@ -0,0 +1,352 @@
+/* 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 <string.h>
+#include <stdlib.h>
+#include "nsDOMClassInfo.h"
+#include "jsapi.h"
+#include "camera/CameraParameters.h"
+#include "CameraControl.h"
+#include "CameraCapabilities.h"
+
+#define DOM_CAMERA_LOG_LEVEL  3
+#include "CameraCommon.h"
+
+using namespace android;
+using namespace mozilla;
+
+DOMCI_DATA(CameraCapabilities, nsICameraCapabilities)
+
+NS_INTERFACE_MAP_BEGIN(nsCameraCapabilities)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRY(nsICameraCapabilities)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraCapabilities)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsCameraCapabilities)
+NS_IMPL_RELEASE(nsCameraCapabilities)
+
+
+nsCameraCapabilities::nsCameraCapabilities(nsCameraControl* aCamera)
+  : mCamera(aCamera)
+{
+  // member initializers and constructor code
+  DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this);
+}
+
+nsCameraCapabilities::~nsCameraCapabilities()
+{
+  // destructor code
+  DOM_CAMERA_LOGI("%s:%d : this=%p, mCamera=%p\n", __func__, __LINE__, this, mCamera.get());
+}
+
+static nsresult
+ParseZoomRatioItemAndAdd(JSContext* aCx, JSObject* aArray, PRUint32 aIndex, const char* aStart, char** aEnd)
+{
+  if (!*aEnd) {
+    // make 'aEnd' follow the same semantics as strchr().
+    aEnd = nullptr;
+  }
+
+  double d = strtod(aStart, aEnd);
+  jsval v;
+
+  d /= 100;
+  if (!JS_NewNumberValue(aCx, d, &v)) {
+    return NS_ERROR_FAILURE;
+  }
+  if (!JS_SetElement(aCx, aArray, aIndex, &v)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+static nsresult
+ParseStringItemAndAdd(JSContext* aCx, JSObject* aArray, PRUint32 aIndex, const char* aStart, char** aEnd)
+{
+  JSString* s;
+
+  if (*aEnd) {
+    s = JS_NewStringCopyN(aCx, aStart, *aEnd - aStart);
+  } else {
+    s = JS_NewStringCopyZ(aCx, aStart);
+  }
+  if (!s) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  jsval v = STRING_TO_JSVAL(s);
+  if (!JS_SetElement(aCx, aArray, aIndex, &v)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+static nsresult
+ParseDimensionItemAndAdd(JSContext* aCx, JSObject* aArray, PRUint32 aIndex, const char* aStart, char** aEnd)
+{
+  char* x;
+
+  if (!*aEnd) {
+    // make 'aEnd' follow the same semantics as strchr().
+    aEnd = nullptr;
+  }
+
+  jsval w = INT_TO_JSVAL(strtol(aStart, &x, 10));
+  jsval h = INT_TO_JSVAL(strtol(x + 1, aEnd, 10));
+
+  JSObject* o = JS_NewObject(aCx, nullptr, nullptr, nullptr);
+  if (!o) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  if (!JS_SetProperty(aCx, o, "width", &w)) {
+    return NS_ERROR_FAILURE;
+  }
+  if (!JS_SetProperty(aCx, o, "height", &h)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  jsval v = OBJECT_TO_JSVAL(o);
+  if (!JS_SetElement(aCx, aArray, aIndex, &v)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsCameraCapabilities::ParameterListToNewArray(JSContext* aCx, JSObject** aArray, const char* aKey, ParseItemAndAddFunc aParseItemAndAdd)
+{
+  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+
+  const char* value = mCamera->GetParameter(aKey);
+  if (!value) {
+    // in case we get nonsense data back
+    *aArray = nullptr;
+    return NS_OK;
+  }
+
+  *aArray = JS_NewArrayObject(aCx, 0, nullptr);
+  if (!*aArray) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  const char* p = value;
+  PRUint32 index = 0;
+  nsresult rv;
+  char* q;
+
+  while (p) {
+    q = strchr(p, ',');
+    if (q != p) { // skip consecutive delimiters, just in case
+      rv = aParseItemAndAdd(aCx, *aArray, index, p, &q);
+      NS_ENSURE_SUCCESS(rv, rv);
+      ++index;
+    }
+    p = q;
+    if (p) {
+      ++p;
+    }
+  }
+
+  return JS_FreezeObject(aCx, *aArray) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
+nsCameraCapabilities::StringListToNewObject(JSContext* aCx, JS::Value* aArray, const char* aKey)
+{
+  JSObject* array;
+
+  nsresult rv = ParameterListToNewArray(aCx, &array, aKey, ParseStringItemAndAdd);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aArray = OBJECT_TO_JSVAL(array);
+  return NS_OK;
+}
+
+nsresult
+nsCameraCapabilities::DimensionListToNewObject(JSContext* aCx, JS::Value* aArray, const char* aKey)
+{
+  JSObject* array;
+  nsresult rv;
+
+  rv = ParameterListToNewArray(aCx, &array, aKey, ParseDimensionItemAndAdd);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aArray = OBJECT_TO_JSVAL(array);
+  return NS_OK;
+}
+
+/* readonly attribute jsval previewSizes; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetPreviewSizes(JSContext* cx, JS::Value* aPreviewSizes)
+{
+  return DimensionListToNewObject(cx, aPreviewSizes, CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES);
+}
+
+/* readonly attribute jsval pictureSizes; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetPictureSizes(JSContext* cx, JS::Value* aPictureSizes)
+{
+  return DimensionListToNewObject(cx, aPictureSizes, CameraParameters::KEY_SUPPORTED_PICTURE_SIZES);
+}
+
+/* readonly attribute jsval fileFormats; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetFileFormats(JSContext* cx, JS::Value* aFileFormats)
+{
+  return StringListToNewObject(cx, aFileFormats, CameraParameters::KEY_SUPPORTED_PICTURE_FORMATS);
+}
+
+/* readonly attribute jsval whiteBalanceModes; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetWhiteBalanceModes(JSContext* cx, JS::Value* aWhiteBalanceModes)
+{
+  return StringListToNewObject(cx, aWhiteBalanceModes, CameraParameters::KEY_SUPPORTED_WHITE_BALANCE);
+}
+
+/* readonly attribute jsval sceneModes; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetSceneModes(JSContext* cx, JS::Value* aSceneModes)
+{
+  return StringListToNewObject(cx, aSceneModes, CameraParameters::KEY_SUPPORTED_SCENE_MODES);
+}
+
+/* readonly attribute jsval effects; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetEffects(JSContext* cx, JS::Value* aEffects)
+{
+  return StringListToNewObject(cx, aEffects, CameraParameters::KEY_SUPPORTED_EFFECTS);
+}
+
+/* readonly attribute jsval flashModes; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetFlashModes(JSContext* cx, JS::Value* aFlashModes)
+{
+  return StringListToNewObject(cx, aFlashModes, CameraParameters::KEY_SUPPORTED_FLASH_MODES);
+}
+
+/* readonly attribute jsval focusModes; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetFocusModes(JSContext* cx, JS::Value* aFocusModes)
+{
+  return StringListToNewObject(cx, aFocusModes, CameraParameters::KEY_SUPPORTED_FOCUS_MODES);
+}
+
+/* readonly attribute long maxFocusAreas; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetMaxFocusAreas(JSContext* cx, PRInt32* aMaxFocusAreas)
+{
+  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+
+  const char* value = mCamera->GetParameter(CameraParameters::KEY_MAX_NUM_FOCUS_AREAS);
+  if (!value) {
+    // in case we get nonsense data back
+    *aMaxFocusAreas = 0;
+    return NS_OK;
+  }
+
+  *aMaxFocusAreas = atoi(value);
+  return NS_OK;
+}
+
+/* readonly attribute double minExposureCompensation; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetMinExposureCompensation(JSContext* cx, double* aMinExposureCompensation)
+{
+  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+
+  const char* value = mCamera->GetParameter(CameraParameters::KEY_MIN_EXPOSURE_COMPENSATION);
+  if (!value) {
+    // in case we get nonsense data back
+    *aMinExposureCompensation = 0;
+    return NS_OK;
+  }
+
+  *aMinExposureCompensation = atof(value);
+  return NS_OK;
+}
+
+/* readonly attribute double maxExposureCompensation; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetMaxExposureCompensation(JSContext* cx, double* aMaxExposureCompensation)
+{
+  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+
+  const char* value = mCamera->GetParameter(CameraParameters::KEY_MAX_EXPOSURE_COMPENSATION);
+  if (!value) {
+    // in case we get nonsense data back
+    *aMaxExposureCompensation = 0;
+    return NS_OK;
+  }
+
+  *aMaxExposureCompensation = atof(value);
+  return NS_OK;
+}
+
+/* readonly attribute double stepExposureCompensation; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetStepExposureCompensation(JSContext* cx, double* aStepExposureCompensation)
+{
+  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+
+  const char* value = mCamera->GetParameter(CameraParameters::KEY_EXPOSURE_COMPENSATION_STEP);
+  if (!value) {
+    // in case we get nonsense data back
+    *aStepExposureCompensation = 0;
+    return NS_OK;
+  }
+
+  *aStepExposureCompensation = atof(value);
+  return NS_OK;
+}
+
+/* readonly attribute long maxMeteringAreas; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetMaxMeteringAreas(JSContext* cx, PRInt32* aMaxMeteringAreas)
+{
+  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+
+  const char* value = mCamera->GetParameter(CameraParameters::KEY_MAX_NUM_METERING_AREAS);
+  if (!value) {
+    // in case we get nonsense data back
+    *aMaxMeteringAreas = 0;
+    return NS_OK;
+  }
+
+  *aMaxMeteringAreas = atoi(value);
+  return NS_OK;
+}
+
+/* readonly attribute jsval zoomRatios; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetZoomRatios(JSContext* cx, JS::Value* aZoomRatios)
+{
+  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+
+  const char* value = mCamera->GetParameter(CameraParameters::KEY_ZOOM_SUPPORTED);
+  if (!value || strcmp(value, CameraParameters::TRUE) != 0) {
+    // if zoom is not supported, return a null object
+    *aZoomRatios = JSVAL_NULL;
+    return NS_OK;
+  }
+
+  JSObject* array;
+
+  nsresult rv = ParameterListToNewArray(cx, &array, CameraParameters::KEY_ZOOM_RATIOS, ParseZoomRatioItemAndAdd);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aZoomRatios = OBJECT_TO_JSVAL(array);
+  return NS_OK;
+}
+
+/* readonly attribute jsval videoSizes; */
+NS_IMETHODIMP
+nsCameraCapabilities::GetVideoSizes(JSContext* cx, JS::Value* aVideoSizes)
+{
+  return DimensionListToNewObject(cx, aVideoSizes, CameraParameters::KEY_SUPPORTED_VIDEO_SIZES);
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/GonkCameraControl.cpp
@@ -0,0 +1,602 @@
+/*
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+#include "libcameraservice/CameraHardwareInterface.h"
+#include "camera/CameraParameters.h"
+#include "nsCOMPtr.h"
+#include "nsDOMClassInfo.h"
+#include "nsMemory.h"
+#include "jsapi.h"
+#include "nsThread.h"
+#include "nsPrintfCString.h"
+#include "DOMCameraManager.h"
+#include "GonkCameraHwMgr.h"
+#include "CameraCapabilities.h"
+#include "GonkCameraControl.h"
+#include "GonkCameraPreview.h"
+
+#define DOM_CAMERA_LOG_LEVEL  3
+#include "CameraCommon.h"
+
+using namespace mozilla;
+
+static const char* getKeyText(PRUint32 aKey)
+{
+  switch (aKey) {
+    case nsCameraControl::CAMERA_PARAM_EFFECT:
+      return CameraParameters::KEY_EFFECT;
+    case nsCameraControl::CAMERA_PARAM_WHITEBALANCE:
+      return CameraParameters::KEY_WHITE_BALANCE;
+    case nsCameraControl::CAMERA_PARAM_SCENEMODE:
+      return CameraParameters::KEY_SCENE_MODE;
+    case nsCameraControl::CAMERA_PARAM_FLASHMODE:
+      return CameraParameters::KEY_FLASH_MODE;
+    case nsCameraControl::CAMERA_PARAM_FOCUSMODE:
+      return CameraParameters::KEY_FOCUS_MODE;
+    case nsCameraControl::CAMERA_PARAM_ZOOM:
+      return CameraParameters::KEY_ZOOM;
+    case nsCameraControl::CAMERA_PARAM_METERINGAREAS:
+      return CameraParameters::KEY_METERING_AREAS;
+    case nsCameraControl::CAMERA_PARAM_FOCUSAREAS:
+      return CameraParameters::KEY_FOCUS_AREAS;
+    case nsCameraControl::CAMERA_PARAM_FOCALLENGTH:
+      return CameraParameters::KEY_FOCAL_LENGTH;
+    case nsCameraControl::CAMERA_PARAM_FOCUSDISTANCENEAR:
+      return CameraParameters::KEY_FOCUS_DISTANCES;
+    case nsCameraControl::CAMERA_PARAM_FOCUSDISTANCEOPTIMUM:
+      return CameraParameters::KEY_FOCUS_DISTANCES;
+    case nsCameraControl::CAMERA_PARAM_FOCUSDISTANCEFAR:
+      return CameraParameters::KEY_FOCUS_DISTANCES;
+    case nsCameraControl::CAMERA_PARAM_EXPOSURECOMPENSATION:
+      return CameraParameters::KEY_EXPOSURE_COMPENSATION;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_PREVIEWSIZES:
+      return CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_VIDEOSIZES:
+      return CameraParameters::KEY_SUPPORTED_VIDEO_SIZES;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_PICTURESIZES:
+      return CameraParameters::KEY_SUPPORTED_PICTURE_SIZES;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_PICTUREFORMATS:
+      return CameraParameters::KEY_SUPPORTED_PICTURE_FORMATS;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_WHITEBALANCES:
+      return CameraParameters::KEY_SUPPORTED_WHITE_BALANCE;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_SCENEMODES:
+      return CameraParameters::KEY_SUPPORTED_SCENE_MODES;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_EFFECTS:
+      return CameraParameters::KEY_SUPPORTED_EFFECTS;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_FLASHMODES:
+      return CameraParameters::KEY_SUPPORTED_FLASH_MODES;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_FOCUSMODES:
+      return CameraParameters::KEY_SUPPORTED_FOCUS_MODES;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS:
+      return CameraParameters::KEY_MAX_NUM_FOCUS_AREAS;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS:
+      return CameraParameters::KEY_MAX_NUM_METERING_AREAS;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION:
+      return CameraParameters::KEY_MIN_EXPOSURE_COMPENSATION;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION:
+      return CameraParameters::KEY_MAX_EXPOSURE_COMPENSATION;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP:
+      return CameraParameters::KEY_EXPOSURE_COMPENSATION_STEP;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_ZOOM:
+      return CameraParameters::KEY_ZOOM_SUPPORTED;
+    case nsCameraControl::CAMERA_PARAM_SUPPORTED_ZOOMRATIOS:
+      return CameraParameters::KEY_ZOOM_RATIOS;
+    default:
+      return nullptr;
+  }
+}
+
+// Gonk-specific CameraControl implementation.
+
+nsGonkCameraControl::nsGonkCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread)
+  : nsCameraControl(aCameraId, aCameraThread)
+  , mHwHandle(0)
+  , mExposureCompensationMin(0.0)
+  , mExposureCompensationStep(0.0)
+  , mDeferConfigUpdate(false)
+{
+  // Constructor runs on the camera thread--see DOMCameraManager.cpp::GetCameraImpl().
+  DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+  mHwHandle = GonkCameraHardware::GetHandle(this, mCameraId);
+  DOM_CAMERA_LOGI("%s:%d : this = %p, mHwHandle = %d\n", __func__, __LINE__, this, mHwHandle);
+
+  // Initialize our camera configuration database.
+  mRwLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "GonkCameraControl.Parameters.Lock");
+  PullParametersImpl(nullptr);
+
+  // Grab any settings we'll need later.
+  mExposureCompensationMin = mParams.getFloat(CameraParameters::KEY_MIN_EXPOSURE_COMPENSATION);
+  mExposureCompensationStep = mParams.getFloat(CameraParameters::KEY_EXPOSURE_COMPENSATION_STEP);
+  mMaxMeteringAreas = mParams.getInt(CameraParameters::KEY_MAX_NUM_METERING_AREAS);
+  mMaxFocusAreas = mParams.getInt(CameraParameters::KEY_MAX_NUM_FOCUS_AREAS);
+
+  DOM_CAMERA_LOGI("minimum exposure compensation = %f\n", mExposureCompensationMin);
+  DOM_CAMERA_LOGI("exposure compensation step = %f\n", mExposureCompensationStep);
+  DOM_CAMERA_LOGI("maximum metering areas = %d\n", mMaxMeteringAreas);
+  DOM_CAMERA_LOGI("maximum focus areas = %d\n", mMaxFocusAreas);
+}
+
+nsGonkCameraControl::~nsGonkCameraControl()
+{
+  DOM_CAMERA_LOGI("%s:%d : this = %p, mHwHandle = %d\n", __func__, __LINE__, this, mHwHandle);
+  GonkCameraHardware::ReleaseHandle(mHwHandle);
+  if (mRwLock) {
+    PRRWLock* lock = mRwLock;
+    mRwLock = nullptr;
+    PR_DestroyRWLock(lock);
+  }
+
+  DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+}
+
+class RwAutoLockRead
+{
+public:
+  RwAutoLockRead(PRRWLock* aRwLock)
+    : mRwLock(aRwLock)
+  {
+    PR_RWLock_Rlock(mRwLock);
+  }
+
+  ~RwAutoLockRead()
+  {
+    PR_RWLock_Unlock(mRwLock);
+  }
+
+protected:
+  PRRWLock* mRwLock;
+};
+
+class RwAutoLockWrite
+{
+public:
+  RwAutoLockWrite(PRRWLock* aRwLock)
+    : mRwLock(aRwLock)
+  {
+    PR_RWLock_Wlock(mRwLock);
+  }
+
+  ~RwAutoLockWrite()
+  {
+    PR_RWLock_Unlock(mRwLock);
+  }
+
+protected:
+  PRRWLock* mRwLock;
+};
+
+const char*
+nsGonkCameraControl::GetParameter(const char* aKey)
+{
+  RwAutoLockRead lock(mRwLock);
+  return mParams.get(aKey);
+}
+
+const char*
+nsGonkCameraControl::GetParameterConstChar(PRUint32 aKey)
+{
+  const char* key = getKeyText(aKey);
+  if (!key) {
+    return nullptr;
+  }
+
+  RwAutoLockRead lock(mRwLock);
+  return mParams.get(key);
+}
+
+double
+nsGonkCameraControl::GetParameterDouble(PRUint32 aKey)
+{
+  double val;
+  int index = 0;
+  double focusDistance[3];
+  const char* s;
+
+  const char* key = getKeyText(aKey);
+  if (!key) {
+    // return 1x when zooming is not supported
+    return aKey == CAMERA_PARAM_ZOOM ? 1.0 : 0.0;
+  }
+
+  RwAutoLockRead lock(mRwLock);
+  switch (aKey) {
+    case CAMERA_PARAM_ZOOM:
+      val = mParams.getInt(key);
+      return val / 100;
+
+    /**
+     * The gonk camera parameters API only exposes one focus distance property
+     * that contains "Near,Optimum,Far" distances, in metres, where 'Far' may
+     * be 'Infinity'.
+     */
+    case CAMERA_PARAM_FOCUSDISTANCEFAR:
+      ++index;
+      // intentional fallthrough
+
+    case CAMERA_PARAM_FOCUSDISTANCEOPTIMUM:
+      ++index;
+      // intentional fallthrough
+
+    case CAMERA_PARAM_FOCUSDISTANCENEAR:
+      s = mParams.get(key);
+      if (sscanf(s, "%lf,%lf,%lf", &focusDistance[0], &focusDistance[1], &focusDistance[2]) == 3) {
+        return focusDistance[index];
+      }
+      return 0.0;
+
+    case CAMERA_PARAM_EXPOSURECOMPENSATION:
+      index = mParams.getInt(key);
+      if (!index) {
+        // NaN indicates automatic exposure compensation
+        return NAN;
+      }
+      val = (index - 1) * mExposureCompensationStep + mExposureCompensationMin;
+      DOM_CAMERA_LOGI("index = %d --> compensation = %f\n", index, val);
+      return val;
+
+    default:
+      return mParams.getFloat(key);
+  }
+}
+
+void
+nsGonkCameraControl::GetParameter(PRUint32 aKey, nsTArray<CameraRegion>& aRegions)
+{
+  aRegions.Clear();
+
+  const char* key = getKeyText(aKey);
+  if (!key) {
+    return;
+  }
+
+  RwAutoLockRead lock(mRwLock);
+
+  const char* value = mParams.get(key);
+  DOM_CAMERA_LOGI("key='%s' --> value='%s'\n", key, value);
+  if (!value) {
+    return;
+  }
+
+  const char* p = value;
+  PRUint32 count = 1;
+
+  // count the number of regions in the string
+  while ((p = strstr(p, "),("))) {
+    ++count;
+    p += 3;
+  }
+
+  aRegions.SetCapacity(count);
+  CameraRegion* r;
+
+  // parse all of the region sets
+  PRUint32 i;
+  for (i = 0, p = value; p && i < count; ++i, p = strchr(p + 1, '(')) {
+    r = aRegions.AppendElement();
+    if (sscanf(p, "(%d,%d,%d,%d,%u)", &r->top, &r->left, &r->bottom, &r->right, &r->weight) != 5) {
+      DOM_CAMERA_LOGE("%s:%d : region tuple has bad format: '%s'\n", __func__, __LINE__, p);
+      goto GetParameter_error;
+    }
+  }
+
+  return;
+
+GetParameter_error:
+  aRegions.Clear();
+}
+
+void
+nsGonkCameraControl::PushParameters()
+{
+  if (!mDeferConfigUpdate) {
+    DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+    /**
+     * If we're already on the camera thread, call PushParametersImpl()
+     * directly, so that it executes synchronously.  Some callers
+     * require this so that changes take effect immediately before
+     * we can proceed.
+     */
+    if (NS_IsMainThread()) {
+      nsCOMPtr<nsIRunnable> pushParametersTask = new PushParametersTask(this);
+      mCameraThread->Dispatch(pushParametersTask, NS_DISPATCH_NORMAL);
+    } else {
+      PushParametersImpl(nullptr);
+    }
+  }
+}
+
+void
+nsGonkCameraControl::SetParameter(const char* aKey, const char* aValue)
+{
+  {
+    RwAutoLockWrite lock(mRwLock);
+    mParams.set(aKey, aValue);
+  }
+  PushParameters();
+}
+
+void
+nsGonkCameraControl::SetParameter(PRUint32 aKey, const char* aValue)
+{
+  const char* key = getKeyText(aKey);
+  if (!key) {
+    return;
+  }
+
+  {
+    RwAutoLockWrite lock(mRwLock);
+    mParams.set(key, aValue);
+  }
+  PushParameters();
+}
+
+void
+nsGonkCameraControl::SetParameter(PRUint32 aKey, double aValue)
+{
+  PRUint32 index;
+
+  const char* key = getKeyText(aKey);
+  if (!key) {
+    return;
+  }
+
+  {
+    RwAutoLockWrite lock(mRwLock);
+    if (aKey == CAMERA_PARAM_EXPOSURECOMPENSATION) {
+      /**
+       * Convert from real value to a Gonk index, round
+       * to the nearest step; index is 1-based.
+       */
+      index = (aValue - mExposureCompensationMin + mExposureCompensationStep / 2) / mExposureCompensationStep + 1;
+      DOM_CAMERA_LOGI("compensation = %f --> index = %d\n", aValue, index);
+      mParams.set(key, index);
+    } else {
+      mParams.setFloat(key, aValue);
+    }
+  }
+  PushParameters();
+}
+
+void
+nsGonkCameraControl::SetParameter(PRUint32 aKey, const nsTArray<CameraRegion>& aRegions)
+{
+  const char* key = getKeyText(aKey);
+  if (!key) {
+    return;
+  }
+
+  PRUint32 length = aRegions.Length();
+
+  if (!length) {
+    // This tells the camera driver to revert to automatic regioning.
+    mParams.set(key, "(0,0,0,0,0)");
+    PushParameters();
+    return;
+  }
+
+  nsCString s;
+
+  for (PRUint32 i = 0; i < length; ++i) {
+    const CameraRegion* r = &aRegions[i];
+    s.AppendPrintf("(%d,%d,%d,%d,%d),", r->top, r->left, r->bottom, r->right, r->weight);
+  }
+
+  // remove the trailing comma
+  s.Trim(",", false, true, true);
+
+  DOM_CAMERA_LOGI("camera region string '%s'\n", s.get());
+
+  {
+    RwAutoLockWrite lock(mRwLock);
+    mParams.set(key, s.get());
+  }
+  PushParameters();
+}
+
+nsresult
+nsGonkCameraControl::GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream)
+{
+  nsCOMPtr<CameraPreview> preview = mPreview;
+  nsresult rv;
+
+  if (!preview) {
+    preview = new GonkCameraPreview(mHwHandle, aGetPreviewStream->mSize.width, aGetPreviewStream->mSize.height);
+    if (!preview) {
+      if (aGetPreviewStream->mOnErrorCb) {
+        rv = NS_DispatchToMainThread(new CameraErrorResult(aGetPreviewStream->mOnErrorCb, NS_LITERAL_STRING("OUT_OF_MEMORY")));
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+  }
+
+  mPreview = preview;
+  return NS_DispatchToMainThread(new GetPreviewStreamResult(preview.get(), aGetPreviewStream->mOnSuccessCb));
+}
+
+nsresult
+nsGonkCameraControl::AutoFocusImpl(AutoFocusTask* aAutoFocus)
+{
+  nsCOMPtr<nsICameraAutoFocusCallback> cb = mAutoFocusOnSuccessCb;
+  if (cb) {
+    /**
+     * We already have a callback, so someone has already
+     * called autoFocus() -- cancel it.
+     */
+    mAutoFocusOnSuccessCb = nullptr;
+    nsCOMPtr<nsICameraErrorCallback> ecb = mAutoFocusOnErrorCb;
+    mAutoFocusOnErrorCb = nullptr;
+    if (ecb) {
+      nsresult rv = NS_DispatchToMainThread(new CameraErrorResult(ecb, NS_LITERAL_STRING("CANCELLED")));
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    GonkCameraHardware::CancelAutoFocus(mHwHandle);
+  }
+
+  mAutoFocusOnSuccessCb = aAutoFocus->mOnSuccessCb;
+  mAutoFocusOnErrorCb = aAutoFocus->mOnErrorCb;
+
+  if (GonkCameraHardware::AutoFocus(mHwHandle) != OK) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+nsresult
+nsGonkCameraControl::TakePictureImpl(TakePictureTask* aTakePicture)
+{
+ nsCOMPtr<nsICameraTakePictureCallback> cb = mTakePictureOnSuccessCb;
+  if (cb) {
+    /**
+     * We already have a callback, so someone has already
+     * called TakePicture() -- cancel it.
+     */
+    mTakePictureOnSuccessCb = nullptr;
+    nsCOMPtr<nsICameraErrorCallback> ecb = mTakePictureOnErrorCb;
+    mTakePictureOnErrorCb = nullptr;
+    if (ecb) {
+      nsresult rv = NS_DispatchToMainThread(new CameraErrorResult(ecb, NS_LITERAL_STRING("CANCELLED")));
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    GonkCameraHardware::CancelTakePicture(mHwHandle);
+  }
+
+  mTakePictureOnSuccessCb = aTakePicture->mOnSuccessCb;
+  mTakePictureOnErrorCb = aTakePicture->mOnErrorCb;
+
+  // batch-update camera configuration
+  mDeferConfigUpdate = true;
+
+  /**
+   * height and width: some drivers are less friendly about getting one of
+   * these set to zero, so if either is not specified, ignore both and go
+   * with current or default settings.
+   */
+  if (aTakePicture->mSize.width && aTakePicture->mSize.height) {
+    nsCString s;
+    s.AppendPrintf("%dx%d", aTakePicture->mSize.width, aTakePicture->mSize.height);
+    DOM_CAMERA_LOGI("setting picture size to '%s'\n", s.get());
+    SetParameter(CameraParameters::KEY_PICTURE_SIZE, s.get());
+  }
+
+  // Picture format -- need to keep it for the callback.
+  mFileFormat = aTakePicture->mFileFormat;
+  SetParameter(CameraParameters::KEY_PICTURE_FORMAT, NS_ConvertUTF16toUTF8(mFileFormat).get());
+
+  // Convert 'rotation' to a positive value from 0..270 degrees, in steps of 90.
+  PRUint32 r = static_cast<PRUint32>(aTakePicture->mRotation);
+  r %= 360;
+  r += 45;
+  r /= 90;
+  r *= 90;
+  DOM_CAMERA_LOGI("setting picture rotation to %d degrees (mapped from %d)\n", r, aTakePicture->mRotation);
+  SetParameter(CameraParameters::KEY_ROTATION, nsPrintfCString("%u", r).get());
+
+  // Add any specified positional information -- don't care if these fail.
+  if (!isnan(aTakePicture->mPosition.latitude)) {
+    DOM_CAMERA_LOGI("setting picture latitude to %lf\n", aTakePicture->mPosition.latitude);
+    SetParameter(CameraParameters::KEY_GPS_LATITUDE, nsPrintfCString("%lf", aTakePicture->mPosition.latitude).get());
+  }
+  if (!isnan(aTakePicture->mPosition.longitude)) {
+    DOM_CAMERA_LOGI("setting picture longitude to %lf\n", aTakePicture->mPosition.longitude);
+    SetParameter(CameraParameters::KEY_GPS_LONGITUDE, nsPrintfCString("%lf", aTakePicture->mPosition.longitude).get());
+  }
+  if (!isnan(aTakePicture->mPosition.altitude)) {
+    DOM_CAMERA_LOGI("setting picture altitude to %lf\n", aTakePicture->mPosition.altitude);
+    SetParameter(CameraParameters::KEY_GPS_ALTITUDE, nsPrintfCString("%lf", aTakePicture->mPosition.altitude).get());
+  }
+  if (!isnan(aTakePicture->mPosition.timestamp)) {
+    DOM_CAMERA_LOGI("setting picture timestamp to %lf\n", aTakePicture->mPosition.timestamp);
+    SetParameter(CameraParameters::KEY_GPS_TIMESTAMP, nsPrintfCString("%lf", aTakePicture->mPosition.timestamp).get());
+  }
+
+  mDeferConfigUpdate = false;
+  PushParameters();
+
+  if (GonkCameraHardware::TakePicture(mHwHandle) != OK) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+nsresult
+nsGonkCameraControl::PushParametersImpl(PushParametersTask* aPushParameters)
+{
+  RwAutoLockRead lock(mRwLock);
+  if (GonkCameraHardware::PushParameters(mHwHandle, mParams) != OK) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsGonkCameraControl::PullParametersImpl(PullParametersTask* aPullParameters)
+{
+  RwAutoLockWrite lock(mRwLock);
+  GonkCameraHardware::PullParameters(mHwHandle, mParams);
+  return NS_OK;
+}
+
+nsresult
+nsGonkCameraControl::StartRecordingImpl(StartRecordingTask* aStartRecording)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsGonkCameraControl::StopRecordingImpl(StopRecordingTask* aStopRecording)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void
+nsGonkCameraControl::ReceiveFrame(PRUint8* aData, PRUint32 aLength)
+{
+  nsCOMPtr<CameraPreview> preview = mPreview;
+
+  if (preview) {
+    GonkCameraPreview* p = static_cast<GonkCameraPreview* >(preview.get());
+    MOZ_ASSERT(p);
+    p->ReceiveFrame(aData, aLength);
+  }
+}
+
+// Gonk callback handlers.
+namespace mozilla {
+
+void
+ReceiveImage(nsGonkCameraControl* gc, PRUint8* aData, PRUint32 aLength)
+{
+  gc->TakePictureComplete(aData, aLength);
+}
+
+void
+AutoFocusComplete(nsGonkCameraControl* gc, bool success)
+{
+  gc->AutoFocusComplete(success);
+}
+
+void
+ReceiveFrame(nsGonkCameraControl* gc, PRUint8* aData, PRUint32 aLength)
+{
+  gc->ReceiveFrame(aData, aLength);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/camera/GonkCameraControl.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef DOM_CAMERA_GONKCAMERACONTROL_H
+#define DOM_CAMERA_GONKCAMERACONTROL_H
+
+#include "prtypes.h"
+#include "prrwlock.h"
+#include "CameraControl.h"
+
+#define DOM_CAMERA_LOG_LEVEL  3
+#include "CameraCommon.h"
+
+namespace mozilla {
+
+class nsGonkCameraControl : public nsCameraControl
+{
+public:
+  nsGonkCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread);
+
+  const char* GetParameter(const char* aKey);
+  const char* GetParameterConstChar(PRUint32 aKey);
+  double GetParameterDouble(PRUint32 aKey);
+  void GetParameter(PRUint32 aKey, nsTArray<dom::CameraRegion>& aRegions);
+  void SetParameter(const char* aKey, const char* aValue);
+  void SetParameter(PRUint32 aKey, const char* aValue);
+  void SetParameter(PRUint32 aKey, double aValue);
+  void SetParameter(PRUint32 aKey, const nsTArray<dom::CameraRegion>& aRegions);
+  void PushParameters();
+
+  void ReceiveFrame(PRUint8 *aData, PRUint32 aLength);
+
+protected:
+  ~nsGonkCameraControl();
+
+  nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream);
+  nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus);
+  nsresult TakePictureImpl(TakePictureTask* aTakePicture);
+  nsresult StartRecordingImpl(StartRecordingTask* aStartRecording);
+  nsresult StopRecordingImpl(StopRecordingTask* aStopRecording);
+  nsresult PushParametersImpl(PushParametersTask* aPushParameters);
+  nsresult PullParametersImpl(PullParametersTask* aPullParameters);
+
+  PRUint32                  mHwHandle;
+  double                    mExposureCompensationMin;
+  double                    mExposureCompensationStep;
+  bool                      mDeferConfigUpdate;
+  PRRWLock*                 mRwLock;
+  android::CameraParameters mParams;
+
+private:
+  nsGonkCameraControl(const nsGonkCameraControl&) MOZ_DELETE;
+  nsGonkCameraControl& operator=(const nsGonkCameraControl&) MOZ_DELETE;
+};
+
+// camera driver callbacks
+void ReceiveImage(nsGonkCameraControl* gc, PRUint8* aData, PRUint32 aLength);
+void AutoFocusComplete(nsGonkCameraControl* gc, bool success);
+void ReceiveFrame(nsGonkCameraControl* gc, PRUint8* aData, PRUint32 aLength);
+
+} // namespace mozilla
+
+#endif // DOM_CAMERA_GONKCAMERACONTROL_H
new file mode 100644
--- /dev/null
+++ b/dom/camera/GonkCameraHwMgr.cpp
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "nsDebug.h"
+#include "GonkCameraHwMgr.h"
+#include "GonkNativeWindow.h"
+
+#define DOM_CAMERA_LOG_LEVEL        3
+#include "CameraCommon.h"
+
+using namespace mozilla;
+
+#if GIHM_TIMING_RECEIVEFRAME
+#define INCLUDE_TIME_H                  1
+#endif
+#if GIHM_TIMING_OVERALL
+#define INCLUDE_TIME_H                  1
+#endif
+
+#if INCLUDE_TIME_H
+#include <time.h>
+
+static __inline void timespecSubtract(struct timespec* a, struct timespec* b)
+{
+  // a = b - a
+  if (b->tv_nsec < a->tv_nsec) {
+    b->tv_nsec += 1000000000;
+    b->tv_sec -= 1;
+  }
+  a->tv_nsec = b->tv_nsec - a->tv_nsec;
+  a->tv_sec = b->tv_sec - a->tv_sec;
+}
+#endif
+
+GonkCameraHardware::GonkCameraHardware(GonkCamera* aTarget, PRUint32 aCamera)
+  : mCamera(aCamera)
+  , mFps(30)
+  , mPreviewFormat(PREVIEW_FORMAT_UNKNOWN)
+  , mClosing(false)
+  , mMonitor("GonkCameraHardware.Monitor")
+  , mNumFrames(0)
+  , mTarget(aTarget)
+  , mInitialized(false)
+{
+  DOM_CAMERA_LOGI( "%s: this = %p (aTarget = %p)\n", __func__, (void* )this, (void* )aTarget );
+  init();
+}
+
+// Android data callback
+void
+GonkCameraHardware::DataCallback(int32_t aMsgType, const sp<IMemory> &aDataPtr, camera_frame_metadata_t* aMetadata, void* aUser)
+{
+  GonkCameraHardware* hw = GetHardware((PRUint32)aUser);
+  if (!hw) {
+    DOM_CAMERA_LOGW("%s:aUser = %d resolved to no camera hw\n", __func__, (PRUint32)aUser);
+    return;
+  }
+  if (hw->mClosing) {
+    return;
+  }
+
+  GonkCamera* camera = hw->mTarget;
+  if (camera) {
+    switch (aMsgType) {
+      case CAMERA_MSG_PREVIEW_FRAME:
+        ReceiveFrame(camera, (PRUint8*)aDataPtr->pointer(), aDataPtr->size());
+        break;
+
+      case CAMERA_MSG_COMPRESSED_IMAGE:
+        ReceiveImage(camera, (PRUint8*)aDataPtr->pointer(), aDataPtr->size());
+        break;
+
+      default:
+        DOM_CAMERA_LOGE("Unhandled data callback event %d\n", aMsgType);
+        break;
+    }
+  } else {
+    DOM_CAMERA_LOGW("%s: hw = %p (camera = NULL)\n", __func__, hw);
+  }
+}
+
+// Android notify callback
+void
+GonkCameraHardware::NotifyCallback(int32_t aMsgType, int32_t ext1, int32_t ext2, void* aUser)
+{
+  bool bSuccess;
+  GonkCameraHardware* hw = GetHardware((PRUint32)aUser);
+  if (!hw) {
+    DOM_CAMERA_LOGW("%s:aUser = %d resolved to no camera hw\n", __func__, (PRUint32)aUser);
+    return;
+  }
+  if (hw->mClosing) {
+    return;
+  }
+
+  GonkCamera* camera = hw->mTarget;
+  if (!camera) {
+    return;
+  }
+
+  switch (aMsgType) {
+    case CAMERA_MSG_FOCUS:
+      if (ext1) {
+        DOM_CAMERA_LOGI("Autofocus complete");
+        bSuccess = true;
+      } else {
+        DOM_CAMERA_LOGW("Autofocus failed");
+        bSuccess = false;
+      }
+      AutoFocusComplete(camera, bSuccess);
+      break;
+
+    case CAMERA_MSG_SHUTTER:
+      DOM_CAMERA_LOGW("Shutter event not handled yet\n");
+      break;
+
+    default:
+      DOM_CAMERA_LOGE("Unhandled notify callback event %d\n", aMsgType);
+      break;
+  }
+}
+
+void
+GonkCameraHardware::init()
+{
+  DOM_CAMERA_LOGI("%s: this = %p\n", __func__, (void* )this);
+
+  if (hw_get_module(CAMERA_HARDWARE_MODULE_ID, (const hw_module_t**)&mModule) < 0) {
+    return;
+  }
+  char cameraDeviceName[4];
+  snprintf(cameraDeviceName, sizeof(cameraDeviceName), "%d", mCamera);
+  mHardware = new CameraHardwareInterface(cameraDeviceName);
+  if (mHardware->initialize(&mModule->common) != OK) {
+    mHardware.clear();
+    return;
+  }
+
+  mWindow = new android::GonkNativeWindow();
+
+  if (sHwHandle == 0) {
+    sHwHandle = 1;  // don't use 0
+  }
+  mHardware->setCallbacks(GonkCameraHardware::NotifyCallback, GonkCameraHardware::DataCallback, NULL, (void*)sHwHandle);
+
+  // initialize the local camera parameter database
+  mParams = mHardware->getParameters();
+
+  mHardware->setPreviewWindow(mWindow);
+
+  mInitialized = true;
+}
+
+GonkCameraHardware::~GonkCameraHardware()
+{
+  DOM_CAMERA_LOGI( "%s:%d : this = %p\n", __func__, __LINE__, (void*)this );
+  sHw = nullptr;
+}
+
+GonkCameraHardware* GonkCameraHardware::sHw         = nullptr;
+PRUint32            GonkCameraHardware::sHwHandle   = 0;
+
+void
+GonkCameraHardware::ReleaseHandle(PRUint32 aHwHandle)
+{
+  GonkCameraHardware* hw = GetHardware(aHwHandle);
+  DOM_CAMERA_LOGI("%s: aHwHandle = %d, hw = %p (sHwHandle = %d)\n", __func__, aHwHandle, (void*)hw, sHwHandle);
+  if (!hw) {
+    return;
+  }
+
+  DOM_CAMERA_LOGI("%s: before: sHwHandle = %d\n", __func__, sHwHandle);
+  sHwHandle += 1; // invalidate old handles before deleting
+  hw->mClosing = true;
+  hw->mHardware->disableMsgType(CAMERA_MSG_ALL_MSGS);
+  hw->mHardware->stopPreview();
+  hw->mHardware->release();
+  DOM_CAMERA_LOGI("%s: after: sHwHandle = %d\n", __func__, sHwHandle);
+  delete hw;     // destroy the camera hardware instance
+}
+
+PRUint32
+GonkCameraHardware::GetHandle(GonkCamera* aTarget, PRUint32 aCamera)
+{
+  ReleaseHandle(sHwHandle);
+
+  sHw = new GonkCameraHardware(aTarget, aCamera);
+
+  if (sHw->IsInitialized()) {
+    return sHwHandle;
+  }
+
+  DOM_CAMERA_LOGE("failed to initialize camera hardware\n");
+  delete sHw;
+  sHw = nullptr;
+  return 0;
+}
+
+PRUint32
+GonkCameraHardware::GetFps(PRUint32 aHwHandle)
+{
+  GonkCameraHardware* hw = GetHardware(aHwHandle);
+  if (!hw) {
+    return 0;
+  }
+
+  return hw->mFps;
+}
+
+void
+GonkCameraHardware::GetPreviewSize(PRUint32 aHwHandle, PRUint32* aWidth, PRUint32* aHeight)
+{
+  GonkCameraHardware* hw = GetHardware(aHwHandle);
+  if (hw) {
+    *aWidth = hw->mWidth;
+    *aHeight = hw->mHeight;
+  } else {
+    *aWidth = 0;
+    *aHeight = 0;
+  }
+}
+
+void
+GonkCameraHardware::SetPreviewSize(PRUint32 aWidth, PRUint32 aHeight)
+{
+  Vector<Size> previewSizes;
+  PRUint32 bestWidth = aWidth;
+  PRUint32 bestHeight = aHeight;
+  PRUint32 minSizeDelta = PR_UINT32_MAX;
+  PRUint32 delta;
+  Size size;
+
+  mParams.getSupportedPreviewSizes(previewSizes);
+
+  if (!aWidth && !aHeight) {
+    // no size specified, take the first supported size
+    size = previewSizes[0];
+    bestWidth = size.width;
+    bestHeight = size.height;
+  } else if (aWidth && aHeight) {
+    // both height and width specified, find the supported size closest to requested size
+    for (PRUint32 i = 0; i < previewSizes.size(); i++) {
+      Size size = previewSizes[i];
+      PRUint32 delta = abs((long int)(size.width * size.height - aWidth * aHeight));
+      if (delta < minSizeDelta) {
+        minSizeDelta = delta;
+        bestWidth = size.width;
+        bestHeight = size.height;
+      }
+    }
+  } else if (!aWidth) {
+    // width not specified, find closest height match
+    for (PRUint32 i = 0; i < previewSizes.size(); i++) {
+      size = previewSizes[i];
+      delta = abs((long int)(size.height - aHeight));
+      if (delta < minSizeDelta) {
+        minSizeDelta = delta;
+        bestWidth = size.width;
+        bestHeight = size.height;
+      }
+    }
+  } else if (!aHeight) {
+    // height not specified, find closest width match
+    for (PRUint32 i = 0; i < previewSizes.size(); i++) {
+      size = previewSizes[i];
+      delta = abs((long int)(size.width - aWidth));
+      if (delta < minSizeDelta) {
+        minSizeDelta = delta;
+        bestWidth = size.width;
+        bestHeight = size.height;
+      }
+    }
+  }
+
+  mWidth = bestWidth;
+  mHeight = bestHeight;
+  mParams.setPreviewSize(mWidth, mHeight);
+}
+
+void
+GonkCameraHardware::SetPreviewSize(PRUint32 aHwHandle, PRUint32 aWidth, PRUint32 aHeight)
+{
+  GonkCameraHardware* hw = GetHardware(aHwHandle);
+  if (hw) {
+    hw->SetPreviewSize(aWidth, aHeight);
+  }
+}
+
+int
+GonkCameraHardware::AutoFocus(PRUint32 aHwHandle)
+{
+  DOM_CAMERA_LOGI("%s: aHwHandle = %d\n", __func__, aHwHandle);
+  GonkCameraHardware* hw = GetHardware(aHwHandle);
+  if (!hw) {
+    return DEAD_OBJECT;
+  }
+
+  hw->mHardware->enableMsgType(CAMERA_MSG_FOCUS);
+  return hw->mHardware->autoFocus();
+}
+
+void
+GonkCameraHardware::CancelAutoFocus(PRUint32 aHwHandle)
+{
+  DOM_CAMERA_LOGI("%s: aHwHandle = %d\n", __func__, aHwHandle);
+  GonkCameraHardware* hw = GetHardware(aHwHandle);
+  if (hw) {
+    hw->mHardware->cancelAutoFocus();
+  }
+}
+
+int
+GonkCameraHardware::TakePicture(PRUint32 aHwHandle)
+{
+  GonkCameraHardware* hw = GetHardware(aHwHandle);
+  if (!hw) {
+    return DEAD_OBJECT;
+  }
+
+  hw->mHardware->enableMsgType(CAMERA_MSG_COMPRESSED_IMAGE);
+  return hw->mHardware->takePicture();
+}
+
+void
+GonkCameraHardware::CancelTakePicture(PRUint32 aHwHandle)
+{
+  GonkCameraHardware* hw = GetHardware(aHwHandle);
+  if (hw) {
+    hw->mHardware->cancelPicture();
+  }
+}
+
+int
+GonkCameraHardware::PushParameters(PRUint32 aHwHandle, const CameraParameters& aParams)
+{
+  GonkCameraHardware* hw = GetHardware(aHwHandle);
+  if (!hw) {
+    return DEAD_OBJECT;
+  }
+
+  return hw->mHardware->setParameters(aParams);
+}
+
+void
+GonkCameraHardware::PullParameters(PRUint32 aHwHandle, CameraParameters& aParams)
+{
+  GonkCameraHardware* hw = GetHardware(aHwHandle);
+  if (hw) {
+    aParams = hw->mHardware->getParameters();
+  }
+}
+
+int
+GonkCameraHardware::StartPreview()
+{
+  const char* format;
+
+  mHardware->enableMsgType(CAMERA_MSG_PREVIEW_FRAME);
+
+  DOM_CAMERA_LOGI("Preview formats: %s\n", mParams.get(mParams.KEY_SUPPORTED_PREVIEW_FORMATS));
+
+  // try to set preferred image format and frame rate
+  const char* const PREVIEW_FORMAT = "yuv420p";
+  const char* const BAD_PREVIEW_FORMAT = "yuv420sp";
+  mParams.setPreviewFormat(PREVIEW_FORMAT);
+  mParams.setPreviewFrameRate(mFps);
+  mHardware->setParameters(mParams);
+
+  // check that our settings stuck
+  mParams = mHardware->getParameters();
+  format = mParams.getPreviewFormat();
+  if (strcmp(format, PREVIEW_FORMAT) == 0) {
+    mPreviewFormat = PREVIEW_FORMAT_YUV420P;  /* \o/ */
+  } else if (strcmp(format, BAD_PREVIEW_FORMAT) == 0) {
+    mPreviewFormat = PREVIEW_FORMAT_YUV420SP;
+    DOM_CAMERA_LOGA("Camera ignored our request for '%s' preview, will have to convert (from %d)\n", PREVIEW_FORMAT, mPreviewFormat);
+  } else {
+    mPreviewFormat = PREVIEW_FORMAT_UNKNOWN;
+    DOM_CAMERA_LOGE("Camera ignored our request for '%s' preview, returned UNSUPPORTED format '%s'\n", PREVIEW_FORMAT, format);
+  }
+
+  // Check the frame rate and log if the camera ignored our setting
+  PRUint32 fps = mParams.getPreviewFrameRate();
+  if (fps != mFps) {
+    DOM_CAMERA_LOGA("We asked for %d fps but camera returned %d fps, using it", mFps, fps);
+    mFps = fps;
+  }
+
+  return mHardware->startPreview();
+}
+
+int
+GonkCameraHardware::StartPreview(PRUint32 aHwHandle)
+{
+  GonkCameraHardware* hw = GetHardware(aHwHandle);
+  DOM_CAMERA_LOGI("%s:%d : aHwHandle = %d, hw = %p\n", __func__, __LINE__, aHwHandle, hw);
+  if (!hw) {
+    return DEAD_OBJECT;
+  }
+
+  return hw->StartPreview();
+}
+
+void
+GonkCameraHardware::StopPreview(PRUint32 aHwHandle)
+{
+  GonkCameraHardware* hw = GetHardware(aHwHandle);
+  if (hw) {
+    hw->mHardware->stopPreview();
+  }
+}
+
+PRUint32
+GonkCameraHardware::GetPreviewFormat(PRUint32 aHwHandle)
+{
+  GonkCameraHardware* hw = GetHardware(aHwHandle);
+  if (!hw) {
+    return PREVIEW_FORMAT_UNKNOWN;
+  }
+
+  return hw->mPreviewFormat;
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/GonkCameraHwMgr.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef DOM_CAMERA_GONKCAMERAHWMGR_H
+#define DOM_CAMERA_GONKCAMERAHWMGR_H
+
+#include "libcameraservice/CameraHardwareInterface.h"
+#include "binder/IMemory.h"
+#include "mozilla/ReentrantMonitor.h"
+
+#include "GonkCameraControl.h"
+
+#define DOM_CAMERA_LOG_LEVEL  3
+#include "CameraCommon.h"
+
+// config
+#define GIHM_TIMING_RECEIVEFRAME    0
+#define GIHM_TIMING_OVERALL         1
+
+using namespace mozilla;
+using namespace android;
+
+namespace mozilla {
+
+typedef class nsGonkCameraControl GonkCamera;
+
+class GonkCameraHardware
+{
+protected:
+  GonkCameraHardware(GonkCamera* aTarget, PRUint32 aCamera);
+  ~GonkCameraHardware();
+  void init();
+
+  static void                   DataCallback(int32_t aMsgType, const sp<IMemory> &aDataPtr, camera_frame_metadata_t* aMetadata, void* aUser);
+  static void                   NotifyCallback(int32_t aMsgType, int32_t ext1, int32_t ext2, void* aUser);
+
+public:
+  static void                   ReleaseHandle(PRUint32 aHwHandle);
+  static PRUint32               GetHandle(GonkCamera* aTarget, PRUint32 aCamera);
+  static PRUint32               GetFps(PRUint32 aHwHandle);
+  static void                   GetPreviewSize(PRUint32 aHwHandle, PRUint32* aWidth, PRUint32* aHeight);
+  static void                   SetPreviewSize(PRUint32 aHwHandle, PRUint32 aWidth, PRUint32 aHeight);
+  static int                    AutoFocus(PRUint32 aHwHandle);
+  static void                   CancelAutoFocus(PRUint32 aHwHandle);
+  static int                    TakePicture(PRUint32 aHwHandle);
+  static void                   CancelTakePicture(PRUint32 aHwHandle);
+  static int                    StartPreview(PRUint32 aHwHandle);
+  static void                   StopPreview(PRUint32 aHwHandle);
+  static int                    PushParameters(PRUint32 aHwHandle, const CameraParameters& aParams);
+  static void                   PullParameters(PRUint32 aHwHandle, CameraParameters& aParams);
+
+  enum {
+    PREVIEW_FORMAT_UNKNOWN,
+    PREVIEW_FORMAT_YUV420P,
+    PREVIEW_FORMAT_YUV420SP
+  };
+  // GetPreviewFormat() MUST be called only after StartPreview().
+  static PRUint32               GetPreviewFormat(PRUint32 aHwHandle);
+
+protected:
+  static GonkCameraHardware*    sHw;
+  static PRUint32               sHwHandle;
+
+  static GonkCameraHardware*    GetHardware(PRUint32 aHwHandle)
+  {
+    if (aHwHandle == sHwHandle) {
+      /**
+       * In the initial case, sHw will be null and sHwHandle will be 0,
+       * so even if this function is called with aHwHandle = 0, the
+       * result will still be null.
+       */
+      return sHw;
+    }
+    return nullptr;
+  }
+
+  // Instance wrappers to make member function access easier.
+  void SetPreviewSize(PRUint32 aWidth, PRUint32 aHeight);
+  int StartPreview();
+
+  PRUint32                      mCamera;
+  PRUint32                      mWidth;
+  PRUint32                      mHeight;
+  PRUint32                      mFps;
+  PRUint32                      mPreviewFormat;
+  bool                          mClosing;
+  mozilla::ReentrantMonitor     mMonitor;
+  PRUint32                      mNumFrames;
+  sp<CameraHardwareInterface>   mHardware;
+  GonkCamera*                   mTarget;
+  camera_module_t*              mModule;
+  sp<ANativeWindow>             mWindow;
+  CameraParameters              mParams;
+#if GIHM_TIMING_OVERALL
+  struct timespec               mStart;
+  struct timespec               mAutoFocusStart;
+#endif
+  bool                          mInitialized;
+
+  bool IsInitialized()
+  {
+    return mInitialized;
+  }
+
+private:
+  GonkCameraHardware(const GonkCameraHardware&) MOZ_DELETE;
+  GonkCameraHardware& operator=(const GonkCameraHardware&) MOZ_DELETE;
+};
+
+} // namespace mozilla
+
+#endif // GONK_IMPL_HW_MGR_H
new file mode 100644
--- /dev/null
+++ b/dom/camera/GonkCameraManager.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jsapi.h"
+#include "libcameraservice/CameraHardwareInterface.h"
+#include "GonkCameraControl.h"
+#include "DOMCameraManager.h"
+
+#define DOM_CAMERA_LOG_LEVEL  3
+#include "CameraCommon.h"
+
+// From nsDOMCameraManager, but gonk-specific!
+
+/* [implicit_jscontext] jsval getListOfCameras (); */
+NS_IMETHODIMP
+nsDOMCameraManager::GetListOfCameras(JSContext* cx, JS::Value* _retval)
+{
+  JSObject* a = JS_NewArrayObject(cx, 0, nullptr);
+  camera_module_t* module;
+  PRUint32 index = 0;
+  PRUint32 count;
+
+  if (!a) {
+    DOM_CAMERA_LOGE("getListOfCameras : Could not create array object");
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  if (hw_get_module(CAMERA_HARDWARE_MODULE_ID, (const hw_module_t**)&module) < 0) {
+    DOM_CAMERA_LOGE("getListOfCameras : Could not load camera HAL module");
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  count = module->get_number_of_cameras();
+  DOM_CAMERA_LOGI("getListOfCameras : get_number_of_cameras() returned %d\n", count);
+  while (count--) {
+    struct camera_info info;
+    int rv = module->get_camera_info(count, &info);
+    if (rv != 0) {
+      DOM_CAMERA_LOGE("getListOfCameras : get_camera_info(%d) failed: %d\n", count, rv);
+      continue;
+    }
+
+    JSString* v;
+    jsval jv;
+
+    switch (info.facing) {
+      case CAMERA_FACING_BACK:
+        v = JS_NewStringCopyZ(cx, "back");
+        index = 0;
+        break;
+
+      case CAMERA_FACING_FRONT:
+        v = JS_NewStringCopyZ(cx, "front");
+        index = 1;
+        break;
+
+      default:
+        // TODO: handle extra cameras in getCamera().
+        {
+          static PRUint32 extraIndex = 2;
+          nsCString s;
+          s.AppendPrintf("extra-camera-%d", count);
+          v = JS_NewStringCopyZ(cx, s.get());
+          index = extraIndex++;
+        }
+        break;
+    }
+    if (!v) {
+      DOM_CAMERA_LOGE("getListOfCameras : out of memory populating camera list");
+      delete a;
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+    jv = STRING_TO_JSVAL(v);
+    if (!JS_SetElement(cx, a, index, &jv)) {
+      DOM_CAMERA_LOGE("getListOfCameras : failed building list of cameras");
+      delete a;
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+  }
+
+  *_retval = OBJECT_TO_JSVAL(a);
+  return NS_OK;
+}
+
+using namespace mozilla;
+
+NS_IMETHODIMP
+GetCameraTask::Run()
+{
+  nsCOMPtr<nsICameraControl> cameraControl = new nsGonkCameraControl(mCameraId, mCameraThread);
+
+  DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+
+  return NS_DispatchToMainThread(new GetCameraResult(cameraControl, mOnSuccessCb));
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/GonkCameraPreview.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "VideoUtils.h"
+#include "GonkCameraHwMgr.h"
+#include "GonkCameraPreview.h"
+
+#define DOM_CAMERA_LOG_LEVEL  2
+#include "CameraCommon.h"
+
+using namespace mozilla;
+
+/**
+ * This big macro takes two 32-bit input blocks of interlaced u and
+ * v data (from a yuv420sp frame) in 's0' and 's1', and deinterlaces
+ * them into pairs of contiguous 32-bit blocks, the u plane data in
+ * 'u', and the v plane data in 'v' (i.e. for a yuv420p frame).
+ *
+ * yuv420sp:
+ *  [ y-data ][ uv-data ]
+ *    [ uv-data ]: [u0][v0][u1][v1][u2][v2]...
+ *
+ * yuv420p:
+ *  [ y-data ][ u-data ][ v-data ]
+ *    [ u-data ]: [u0][u1][u2]...
+ *    [ v-data ]: [v0][v1][v2]...
+ *
+ * Doing this in 32-bit blocks is significantly faster than using
+ * byte-wise operations on ARM.  (In some cases, the byte-wise
+ * de-interlacing can be too slow to keep up with the preview frames
+ * coming from the driver.
+ */
+#define DEINTERLACE( u, v, s0, s1 )                             \
+  u = ( (s0) & 0xFF00UL ) >> 8 | ( (s0) & 0xFF000000UL ) >> 16; \
+  u |= ( (s1) & 0xFF00UL ) << 8 | ( (s1) & 0xFF000000UL );      \
+  v = ( (s0) & 0xFFUL ) | ( (s0) & 0xFF0000UL ) >> 8;           \
+  v |= ( (s1) & 0xFFUL ) << 16 | ( (s1) & 0xFF0000UL ) << 8;
+
+void
+GonkCameraPreview::ReceiveFrame(PRUint8 *aData, PRUint32 aLength)
+{
+  DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this);
+
+  if (mInput->HaveEnoughBuffered(TRACK_VIDEO)) {
+    if (mDiscardedFrameCount == 0) {
+      DOM_CAMERA_LOGI("mInput has enough data buffered, starting to discard\n");
+    }
+    ++mDiscardedFrameCount;
+    return;
+  } else if (mDiscardedFrameCount) {
+    DOM_CAMERA_LOGI("mInput needs more data again; discarded %d frames in a row\n", mDiscardedFrameCount);
+    mDiscardedFrameCount = 0;
+  }
+
+  switch (mFormat) {
+    case GonkCameraHardware::PREVIEW_FORMAT_YUV420SP:
+      {
+        // de-interlace the u and v planes
+        uint8_t* y = aData;
+        uint32_t yN = mWidth * mHeight;
+
+        NS_ASSERTION(yN & 0x3 == 0, "Invalid image dimensions!");
+
+        uint32_t uvN = yN / 4;
+        uint32_t* src = (uint32_t*)( y + yN );
+        uint32_t* d = new uint32_t[ uvN / 2 ];
+        uint32_t* u = d;
+        uint32_t* v = u + uvN / 4;
+
+        // we're handling pairs of 32-bit words, so divide by 8
+        NS_ASSERTION(uvN & 0x7 == 0, "Invalid image dimensions!");
+        uvN /= 8;
+
+        while (uvN--) {
+          uint32_t src0 = *src++;
+          uint32_t src1 = *src++;
+
+          uint32_t u0;
+          uint32_t v0;
+          uint32_t u1;
+          uint32_t v1;
+
+          DEINTERLACE( u0, v0, src0, src1 );
+
+          src0 = *src++;
+          src1 = *src++;
+
+          DEINTERLACE( u1, v1, src0, src1 );
+
+          *u++ = u0;
+          *u++ = u1;
+          *v++ = v0;
+          *v++ = v1;
+        }
+
+        memcpy(y + yN, d, yN / 2);
+        delete[] d;
+      }
+      break;
+
+    case GonkCameraHardware::PREVIEW_FORMAT_YUV420P:
+      // no transformating required
+      break;
+
+    default:
+      // in a format we don't handle, get out of here
+      return;
+  }
+
+  Image::Format format = Image::PLANAR_YCBCR;
+  nsRefPtr<Image> image = mImageContainer->CreateImage(&format, 1);
+  image->AddRef();
+  PlanarYCbCrImage* videoImage = static_cast<PlanarYCbCrImage*>(image.get());
+
+  /**
+   * If you change either lumaBpp or chromaBpp, make sure the
+   * assertions below still hold.
+   */
+  const PRUint8 lumaBpp = 8;
+  const PRUint8 chromaBpp = 4;
+  PlanarYCbCrImage::Data data;
+  data.mYChannel = aData;
+  data.mYSize = gfxIntSize(mWidth, mHeight);
+
+  data.mYStride = mWidth * lumaBpp;
+  NS_ASSERTION(data.mYStride & 0x7 == 0, "Invalid image dimensions!");
+  data.mYStride /= 8;
+
+  data.mCbCrStride = mWidth * chromaBpp;
+  NS_ASSERTION(data.mCbCrStride & 0x7 == 0, "Invalid image dimensions!");
+  data.mCbCrStride /= 8;
+
+  data.mCbChannel = aData + mHeight * data.mYStride;
+  data.mCrChannel = data.mCbChannel + mHeight * data.mCbCrStride / 2;
+  data.mCbCrSize = gfxIntSize(mWidth / 2, mHeight / 2);
+  data.mPicX = 0;
+  data.mPicY = 0;
+  data.mPicSize = gfxIntSize(mWidth, mHeight);
+  data.mStereoMode = mozilla::layers::STEREO_MODE_MONO;
+  videoImage->SetData(data); // copies buffer
+
+  mVideoSegment.AppendFrame(videoImage, 1, gfxIntSize(mWidth, mHeight));
+  mInput->AppendToTrack(TRACK_VIDEO, &mVideoSegment);
+
+  mFrameCount += 1;
+
+  if ((mFrameCount % 10) == 0) {
+    DOM_CAMERA_LOGI("%s:%d : mFrameCount = %d\n", __func__, __LINE__, mFrameCount);
+  }
+}
+
+void
+GonkCameraPreview::Start()
+{
+  DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this);
+
+  /**
+   * We set and then immediately get the preview size, in case the camera
+   * driver has decided to ignore our given dimensions.  We need to know
+   * the dimensions the driver is using so that, if needed, we can properly
+   * de-interlace the yuv420sp format in ReceiveFrame() above.
+   */
+  GonkCameraHardware::SetPreviewSize(mHwHandle, mWidth, mHeight);
+  GonkCameraHardware::GetPreviewSize(mHwHandle, &mWidth, &mHeight);
+  SetFrameRate(GonkCameraHardware::GetFps(mHwHandle));
+
+  if (GonkCameraHardware::StartPreview(mHwHandle) == OK) {
+    // GetPreviewFormat() must be called after StartPreview().
+    mFormat = GonkCameraHardware::GetPreviewFormat(mHwHandle);
+    DOM_CAMERA_LOGI("preview stream is (actually!) %d x %d (w x h), %d frames per second, format %d\n", mWidth, mHeight, mFramesPerSecond, mFormat);
+  } else {
+    DOM_CAMERA_LOGE("%s: failed to start preview\n", __func__);
+  }
+}
+
+void
+GonkCameraPreview::Stop()
+{
+  DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this);
+
+  GonkCameraHardware::StopPreview(mHwHandle);
+  mInput->EndTrack(TRACK_VIDEO);
+  mInput->Finish();
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/GonkCameraPreview.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef DOM_CAMERA_GONKCAMERAPREVIEW_H
+#define DOM_CAMERA_GONKCAMERAPREVIEW_H
+
+#include "CameraPreview.h"
+
+#define DOM_CAMERA_LOG_LEVEL  3
+#include "CameraCommon.h"
+
+namespace mozilla {
+
+class GonkCameraPreview : public CameraPreview
+{
+public:
+  GonkCameraPreview(PRUint32 aHwHandle, PRUint32 aWidth, PRUint32 aHeight)
+    : CameraPreview(aWidth, aHeight)
+    , mHwHandle(aHwHandle)
+    , mDiscardedFrameCount(0)
+    , mFormat(GonkCameraHardware::PREVIEW_FORMAT_UNKNOWN)
+  { }
+
+  void ReceiveFrame(PRUint8 *aData, PRUint32 aLength);
+
+  void Start();
+  void Stop();
+
+protected:
+  ~GonkCameraPreview()
+  {
+    Stop();
+  }
+
+  PRUint32 mHwHandle;
+  PRUint32 mDiscardedFrameCount;
+  PRUint32 mFormat;
+
+private:
+  GonkCameraPreview(const GonkCameraPreview&) MOZ_DELETE;
+  GonkCameraPreview& operator=(const GonkCameraPreview&) MOZ_DELETE;
+};
+
+} // namespace mozilla
+
+#endif // DOM_CAMERA_GONKCAMERAPREVIEW_H
new file mode 100644
--- /dev/null
+++ b/dom/camera/GonkNativeWindow.cpp
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GonkNativeWindow.h"
+#include "nsDebug.h"
+
+// enable debug logging by setting to 1
+#define CNW_DEBUG 0
+#if CNW_DEBUG
+#define CNW_LOGD(...) {(void)printf_stderr(__VA_ARGS__);}
+#else
+#define CNW_LOGD(...) ((void)0)
+#endif
+
+#define CNW_LOGE(...) {(void)printf_stderr(__VA_ARGS__);}
+
+using namespace android;
+
+GonkNativeWindow::GonkNativeWindow()
+{
+    GonkNativeWindow::init();
+}
+
+GonkNativeWindow::~GonkNativeWindow()
+{
+    freeAllBuffersLocked();
+}
+
+void GonkNativeWindow::init()
+{
+    // Initialize the ANativeWindow function pointers.
+    ANativeWindow::setSwapInterval  = hook_setSwapInterval;
+    ANativeWindow::dequeueBuffer    = hook_dequeueBuffer;
+    ANativeWindow::cancelBuffer     = hook_cancelBuffer;
+    ANativeWindow::lockBuffer       = hook_lockBuffer;
+    ANativeWindow::queueBuffer      = hook_queueBuffer;
+    ANativeWindow::query            = hook_query;
+    ANativeWindow::perform          = hook_perform;
+
+    mDefaultWidth = 0;
+    mDefaultHeight = 0;
+    mPixelFormat = 0;
+    mUsage = 0;
+    mTimestamp = NATIVE_WINDOW_TIMESTAMP_AUTO;
+    mBufferCount = MIN_BUFFER_SLOTS;
+    mFrameCounter = 0;
+}
+
+
+int GonkNativeWindow::hook_setSwapInterval(ANativeWindow* window, int interval)
+{
+    GonkNativeWindow* c = getSelf(window);
+    return c->setSwapInterval(interval);
+}
+
+int GonkNativeWindow::hook_dequeueBuffer(ANativeWindow* window,
+        ANativeWindowBuffer** buffer)
+{
+    GonkNativeWindow* c = getSelf(window);
+    return c->dequeueBuffer(buffer);
+}
+
+int GonkNativeWindow::hook_cancelBuffer(ANativeWindow* window,
+        ANativeWindowBuffer* buffer)
+{
+    GonkNativeWindow* c = getSelf(window);
+    return c->cancelBuffer(buffer);
+}
+
+int GonkNativeWindow::hook_lockBuffer(ANativeWindow* window,
+        ANativeWindowBuffer* buffer)
+{
+    GonkNativeWindow* c = getSelf(window);
+    return c->lockBuffer(buffer);
+}
+
+int GonkNativeWindow::hook_queueBuffer(ANativeWindow* window,
+        ANativeWindowBuffer* buffer)
+{
+    GonkNativeWindow* c = getSelf(window);
+    return c->queueBuffer(buffer);
+}
+
+int GonkNativeWindow::hook_query(const ANativeWindow* window,
+                                int what, int* value)
+{
+    const GonkNativeWindow* c = getSelf(window);
+    return c->query(what, value);
+}
+
+int GonkNativeWindow::hook_perform(ANativeWindow* window, int operation, ...)
+{
+    va_list args;
+    va_start(args, operation);
+    GonkNativeWindow* c = getSelf(window);
+    return c->perform(operation, args);
+}
+
+void GonkNativeWindow::freeBufferLocked(int i)
+{
+    if (mSlots[i].mGraphicBuffer != NULL) {
+        mSlots[i].mGraphicBuffer.clear();
+        mSlots[i].mGraphicBuffer = NULL;
+    }
+    mSlots[i].mBufferState = BufferSlot::FREE;
+}
+
+void GonkNativeWindow::freeAllBuffersLocked()
+{
+    for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+        freeBufferLocked(i);
+    }
+}
+
+int GonkNativeWindow::setBufferCount(int bufferCount) {
+    CNW_LOGD("setBufferCount: count=%d", bufferCount);
+    Mutex::Autolock lock(mMutex);
+
+    if (bufferCount > NUM_BUFFER_SLOTS) {
+        CNW_LOGE("setBufferCount: bufferCount larger than slots available");
+        return BAD_VALUE;
+    }
+
+    // special-case, nothing to do
+    if (bufferCount == mBufferCount) {
+        return OK;
+    }
+
+    if (bufferCount < MIN_BUFFER_SLOTS) {
+        CNW_LOGE("setBufferCount: requested buffer count (%d) is less than "
+                "minimum (%d)", bufferCount, MIN_BUFFER_SLOTS);
+        return BAD_VALUE;
+    }
+
+    // Error out if the user has dequeued buffers
+    for (int i=0 ; i<mBufferCount ; i++) {
+        if (mSlots[i].mBufferState == BufferSlot::DEQUEUED) {
+            CNW_LOGE("setBufferCount: client owns some buffers");
+            return -EINVAL;
+        }
+    }
+
+    if (bufferCount > mBufferCount) {
+        // easy, we just have more buffers
+        mBufferCount = bufferCount;
+        mDequeueCondition.signal();
+        return OK;
+    }
+
+    // reducing the number of buffers
+    // here we're guaranteed that the client doesn't have dequeued buffers
+    // and will release all of its buffer references.
+    freeAllBuffersLocked();
+    mBufferCount = bufferCount;
+    mDequeueCondition.signal();
+    return OK;
+}
+
+int GonkNativeWindow::dequeueBuffer(android_native_buffer_t** buffer)
+{
+    Mutex::Autolock lock(mMutex);
+
+    int found = -1;
+    int dequeuedCount = 0;
+    bool tryAgain = true;
+
+    CNW_LOGD("dequeueBuffer: E");
+    while (tryAgain) {
+        // look for a free buffer to give to the client
+        found = INVALID_BUFFER_SLOT;
+        dequeuedCount = 0;
+        for (int i = 0; i < mBufferCount; i++) {
+            const int state = mSlots[i].mBufferState;
+            if (state == BufferSlot::DEQUEUED) {
+                dequeuedCount++;
+            }
+            else if (state == BufferSlot::FREE) {
+                /* We return the oldest of the free buffers to avoid
+                 * stalling the producer if possible.  This is because
+                 * the consumer may still have pending reads of the
+                 * buffers in flight.
+                 */
+                bool isOlder = mSlots[i].mFrameNumber < mSlots[found].mFrameNumber;
+                if (found < 0 || isOlder) {
+                    found = i;
+                }
+            }
+        }
+
+        // we're in synchronous mode and didn't find a buffer, we need to
+        // wait for some buffers to be consumed
+        tryAgain = (found == INVALID_BUFFER_SLOT);
+        if (tryAgain) {
+            mDequeueCondition.wait(mMutex);
+        }
+    }
+
+    if (found == INVALID_BUFFER_SLOT) {
+        // This should not happen.
+        CNW_LOGE("dequeueBuffer: no available buffer slots");
+        return -EBUSY;
+    }
+
+    const int buf = found;
+
+    // buffer is now in DEQUEUED
+    mSlots[buf].mBufferState = BufferSlot::DEQUEUED;
+
+    const sp<GraphicBuffer>& gbuf(mSlots[buf].mGraphicBuffer);
+
+    if (gbuf == NULL) {
+        status_t error;
+        sp<GraphicBuffer> graphicBuffer( new GraphicBuffer( mDefaultWidth, mDefaultHeight, mPixelFormat, mUsage));
+        error = graphicBuffer->initCheck();
+        if (error != NO_ERROR) {
+            CNW_LOGE("dequeueBuffer: createGraphicBuffer failed with error %d",error);
+            return error;
+        }
+        mSlots[buf].mGraphicBuffer = graphicBuffer;
+    }
+    *buffer = mSlots[buf].mGraphicBuffer.get();
+
+    CNW_LOGD("dequeueBuffer: returning slot=%d buf=%p ", buf,
+            mSlots[buf].mGraphicBuffer->handle );
+
+    CNW_LOGD("dequeueBuffer: X");
+    return NO_ERROR;
+}
+
+int GonkNativeWindow::getSlotFromBufferLocked(
+        android_native_buffer_t* buffer) const
+{
+    if (buffer == NULL) {
+        CNW_LOGE("getSlotFromBufferLocked: encountered NULL buffer");
+        return BAD_VALUE;
+    }
+
+    for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+        if (mSlots[i].mGraphicBuffer != NULL && mSlots[i].mGraphicBuffer->handle == buffer->handle) {
+            return i;
+        }
+    }
+    CNW_LOGE("getSlotFromBufferLocked: unknown buffer: %p", buffer->handle);
+    return BAD_VALUE;
+}
+
+int GonkNativeWindow::queueBuffer(ANativeWindowBuffer* buffer)
+{
+    Mutex::Autolock lock(mMutex);
+    CNW_LOGD("queueBuffer: E");
+    int buf = getSlotFromBufferLocked(buffer);
+
+    if (buf < 0 || buf >= mBufferCount) {
+        CNW_LOGE("queueBuffer: slot index out of range [0, %d]: %d",
+                mBufferCount, buf);
+        return -EINVAL;
+    } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
+        CNW_LOGE("queueBuffer: slot %d is not owned by the client "
+                "(state=%d)", buf, mSlots[buf].mBufferState);
+        return -EINVAL;
+    }
+
+    int64_t timestamp;
+    if (mTimestamp == NATIVE_WINDOW_TIMESTAMP_AUTO) {
+        timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
+    } else {
+        timestamp = mTimestamp;
+    }
+
+    // Set the state to FREE as there are no operations on the queued buffer
+    // And, so that the buffer can be dequeued when needed.
+    mSlots[buf].mBufferState = BufferSlot::FREE;
+    mSlots[buf].mTimestamp = timestamp;
+    mFrameCounter++;
+    mSlots[buf].mFrameNumber = mFrameCounter;
+
+    mDequeueCondition.signal();
+    CNW_LOGD("queueBuffer: X");
+
+    return OK;
+}
+
+int GonkNativeWindow::lockBuffer(ANativeWindowBuffer* buffer)
+{
+    CNW_LOGD("GonkNativeWindow::lockBuffer");
+    Mutex::Autolock lock(mMutex);
+    return OK;
+}
+
+int GonkNativeWindow::cancelBuffer(ANativeWindowBuffer* buffer)
+{
+    Mutex::Autolock lock(mMutex);
+    int buf = getSlotFromBufferLocked(buffer);
+
+    CNW_LOGD("cancelBuffer: slot=%d", buf);
+    if (buf < 0 || buf >= mBufferCount) {
+        CNW_LOGE("cancelBuffer: slot index out of range [0, %d]: %d",
+                mBufferCount, buf);
+        return -EINVAL;
+    } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
+        CNW_LOGE("cancelBuffer: slot %d is not owned by the client (state=%d)",
+                buf, mSlots[buf].mBufferState);
+        return -EINVAL;
+    }
+    mSlots[buf].mBufferState = BufferSlot::FREE;
+    mSlots[buf].mFrameNumber = 0;
+    mDequeueCondition.signal();
+    return OK;
+}
+
+int GonkNativeWindow::perform(int operation, va_list args)
+{
+    switch (operation) {
+        case NATIVE_WINDOW_CONNECT:
+            // deprecated. must return NO_ERROR.
+            return NO_ERROR;
+        case NATIVE_WINDOW_DISCONNECT:
+            // deprecated. must return NO_ERROR.
+            return NO_ERROR;
+        case NATIVE_WINDOW_SET_USAGE:
+            return dispatchSetUsage(args);
+        case NATIVE_WINDOW_SET_BUFFER_COUNT:
+            return dispatchSetBufferCount(args);
+        case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY:
+            return dispatchSetBuffersGeometry(args);
+        case NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP:
+            return dispatchSetBuffersTimestamp(args);
+        case NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS:
+            return dispatchSetBuffersDimensions(args);
+        case NATIVE_WINDOW_SET_BUFFERS_FORMAT:
+            return dispatchSetBuffersFormat(args);
+        case NATIVE_WINDOW_SET_CROP:
+        case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM:
+        case NATIVE_WINDOW_SET_SCALING_MODE:
+        case NATIVE_WINDOW_LOCK:
+        case NATIVE_WINDOW_UNLOCK_AND_POST:
+        case NATIVE_WINDOW_API_CONNECT:
+        case NATIVE_WINDOW_API_DISCONNECT:
+        default:
+            return INVALID_OPERATION;
+    }
+}
+
+int GonkNativeWindow::query(int what, int* outValue) const
+{
+    Mutex::Autolock lock(mMutex);
+
+    int value;
+    switch (what) {
+    case NATIVE_WINDOW_WIDTH:
+        value = mDefaultWidth;
+        break;
+    case NATIVE_WINDOW_HEIGHT:
+        value = mDefaultHeight;
+        break;
+    case NATIVE_WINDOW_FORMAT:
+        value = mPixelFormat;
+        break;
+    case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
+        value = MIN_UNDEQUEUED_BUFFERS;
+        break;
+    default:
+        return BAD_VALUE;
+    }
+    outValue[0] = value;
+    return NO_ERROR;
+}
+
+int GonkNativeWindow::setSwapInterval(int interval)
+{
+    return NO_ERROR;
+}
+
+int GonkNativeWindow::dispatchSetUsage(va_list args)
+{
+    int usage = va_arg(args, int);
+    return setUsage(usage);
+}
+
+int GonkNativeWindow::dispatchSetBufferCount(va_list args)
+{
+    size_t bufferCount = va_arg(args, size_t);
+    return setBufferCount(bufferCount);
+}
+
+int GonkNativeWindow::dispatchSetBuffersGeometry(va_list args)
+{
+    int w = va_arg(args, int);
+    int h = va_arg(args, int);
+    int f = va_arg(args, int);
+    int err = setBuffersDimensions(w, h);
+    if (err != 0) {
+        return err;
+    }
+    return setBuffersFormat(f);
+}
+
+int GonkNativeWindow::dispatchSetBuffersDimensions(va_list args)
+{
+    int w = va_arg(args, int);
+    int h = va_arg(args, int);
+    return setBuffersDimensions(w, h);
+}
+
+int GonkNativeWindow::dispatchSetBuffersFormat(va_list args)
+{
+    int f = va_arg(args, int);
+    return setBuffersFormat(f);
+}
+
+int GonkNativeWindow::dispatchSetBuffersTimestamp(va_list args)
+{
+    int64_t timestamp = va_arg(args, int64_t);
+    return setBuffersTimestamp(timestamp);
+}
+
+int GonkNativeWindow::setUsage(uint32_t reqUsage)
+{
+    CNW_LOGD("GonkNativeWindow::setUsage");
+    Mutex::Autolock lock(mMutex);
+    mUsage = reqUsage;
+    return OK;
+}
+
+int GonkNativeWindow::setBuffersDimensions(int w, int h)
+{
+    CNW_LOGD("GonkNativeWindow::setBuffersDimensions");
+    Mutex::Autolock lock(mMutex);
+
+    if (w<0 || h<0)
+        return BAD_VALUE;
+
+    if ((w && !h) || (!w && h))
+        return BAD_VALUE;
+
+    mDefaultWidth = w;
+    mDefaultHeight = h;
+
+    return OK;
+}
+
+int GonkNativeWindow::setBuffersFormat(int format)
+{
+    CNW_LOGD("GonkNativeWindow::setBuffersFormat");
+    Mutex::Autolock lock(mMutex);
+
+    if (format<0)
+        return BAD_VALUE;
+
+    mPixelFormat = format;
+
+    return NO_ERROR;
+}
+
+int GonkNativeWindow::setBuffersTimestamp(int64_t timestamp)
+{
+    CNW_LOGD("GonkNativeWindow::setBuffersTimestamp");
+    Mutex::Autolock lock(mMutex);
+    mTimestamp = timestamp;
+    return OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/GonkNativeWindow.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef DOM_CAMERA_GONKNATIVEWINDOW_H
+#define DOM_CAMERA_GONKNATIVEWINDOW_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <ui/egl/android_natives.h>
+
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+#include <ui/GraphicBuffer.h>
+#include <ui/Rect.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class GonkNativeWindow : public EGLNativeBase<ANativeWindow, GonkNativeWindow, RefBase>
+{
+public:
+    enum { MIN_UNDEQUEUED_BUFFERS = 2 };
+    enum { MIN_BUFFER_SLOTS = MIN_UNDEQUEUED_BUFFERS };
+    enum { NUM_BUFFER_SLOTS = 32 };
+
+    GonkNativeWindow();
+    ~GonkNativeWindow(); // this class cannot be overloaded
+
+    // ANativeWindow hooks
+    static int hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer);
+    static int hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer);
+    static int hook_lockBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer);
+    static int hook_perform(ANativeWindow* window, int operation, ...);
+    static int hook_query(const ANativeWindow* window, int what, int* value);
+    static int hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer);
+    static int hook_setSwapInterval(ANativeWindow* window, int interval);
+
+protected:
+    virtual int cancelBuffer(ANativeWindowBuffer* buffer);
+    virtual int dequeueBuffer(ANativeWindowBuffer** buffer);
+    virtual int lockBuffer(ANativeWindowBuffer* buffer);
+    virtual int perform(int operation, va_list args);
+    virtual int query(int what, int* value) const;
+    virtual int queueBuffer(ANativeWindowBuffer* buffer);
+    virtual int setSwapInterval(int interval);
+
+    virtual int setBufferCount(int bufferCount);
+    virtual int setBuffersDimensions(int w, int h);
+    virtual int setBuffersFormat(int format);
+    virtual int setBuffersTimestamp(int64_t timestamp);
+    virtual int setUsage(uint32_t reqUsage);
+
+    // freeBufferLocked frees the resources (both GraphicBuffer and EGLImage)
+    // for the given slot.
+    void freeBufferLocked(int index);
+
+    // freeAllBuffersLocked frees the resources (both GraphicBuffer and
+    // EGLImage) for all slots.
+    void freeAllBuffersLocked();
+
+private:
+    void init();
+
+    int dispatchSetBufferCount(va_list args);
+    int dispatchSetBuffersGeometry(va_list args);
+    int dispatchSetBuffersDimensions(va_list args);
+    int dispatchSetBuffersFormat(va_list args);
+    int dispatchSetBuffersTimestamp(va_list args);
+    int dispatchSetUsage(va_list args);
+
+    int getSlotFromBufferLocked(android_native_buffer_t* buffer) const;
+
+private:
+    enum { INVALID_BUFFER_SLOT = -1 };
+
+    struct BufferSlot {
+
+        BufferSlot()
+            : mGraphicBuffer(0),
+              mBufferState(BufferSlot::FREE),
+              mTimestamp(0),
+              mFrameNumber(0){
+        }
+
+        // mGraphicBuffer points to the buffer allocated for this slot or is NULL
+        // if no buffer has been allocated.
+        sp<GraphicBuffer> mGraphicBuffer;
+
+        // BufferState represents the different states in which a buffer slot
+        // can be.
+        enum BufferState {
+            // FREE indicates that the buffer is not currently being used and
+            // will not be used in the future until it gets dequeued and
+            // subsequently queued by the client.
+            FREE = 0,
+
+            // DEQUEUED indicates that the buffer has been dequeued by the
+            // client, but has not yet been queued or canceled. The buffer is
+            // considered 'owned' by the client, and the server should not use
+            // it for anything.
+            //
+            // Note that when in synchronous-mode (mSynchronousMode == true),
+            // the buffer that's currently attached to the texture may be
+            // dequeued by the client.  That means that the current buffer can
+            // be in either the DEQUEUED or QUEUED state.  In asynchronous mode,
+            // however, the current buffer is always in the QUEUED state.
+            DEQUEUED = 1,
+
+            // QUEUED indicates that the buffer has been queued by the client,
+            // and has not since been made available for the client to dequeue.
+            // Attaching the buffer to the texture does NOT transition the
+            // buffer away from the QUEUED state. However, in Synchronous mode
+            // the current buffer may be dequeued by the client under some
+            // circumstances. See the note about the current buffer in the
+            // documentation for DEQUEUED.
+            QUEUED = 2,
+        };
+
+        // mBufferState is the current state of this buffer slot.
+        BufferState mBufferState;
+
+        // mTimestamp is the current timestamp for this buffer slot. This gets
+        // to set by queueBuffer each time this slot is queued.
+        int64_t mTimestamp;
+
+        // mFrameNumber is the number of the queued frame for this slot.
+        uint64_t mFrameNumber;
+    };
+
+    // mSlots is the array of buffer slots that must be mirrored on the client
+    // side. This allows buffer ownership to be transferred between the client
+    // and server without sending a GraphicBuffer over binder. The entire array
+    // is initialized to NULL at construction time, and buffers are allocated
+    // for a slot when requestBuffer is called with that slot's index.
+    BufferSlot mSlots[NUM_BUFFER_SLOTS];
+
+    // mDequeueCondition condition used for dequeueBuffer in synchronous mode
+    mutable Condition mDequeueCondition;
+
+    // mTimestamp is the timestamp that will be used for the next buffer queue
+    // operation. It defaults to NATIVE_WINDOW_TIMESTAMP_AUTO, which means that
+    // a timestamp is auto-generated when queueBuffer is called.
+    int64_t mTimestamp;
+
+    // mDefaultWidth holds the default width of allocated buffers. It is used
+    // in requestBuffers() if a width and height of zero is specified.
+    uint32_t mDefaultWidth;
+
+    // mDefaultHeight holds the default height of allocated buffers. It is used
+    // in requestBuffers() if a width and height of zero is specified.
+    uint32_t mDefaultHeight;
+
+    // mPixelFormat holds the pixel format of allocated buffers. It is used
+    // in requestBuffers() if a format of zero is specified.
+    uint32_t mPixelFormat;
+
+    // usage flag
+    uint32_t mUsage;
+
+    // mBufferCount is the number of buffer slots that the client and server
+    // must maintain. It defaults to MIN_ASYNC_BUFFER_SLOTS and can be changed
+    // by calling setBufferCount or setBufferCountServer
+    int mBufferCount;
+
+    // mMutex is the mutex used to prevent concurrent access to the member
+    // variables. It must be locked whenever the member variables are accessed.
+    mutable Mutex mMutex;
+
+    // mFrameCounter is the free running counter, incremented for every buffer queued
+    uint64_t mFrameCounter;
+};
+
+}; // namespace android
+
+#endif // DOM_CAMERA_GONKNATIVEWINDOW_H
new file mode 100644
--- /dev/null
+++ b/dom/camera/Makefile.in
@@ -0,0 +1,52 @@
+# 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/.
+
+DEPTH            = ../..
+topsrcdir        = @top_srcdir@
+srcdir           = @srcdir@
+VPATH            = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE           = dom
+LIBRARY_NAME     = domcamera_s
+XPIDL_MODULE     = dom_camera
+LIBXUL_LIBRARY   = 1
+FORCE_STATIC_LIB = 1
+
+include $(topsrcdir)/dom/dom-config.mk
+
+CPPSRCS = \
+  DOMCameraManager.cpp \
+  CameraControl.cpp \
+  CameraPreview.cpp \
+  $(NULL)
+
+ifeq ($(MOZ_B2G_CAMERA),1)
+CPPSRCS += \
+  GonkCameraManager.cpp \
+  GonkCameraControl.cpp \
+  GonkCameraHwMgr.cpp \
+  GonkCameraPreview.cpp \
+  GonkNativeWindow.cpp \
+  GonkCameraCapabilities.cpp \
+  $(NULL)
+else
+CPPSRCS += \
+  FallbackCameraManager.cpp \
+  FallbackCameraControl.cpp \
+  FallbackCameraCapabilities.cpp \
+  $(NULL)
+endif
+
+XPIDLSRCS = \
+  nsIDOMNavigatorCamera.idl \
+  nsIDOMCameraManager.idl \
+  $(NULL)
+
+EXPORTS = \
+  DOMCameraManager.h \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/camera/nsIDOMCameraManager.idl
@@ -0,0 +1,341 @@
+#include "domstubs.idl"
+
+#include "nsIDOMMediaStream.idl"
+#include "nsIDOMDOMRequest.idl"
+
+
+interface nsIDOMBlob;
+
+/* Used to set the dimensions of a captured picture,
+   a preview stream, a video capture stream, etc. */
+dictionary CameraSize {
+    unsigned long width;
+    unsigned long height;
+};
+
+/* Camera regions are used to set focus and metering areas;
+   the coordinates are referenced to the sensor:
+     (-1000, -1000) is the top left corner
+     (1000, 1000) is the bottom left corner
+   The weight of the region can range from 0 to 1000. */
+dictionary CameraRegion {
+    long top;
+    long left;
+    long bottom;
+    long right;
+    unsigned long weight;
+};
+
+/* The position information to record in the image header.
+   'NaN' indicates the information is not available. */
+dictionary CameraPosition {
+    double latitude;
+    double longitude;
+    double altitude;
+    double timestamp;
+};
+
+/* Select a camera to use. */
+dictionary CameraSelector {
+    DOMString camera = "back";
+};
+
+[scriptable, uuid(64196840-0d03-4b65-a955-790f43a4b810)]
+interface nsICameraCapabilities : nsISupports
+{
+    /* an array of objects with 'height' and 'width' properties
+       supported for the preview stream */
+    [implicit_jscontext]
+    readonly attribute jsval        previewSizes;
+
+    /* an array of objects with 'height' and 'width' properties
+       supported for picture taking */
+    [implicit_jscontext]
+    readonly attribute jsval        pictureSizes;
+
+    /* an array of strings, e.g. [ "jpeg", "rgb565" ] */
+    [implicit_jscontext]
+    readonly attribute jsval        fileFormats;
+
+    /* an array of strings, e.g. [ "auto", "fluorescent", etc. ] */
+    [implicit_jscontext]
+    readonly attribute jsval        whiteBalanceModes;
+
+    /* an array of strings, e.g. [ "auto", "night", "beach", etc. ] */
+    [implicit_jscontext]
+    readonly attribute jsval        sceneModes;
+
+    /* an array of strings, e.g. [ "normal", "sepia", "mono", etc. ] */
+    [implicit_jscontext]
+    readonly attribute jsval        effects;
+
+    /* an array of strings, e.g. [ "auto", "off", "on", etc. ] */
+    [implicit_jscontext]
+    readonly attribute jsval        flashModes;
+
+    /* an array of strings, e.g. [ "auto", "fixed", "macro", etc. ] */
+    [implicit_jscontext]
+    readonly attribute jsval        focusModes;
+
+    /* the maximum number of focus areas supported by the camera */
+    [implicit_jscontext]
+    readonly attribute long         maxFocusAreas;
+
+    /* the minimum supported exposure compensation value */
+    [implicit_jscontext]
+    readonly attribute double       minExposureCompensation;
+
+    /* the maximum supported exposure compensation value */
+    [implicit_jscontext]
+    readonly attribute double       maxExposureCompensation;
+
+    /* exposure compensation minimum step-size */
+    [implicit_jscontext]
+    readonly attribute double       stepExposureCompensation;
+
+    /* the maximum number of metering areas supported by the camera */
+    [implicit_jscontext]
+    readonly attribute long         maxMeteringAreas;
+
+    /* an array of doubles, e.g. [ 1.0, 1.2, 1.5, 2.0, 3.0, etc. ],
+       or null if zooming is not supported */
+    [implicit_jscontext]
+    readonly attribute jsval        zoomRatios;
+
+    /* an array of objects with 'height' and 'width' properties
+       supported for video recording */
+    [implicit_jscontext]
+    readonly attribute jsval        videoSizes;
+};
+
+/*
+    These properties only affect the captured image;
+    invalid property settings are ignored.
+*/
+dictionary CameraPictureOptions
+{
+    /* an object with a combination of 'height' and 'width' properties
+       chosen from nsICameraCapabilities.pictureSizes */
+    jsval     pictureSize;
+
+    /* one of the file formats chosen from
+       nsICameraCapabilities.fileFormats */
+    DOMString fileFormat;
+
+    /* the rotation of the image in degrees, from 0 to 270 in
+       steps of 90; this doesn't affect the image, only the
+       rotation recorded in the image header.*/
+    long      rotation;
+
+    /* an object containing any or all of 'latitude', 'longitude',
+       'altitude', and 'timestamp', used to record when and where
+       the image was taken.  e.g.
+        {
+            latitude:  43.647118,
+            longitude: -79.3943,
+            altitude:  500
+            // timestamp not specified, in this case, and
+            // won't be included in the image header
+        }
+
+        can be null in the case where position information isn't
+        available/desired.
+
+        'altitude' is in metres; 'timestamp' is UTC, in seconds from
+        January 1, 1970.
+    */
+    jsval     position;
+};
+
+[scriptable, function, uuid(0444a687-4bc9-462c-8246-5423f0fe46a4)]
+interface nsICameraPreviewStreamCallback : nsISupports
+{
+    void handleEvent(in nsIDOMMediaStream stream);
+};
+
+[scriptable, function, uuid(6baa4ac7-9c25-4c48-9bb0-5193b38b9b0a)]
+interface nsICameraAutoFocusCallback : nsISupports
+{
+    void handleEvent(in boolean success);
+};
+
+[scriptable, function, uuid(17af779e-cb6f-4ca5-890c-06468ff82e4f)]
+interface nsICameraTakePictureCallback : nsISupports
+{
+    void handleEvent(in nsIDOMBlob picture);
+};
+
+[scriptable, function, uuid(ac43f123-529c-48d3-84dd-ad206b7aca9b)]
+interface nsICameraStartRecordingCallback : nsISupports
+{
+    void handleEvent(in nsIDOMMediaStream stream);
+};
+
+[scriptable, function, uuid(fb80db71-e315-42f0-9ea9-dd3dd312ed70)]
+interface nsICameraShutterCallback : nsISupports
+{
+    void handleEvent();
+};
+
+[scriptable, function, uuid(a302c6c9-3776-4d1d-a395-f4105d47c3d3)]
+interface nsICameraErrorCallback : nsISupports
+{
+    void handleEvent(in DOMString error);
+};
+
+/*
+    attributes here affect the preview, any pictures taken, and/or
+    any video recorded by the camera.
+*/
+[scriptable, uuid(3066c884-d2c3-4477-847d-08ea1c2d188a)]
+interface nsICameraControl : nsISupports
+{
+    readonly attribute nsICameraCapabilities capabilities;
+
+    /* one of the vales chosen from capabilities.effects;
+       default is "none" */
+    attribute DOMString         effect;
+
+    /* one of the values chosen from capabilities.whiteBalanceModes;
+       default is "auto" */
+    attribute DOMString         whiteBalanceMode;
+
+    /* one of the valus chosen from capabilities.sceneModes;
+       default is "auto" */
+    attribute DOMString         sceneMode;
+
+    /* one of the values chosen from capabilities.flashModes;
+       default is "auto" */
+    attribute DOMString         flashMode;
+
+    /* one of the values chosen from capabilities.focusModes;
+       default is "auto", if supported, or "fixed" */
+    attribute DOMString         focusMode;
+
+    /* one of the values chosen from capabilities.zoomRatios; other
+       values will be rounded to the nearest supported value;
+       default is 1.0 */
+    attribute double            zoom;
+
+    /* an array of one or more objects that define where the
+       camera will perform light metering, each defining the properties:
+        {
+            top: -1000,
+            left: -1000,
+            bottom: 1000,
+            right: 1000,
+            weight: 1000
+        }
+
+        'top', 'left', 'bottom', and 'right' all range from -1000 at
+        the top-/leftmost of the sensor to 1000 at the bottom-/rightmost
+        of the sensor.
+
+        objects missing one or more of these properties will be ignored;
+        if the array contains more than capabilities.maxMeteringAreas,
+        extra areas will be ignored.
+
+        this attribute can be set to null to allow the camera to determine
+        where to perform light metering. */
+    [implicit_jscontext]
+    attribute jsval             meteringAreas;
+
+    /* an array of one or more objects that define where the camera will
+       perform auto-focusing, with the same definition as meteringAreas.
+
+       if the array contains more than capabilities.maxFocusAreas, extra
+       areas will be ignored.
+
+       this attribute can be set to null to allow the camera to determine
+       where to focus. */
+    [implicit_jscontext]
+    attribute jsval             focusAreas;
+
+    /* focal length in millimetres */
+    readonly attribute double   focalLength;
+
+    /* the distances in metres to where the image subject appears to be
+       in focus.  'focusDistanceOptimum' is where the subject will appear
+       sharpest; the difference between 'focusDistanceFar' and
+       'focusDistanceNear' is the image's depth of field.
+
+       'focusDistanceFar' may be infinity. */
+    readonly attribute double   focusDistanceNear;
+    readonly attribute double   focusDistanceOptimum;
+    readonly attribute double   focusDistanceFar;
+
+    /* 'compensation' is optional, and if missing, will
+       set the camera to use automatic exposure compensation.
+
+       acceptable values must range from minExposureCompensation
+       to maxExposureCompensation in steps of stepExposureCompensation;
+       invalid values will be rounded to the nearest valid value. */
+    [implicit_jscontext]
+    void setExposureCompensation([optional] in jsval compensation);
+    readonly attribute double   exposureCompensation;
+
+    /* the function to call on the camera's shutter event, to trigger
+       a shutter sound and/or a visual shutter indicator. */
+    attribute nsICameraShutterCallback onShutter;
+
+    /* tell the camera to attempt to focus the image */
+    void autoFocus(in nsICameraAutoFocusCallback onSuccess, [optional] in nsICameraErrorCallback onError);
+
+    /* 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). */
+    [implicit_jscontext]
+    void takePicture(in jsval aOptions, in nsICameraTakePictureCallback onSuccess, [optional] in nsICameraErrorCallback onError);
+
+    /* start recording video; 'aOptions' define the frame size of to
+       capture, chosen from capabilities.videoSizes, e.g.:
+        {
+            width: 640,
+            height: 480
+        }
+    */
+    [implicit_jscontext]
+    void startRecording(in jsval aOptions, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError);
+
+    /* stop precording video. */
+    void stopRecording();
+
+    /* get a media stream to be used as a camera viewfinder; the options
+       define the desired frame size of the preview, chosen from
+       capabilities.previewSizes, e.g.:
+        {
+            height: 640,
+            width:  480,
+         }
+    */
+    [implicit_jscontext]
+    void getPreviewStream(in jsval aOptions, in nsICameraPreviewStreamCallback onSuccess, [optional] in nsICameraErrorCallback onError);
+};
+
+[scriptable, function, uuid(a267afbc-d91c-413a-8de5-0b94aecffa3e)]
+interface nsICameraGetCameraCallback : nsISupports
+{
+    void handleEvent(in nsICameraControl camera);
+};
+
+[scriptable, uuid(671ee624-0336-441a-a24e-26b5319f14fe)]
+interface nsIDOMCameraManager : nsISupports
+{
+    /* get a camera instance; options will be used to specify which
+       camera to get from the list returned by getListOfCameras(), e.g.:
+        {
+            camera: front
+        }
+    */
+    [implicit_jscontext]
+    void getCamera([optional] in jsval aOptions, in nsICameraGetCameraCallback onSuccess, [optional] in nsICameraErrorCallback onError);
+
+    /* return a JSON array of camera   identifiers, e.g.
+        [ "front", "back" ]
+    */
+    [implicit_jscontext]
+    jsval getListOfCameras();
+};
new file mode 100644
--- /dev/null
+++ b/dom/camera/nsIDOMNavigatorCamera.idl
@@ -0,0 +1,15 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* 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 "nsISupports.idl"
+
+interface nsIDOMCameraManager;
+
+[scriptable, uuid(bbb2456a-a6c8-42c8-8f52-6de071097e4b)]
+interface nsIDOMNavigatorCamera : nsISupports
+{
+  readonly attribute nsIDOMCameraManager mozCameras;
+};
--- a/dom/dom-config.mk
+++ b/dom/dom-config.mk
@@ -25,16 +25,17 @@ DOM_SRCDIRS = \
   content/base/src \
   content/html/content/src \
   content/html/document/src \
   content/svg/content/src \
   layout/generic \
   layout/style \
   layout/xul/base/src \
   layout/xul/base/src/tree/src \
+  dom/camera \
   $(NULL)
 
 ifdef MOZ_B2G_RIL
 DOM_SRCDIRS += \
   dom/system/gonk \
   dom/telephony \
   dom/wifi \
   $(NULL)
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -518,17 +518,21 @@ var interfaceNamesInGlobalScope =
     "HTMLParagraphElement",
     "EventTarget",
     "File",
     "WebGLActiveInfo",
     "SVGGradientElement",
     "ContactTelephone",
     "ContactEmail",
     "SVGFitToViewBox",
-    "SVGAElement"
+    "SVGAElement",
+    "NavigatorCamera",
+    "CameraControl",
+    "CameraCapabilities",
+    "CameraManager"
   ]
 
 for (var i in Components.interfaces) {
   var s = i.toString();
   var name = null;
   if (s.indexOf("nsIDOM") == 0) {
     name = s.substring("nsIDOM".length);
   } else if (s.indexOf("nsI") == 0) {
--- a/js/xpconnect/src/dictionary_helper_gen.conf
+++ b/js/xpconnect/src/dictionary_helper_gen.conf
@@ -13,17 +13,22 @@ dictionaries = [
      [ 'BlobPropertyBag', 'nsIDOMFile.idl' ],
      [ 'MutationObserverInit', 'nsIDOMMutationObserver.idl' ],
      [ 'SettingsEventInit', 'nsIDOMSettingsManager.idl' ],
      [ 'WifiConnectionInfoEventInit', 'nsIWifiEventInits.idl' ],
      [ 'WifiStatusChangeEventInit', 'nsIWifiEventInits.idl' ],
      [ 'GeoPositionOptions', 'nsIDOMGeoGeolocation.idl' ],
      [ 'DOMFileMetadataParameters', 'nsIDOMLockedFile.idl' ],
      [ 'XMLHttpRequestParameters', 'nsIXMLHttpRequest.idl' ],
-     [ 'DeviceStorageEnumerationParameters', 'nsIDOMDeviceStorage.idl' ]
+     [ 'DeviceStorageEnumerationParameters', 'nsIDOMDeviceStorage.idl' ],
+     [ 'CameraSize', 'nsIDOMCameraManager.idl' ],
+     [ 'CameraRegion', 'nsIDOMCameraManager.idl' ],
+     [ 'CameraPosition', 'nsIDOMCameraManager.idl' ],
+     [ 'CameraSelector', 'nsIDOMCameraManager.idl' ],
+     [ 'CameraPictureOptions', 'nsIDOMCameraManager.idl' ]
    ]
 
 # include file names
 special_includes = [
     'nsContentUtils.h',
     'XPCQuickStubs.h',
     'nsIDOMApplicationRegistry.h'
   ]
--- a/layout/build/Makefile.in
+++ b/layout/build/Makefile.in
@@ -116,16 +116,18 @@ LOCAL_INCLUDES	+= \
 	-I$(topsrcdir)/dom/system \
 	$(NULL)
 endif
 
 ifdef MOZ_B2G_BT #{
 SHARED_LIBRARY_LIBS	+= $(DEPTH)/dom/bluetooth/$(LIB_PREFIX)dombluetooth_s.$(LIB_SUFFIX)
 endif #}
 
+SHARED_LIBRARY_LIBS	+= $(DEPTH)/dom/camera/$(LIB_PREFIX)domcamera_s.$(LIB_SUFFIX)
+
 ifdef MOZ_B2G_RIL #{
 SHARED_LIBRARY_LIBS	+= \
   $(DEPTH)/dom/system/gonk/$(LIB_PREFIX)domsystemgonk_s.$(LIB_SUFFIX) \
   $(DEPTH)/dom/telephony/$(LIB_PREFIX)domtelephony_s.$(LIB_SUFFIX) \
   $(NULL)
 endif #}
 
 ifdef MOZ_MEDIA
@@ -268,9 +270,11 @@ LOCAL_INCLUDES	+= -I$(srcdir)/../base \
 ifdef MOZ_B2G_RIL #{
 LOCAL_INCLUDES	+= -I$(topsrcdir)/dom/system/gonk
 endif #}
 
 ifdef MOZ_B2G_BT #{
 LOCAL_INCLUDES	+= -I$(topsrcdir)/dom/bluetooth
 endif #}
 
+LOCAL_INCLUDES	+= -I$(topsrcdir)/dom/camera
+
 DEFINES += -D_IMPL_NS_LAYOUT
--- a/mobile/xul/installer/package-manifest.in
+++ b/mobile/xul/installer/package-manifest.in
@@ -159,16 +159,17 @@
 @BINPATH@/components/dom_telephony.xpt
 @BINPATH@/components/dom_wifi.xpt
 @BINPATH@/components/dom_system_gonk.xpt
 #endif
 @BINPATH@/components/dom_battery.xpt
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #endif
+@BINPATH@/components/dom_camera.xpt
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_devicestorage.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_file.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_network.xpt