Bug 740997 - ICS camera support, r=jst,gal,roc
authorMike Habicher <mhabicher@mozilla.com>
Mon, 30 Jul 2012 17:59:05 -0400
changeset 102796 d59f932deea97bb8b99a41bef1f06e3020f7cf4b
parent 102795 06c2dd7e702ded27cc2ab25eacb5d5042b877dbc
child 102797 8051eeb12d0c2050ceab0ee24d804e5990e49b98
push id18
push usershu@rfrn.org
push dateMon, 06 Aug 2012 22:42:45 +0000
reviewersjst, gal, roc
bugs740997
milestone17.0a1
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