Merge m-c to m-i
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 15 Feb 2014 10:15:56 -0800
changeset 169401 e8afde67c29031370a84c6ff50fe6746b39cc50d
parent 169289 b3a9c9540bfc624b12b309c26f6e0bb01c1d1382 (current diff)
parent 169400 3051177649294795c3791d489c5ac6975c3be7bd (diff)
child 169402 ddac6a90ceb664ade501ec1d80bcd97eb9c36f18
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
milestone30.0a1
Merge m-c to m-i
browser/components/customizableui/content/aboutCustomizing.xhtml
dom/camera/DOMCameraPreview.cpp
dom/camera/DOMCameraPreview.h
dom/camera/nsIDOMCameraManager.idl
mobile/android/base/CameraImageResultHandler.java
mobile/android/base/CameraVideoResultHandler.java
mobile/android/base/FilePickerResultHandlerSync.java
security/build/b2g-app-root-cert.der
security/manager/ssl/src/JARSignatureVerification.cpp
--- a/b2g/components/UpdatePrompt.js
+++ b/b2g/components/UpdatePrompt.js
@@ -361,26 +361,43 @@ UpdatePrompt.prototype = {
       this._update.errorCode = Cr.NS_ERROR_FAILURE;
       this.showUpdateError(this._update);
     }
   },
 
   restartProcess: function UP_restartProcess() {
     log("Update downloaded, restarting to apply it");
 
+    let callbackAfterSet = function() {
 #ifndef MOZ_WIDGET_GONK
-    let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
-                     .getService(Ci.nsIAppStartup);
-    appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
+      let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+                       .getService(Ci.nsIAppStartup);
+      appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
 #else
-    // NB: on Gonk, we rely on the system process manager to restart us.
-    let pmService = Cc["@mozilla.org/power/powermanagerservice;1"]
-                    .getService(Ci.nsIPowerManagerService);
-    pmService.restart();
+      // NB: on Gonk, we rely on the system process manager to restart us.
+      let pmService = Cc["@mozilla.org/power/powermanagerservice;1"]
+                      .getService(Ci.nsIPowerManagerService);
+      pmService.restart();
 #endif
+    }
+
+    // Save current os version in deviceinfo.previous_os
+    let lock = Services.settings.createLock({
+      handle: callbackAfterSet,
+      handleAbort: function(error) {
+        log("Abort callback when trying to set previous_os: " + error);
+        callbackAfterSet();
+      }
+    });
+    lock.get("deviceinfo.os", {
+      handle: function(name, value) {
+        log("Set previous_os to: " + value);
+        lock.set("deviceinfo.previous_os", value, null, null);
+      }
+    });
   },
 
   forceUpdateCheck: function UP_forceUpdateCheck() {
     log("Forcing update check");
 
     let checker = Cc["@mozilla.org/updates/update-checker;1"]
                     .createInstance(Ci.nsIUpdateChecker);
     checker.checkForUpdates(this._updateCheckListener, true);
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -7,17 +7,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f75b9dac351c0676f0fc696fa36330933985ed36"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eda08beb3ba9a159843c70ffde0f9660ec351eb9"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="87aa8679560ce09f6445621d6f370d9de722cdba"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -6,17 +6,17 @@
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="97a5b461686757dbb8ecab2aac5903e41d2e1afe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f75b9dac351c0676f0fc696fa36330933985ed36"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="905bfa3548eb75cf1792d0d8412b92113bbd4318"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="c3d7efc45414f1b44cd9c479bb2758c91c4707c0"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
@@ -120,11 +120,11 @@
   <!-- Emulator specific things -->
   <project name="android-development" path="development" remote="b2g" revision="dab55669da8f48b6e57df95d5af9f16b4a87b0b1"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="09485b73629856b21b2ed6073e327ab0e69a1189"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="8f7c9ac889ae2c778197b4a4c0529d60530f480b"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2838a77ce4b8c09fa6a46fe25410bb3a4474cbd4"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="2b6c3c7662bf4e2178003e900909b1b4a0903e83"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="c283f635d537f3fb249813028702577be50fdcef"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
 </manifest>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -7,17 +7,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f75b9dac351c0676f0fc696fa36330933985ed36"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eda08beb3ba9a159843c70ffde0f9660ec351eb9"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="87aa8679560ce09f6445621d6f370d9de722cdba"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "4f00231c5cc538139e63bee1a7ed8456f6cefed7", 
+    "revision": "f17cd16252b9d28b973c7b223064455c401e5ab3", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -6,17 +6,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f75b9dac351c0676f0fc696fa36330933985ed36"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -5,17 +5,17 @@
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f75b9dac351c0676f0fc696fa36330933985ed36"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -7,17 +7,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f75b9dac351c0676f0fc696fa36330933985ed36"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -6,17 +6,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f75b9dac351c0676f0fc696fa36330933985ed36"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -6,17 +6,17 @@
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="97a5b461686757dbb8ecab2aac5903e41d2e1afe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f75b9dac351c0676f0fc696fa36330933985ed36"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="905bfa3548eb75cf1792d0d8412b92113bbd4318"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="c3d7efc45414f1b44cd9c479bb2758c91c4707c0"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
@@ -116,17 +116,17 @@
   <project name="platform/system/netd" path="system/netd" revision="56112dd7b811301b718d0643a82fd5cac9522073"/>
   <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/>
   <project name="platform/system/vold" path="system/vold" revision="8de05d4a52b5a91e7336e6baa4592f945a6ddbea"/>
   <default remote="caf" revision="refs/tags/android-4.3_r2.1" sync-j="4"/>
   <!-- Nexus 4 specific things -->
   <project name="device-mako" path="device/lge/mako" remote="b2g" revision="78d17f0c117f0c66dd55ee8d5c5dde8ccc93ecba"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device/lge/mako-kernel" path="device/lge/mako-kernel" revision="d1729e53d71d711c8fde25eab8728ff2b9b4df0e"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="2b6c3c7662bf4e2178003e900909b1b4a0903e83"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="c283f635d537f3fb249813028702577be50fdcef"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="0e1929fa3aa38bf9d40e9e953d619fab8164c82e"/>
   <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="b0a528d839cfd9d170d092fe3743b5252b4243a6"/>
   <project name="platform/hardware/qcom/bt" path="hardware/qcom/bt" revision="380945eaa249a2dbdde0daa4c8adb8ca325edba6"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="6f3b0272cefaffeaed2a7d2bb8f633059f163ddc"/>
   <project name="platform/hardware/qcom/keymaster" path="hardware/qcom/keymaster" revision="16da8262c997a5a0d797885788a64a0771b26910"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="689b476ba3eb46c34b81343295fe144a0e81a18e"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -6,17 +6,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f75b9dac351c0676f0fc696fa36330933985ed36"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -162,17 +162,16 @@
 @BINPATH@/components/dom_icc.xpt
 @BINPATH@/components/dom_cellbroadcast.xpt
 @BINPATH@/components/dom_wappush.xpt
 @BINPATH@/components/dom_mobileconnection.xpt
 #endif
 #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/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1391730565000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1392162665000">
   <emItems>
       <emItem  blockID="i454" id="sqlmoz@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                                 <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
@@ -756,17 +756,17 @@
     </emItem>
       <emItem  blockID="i394" id="{7D4F1959-3F72-49d5-8E59-F02F8AA6815D}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i507" id="4zffxtbr-bs@VideoDownloadConverter_4z.com">
-                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                        <versionRange  minVersion="0" maxVersion="5.75.3.25126" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i7" id="{2224e955-00e9-4613-a844-ce69fccaae91}">
                           <prefs>
               </prefs>
     </emItem>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1355,16 +1355,19 @@ pref("geo.wifi.uri", "https://www.google
 
 // Necko IPC security checks only needed for app isolation for cookies/cache/etc:
 // currently irrelevant for desktop e10s
 pref("network.disable.ipc.security", true);
 
 // CustomizableUI debug logging.
 pref("browser.uiCustomization.debug", false);
 
+// CustomizableUI state of the browser's user interface
+pref("browser.uiCustomization.state", "");
+
 // The URL where remote content that composes the UI for Firefox Accounts should
 // be fetched. Must use HTTPS.
 pref("identity.fxaccounts.remote.uri", "https://accounts.firefox.com/?service=sync&context=fx_desktop_v1");
 
 // The URL where remote content that forces re-authentication for Firefox Accounts
 // should be fetched.  Must use HTTPS.
 pref("identity.fxaccounts.remote.force_auth.uri", "https://accounts.firefox.com/force_auth?service=sync&context=fx_desktop_v1");
 
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -45,19 +45,16 @@
              oncommand="gFindBar.onFindCommand();"
              observes="isImage"/>
     <command id="cmd_findAgain"
              oncommand="gFindBar.onFindAgainCommand(false);"
              observes="isImage"/>
     <command id="cmd_findPrevious"
              oncommand="gFindBar.onFindAgainCommand(true);"
              observes="isImage"/>
-#ifdef XP_MACOSX
-    <command id="cmd_findSelection" oncommand="gFindBar.onFindSelectionCommand();"/>
-#endif
     <!-- work-around bug 392512 -->
     <command id="Browser:AddBookmarkAs"
              oncommand="PlacesCommandHook.bookmarkCurrentPage(true, PlacesUtils.bookmarksMenuFolderId);"/>
     <!-- The command disabled state must be manually updated through
          PlacesCommandHook.updateBookmarkAllTabsCommand() -->
     <command id="Browser:BookmarkAllTabs"
              oncommand="PlacesCommandHook.bookmarkCurrentPages();"/>
     <command id="Browser:Home"    oncommand="BrowserHome();"/>
@@ -348,19 +345,16 @@
     <key key="&reloadCmd.commandkey;" command="Browser:ReloadSkipCache" modifiers="accel,shift"/>
     <key id="key_viewSource" key="&pageSourceCmd.commandkey;" command="View:PageSource" modifiers="accel"/>
 #ifndef XP_WIN
     <key id="key_viewInfo"   key="&pageInfoCmd.commandkey;"   command="View:PageInfo"   modifiers="accel"/>
 #endif
     <key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
     <key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
     <key id="key_findPrevious" key="&findAgainCmd.commandkey;" command="cmd_findPrevious" modifiers="accel,shift"/>
-#ifdef XP_MACOSX
-    <key id="key_findSelection" key="&findSelectionCmd.commandkey;" command="cmd_findSelection" modifiers="accel"/>
-#endif
     <key keycode="&findAgainCmd.commandkey2;" command="cmd_findAgain"/>
     <key keycode="&findAgainCmd.commandkey2;"  command="cmd_findPrevious" modifiers="shift"/>
 
     <key id="addBookmarkAsKb" key="&bookmarkThisPageCmd.commandkey;" command="Browser:AddBookmarkAs" modifiers="accel"/>
 # Accel+Shift+A-F are reserved on GTK
 #ifndef MOZ_WIDGET_GTK
     <key id="bookmarkAllTabsKb" key="&bookmarkThisPageCmd.commandkey;" oncommand="PlacesCommandHook.bookmarkCurrentPages();" modifiers="accel,shift"/>
     <key id="manBookmarkKb" key="&bookmarksCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4233,16 +4233,17 @@ function onViewToolbarsPopupShowing(aEve
   let toolbarItem = popup.triggerNode;
 
   if (toolbarItem && toolbarItem.localName == "toolbarpaletteitem") {
     toolbarItem = toolbarItem.firstChild;
   } else {
     while (toolbarItem && toolbarItem.parentNode) {
       let parent = toolbarItem.parentNode;
       if ((parent.classList && parent.classList.contains("customization-target")) ||
+          parent.getAttribute("overflowfortoolbar") || // Needs to work in the overflow list as well.
           parent.localName == "toolbarpaletteitem" ||
           parent.localName == "toolbar")
         break;
       toolbarItem = parent;
     }
   }
 
   // Right-clicking on an empty part of the tabstrip will exit
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -264,17 +264,24 @@
                 label="&customizeMenu.moveToPanel.label;"
                 class="customize-context-moveToPanel"/>
       <menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode)"
                 accesskey="&customizeMenu.removeFromToolbar.accesskey;"
                 label="&customizeMenu.removeFromToolbar.label;"
                 class="customize-context-removeFromToolbar"/>
       <menuseparator/>
       <menuseparator id="viewToolbarsMenuSeparator"/>
-      <menuitem command="cmd_CustomizeToolbars"
+      <!-- XXXgijs: we're using oncommand handler here to avoid the event being
+                    redirected to the command element, thus preventing
+                    listeners on the menupopup or further up the tree from
+                    seeing the command event pass by. The observes attribute is
+                    here so that the menuitem is still disabled and re-enabled
+                    correctly. -->
+      <menuitem oncommand="BrowserCustomizeToolbar()"
+                observes="cmd_CustomizeToolbars"
                 class="viewCustomizeToolbar"
                 label="&viewCustomizeToolbar.label;"
                 accesskey="&viewCustomizeToolbar.accesskey;"/>
     </menupopup>
 
     <menupopup id="blockedPopupOptions"
                onpopupshowing="gPopupBlockerObserver.fillPopupList(event);"
                onpopuphiding="gPopupBlockerObserver.onPopupHiding(event);">
--- a/browser/base/content/test/general/browser_bug537013.js
+++ b/browser/base/content/test/general/browser_bug537013.js
@@ -6,19 +6,16 @@
 let tabs = [];
 let texts = [
   "This side up.",
   "The world is coming to an end. Please log off.",
   "Klein bottle for sale. Inquire within.",
   "To err is human; to forgive is not company policy."
 ];
 
-let Clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
-let HasFindClipboard = Clipboard.supportsFindClipboard();
-
 function addTabWithText(aText, aCallback) {
   let newTab = gBrowser.addTab("data:text/html,<h1 id='h1'>" + aText + "</h1>");
   tabs.push(newTab);
   gBrowser.selectedTab = newTab;
 }
 
 function setFindString(aString) {
   gFindBar.open();
@@ -59,19 +56,17 @@ function continueTests1() {
   gFindBar.open();
   is(gFindBar._findField.value, texts[0],
      "Second tab kept old find value for new initialization!");
   setFindString(texts[1]);
 
   // Confirm the first tab is still correct, ensure re-hiding works as expected
   gBrowser.selectedTab = tabs[0];
   ok(!gFindBar.hidden, "First tab shows find bar!");
-  // When the Find Clipboard is supported, this test not relevant.
-  if (!HasFindClipboard)
-    is(gFindBar._findField.value, texts[0], "First tab persists find value!");
+  is(gFindBar._findField.value, texts[0], "First tab persists find value!");
   ok(gFindBar.getElement("highlight").checked,
      "Highlight button state persists!");
 
   // While we're here, let's test bug 253793
   gBrowser.reload();
   gBrowser.addEventListener("DOMContentLoaded", continueTests2, true);
 }
 
@@ -94,27 +89,25 @@ function continueTests2() {
 
   // Now we jump to the second, then first, and then fourth
   gBrowser.selectedTab = tabs[1];
   gBrowser.selectedTab = tabs[0];
   gBrowser.selectedTab = tabs[3];
   ok(gFindBar.hidden, "Fourth tab doesn't show find bar!");
   is(gFindBar, gBrowser.getFindBar(), "Find bar is right one!");
   gFindBar.open();
-  let toTest = HasFindClipboard ? texts[2] : texts[1];
-  is(gFindBar._findField.value, toTest,
+  is(gFindBar._findField.value, texts[1],
      "Fourth tab has second tab's find value!");
 
   newWindow = gBrowser.replaceTabWithWindow(tabs.pop());
   whenDelayedStartupFinished(newWindow, checkNewWindow);
 }
 
 // Test that findbar gets restored when a tab is moved to a new window.
 function checkNewWindow() {
   ok(!newWindow.gFindBar.hidden, "New window shows find bar!");
-  let toTest = HasFindClipboard ? texts[2] : texts[1];
-  is(newWindow.gFindBar._findField.value, toTest,
+  is(newWindow.gFindBar._findField.value, texts[1],
      "New window find bar has correct find value!");
   ok(!newWindow.gFindBar.getElement("find-next").disabled,
      "New window findbar has enabled buttons!");
   newWindow.close();
   finish();
 }
--- a/browser/base/content/test/general/browser_bug567306.js
+++ b/browser/base/content/test/general/browser_bug567306.js
@@ -1,16 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-const {Ci: interfaces, Cc: classes} = Components;
-
-let Clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
-let HasFindClipboard = Clipboard.supportsFindClipboard();
+let Ci = Components.interfaces;
 
 function test() {
   waitForExplicitFinish();
 
   whenNewWindowLoaded(undefined, function (win) {
     whenDelayedStartupFinished(win, function () {
       let selectedBrowser = win.gBrowser.selectedBrowser;
       selectedBrowser.addEventListener("pageshow", function() {
@@ -35,16 +32,13 @@ function selectText(win) {
   selection.addRange(range);
 }
 
 function onFocus(win) {
   ok(!win.gFindBarInitialized, "find bar is not yet initialized");
   let findBar = win.gFindBar;
   selectText(win.content);
   findBar.onFindCommand();
-  // When the OS supports the Find Clipboard (OSX), the find field value is
-  // persisted across Fx sessions, thus not useful to test.
-  if (!HasFindClipboard)
-    is(findBar._findField.value, "Select Me", "Findbar is initialized with selection");
+  is(findBar._findField.value, "Select Me", "Findbar is initialized with selection");
   findBar.close();
   win.close();
   finish();
 }
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -90,17 +90,17 @@ static RedirEntry kRedirMap[] = {
 #ifdef MOZ_SERVICES_HEALTHREPORT
   { "healthreport", "chrome://browser/content/abouthealthreport/abouthealth.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
 #endif
   { "accounts", "chrome://browser/content/aboutaccounts/aboutaccounts.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
   { "app-manager", "chrome://browser/content/devtools/app-manager/index.xul",
     nsIAboutModule::ALLOW_SCRIPT },
-  { "customizing", "chrome://browser/content/customizableui/aboutCustomizing.xhtml",
+  { "customizing", "chrome://browser/content/customizableui/aboutCustomizing.xul",
     nsIAboutModule::ALLOW_SCRIPT },
 };
 static const int kRedirTotal = ArrayLength(kRedirMap);
 
 static nsAutoCString
 GetAboutModuleName(nsIURI *aURI)
 {
   nsAutoCString path;
deleted file mode 100644
--- a/browser/components/customizableui/content/aboutCustomizing.xhtml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 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/. -->
-
-<!DOCTYPE html [
-  <!ENTITY % htmlDTD
-    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-    "DTD/xhtml1-strict.dtd">
-  %htmlDTD;
-  <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
-  %brandDTD;
-  <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
-  %browserDTD;
-]>
-
-<html xmlns="http://www.w3.org/1999/xhtml"
-      disablefastfind="true">
-	<head>
-		<title>&customizeMode.tabTitle;</title>
-		<link rel="icon" type="image/x-icon"
-		      href="chrome://browser/skin/customizableui/customizeFavicon.ico"/>
-	</head>
-	<body></body>
-</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/content/aboutCustomizing.xul
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- 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/. -->
+
+<!DOCTYPE window [
+  <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+  %brandDTD;
+  <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+  %browserDTD;
+]>
+
+<window id="aboutCustomizingWindow"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        xmlns:html="http://www.w3.org/1999/xhtml"
+        title="&customizeMode.tabTitle;">
+  <html:head>
+    <html:link rel="icon" type="image/x-icon"
+               href="chrome://browser/skin/customizableui/customizeFavicon.ico"/>
+  </html:head>
+</window>
--- a/browser/components/customizableui/content/customizeMode.inc.xul
+++ b/browser/components/customizableui/content/customizeMode.inc.xul
@@ -25,17 +25,17 @@
 #NB: because oncommand fires after click, by the time we've fired, the checkbox binding
 #    will already have switched the button's state, so this is correct:
               oncommand="gCustomizeMode.toggleTitlebar(this.hasAttribute('checked'))"/>
 #endif
       <button id="customization-toolbar-visibility-button" label="&customizeMode.toolbars;" class="customizationmode-button" type="menu">
         <menupopup id="customization-toolbar-menu" onpopupshowing="onViewToolbarsPopupShowing(event)"/>
       </button>
       <spacer flex="1"/>
-      <button id="customization-undo-reset"
+      <button id="customization-undo-reset-button"
               class="customizationmode-button"
               hidden="true"
               oncommand="gCustomizeMode.undoReset();"
               label="&undoCmd.label;"/>
       <button id="customization-reset-button" oncommand="gCustomizeMode.reset();" label="&customizeMode.restoreDefaults;" class="customizationmode-button"/>
     </hbox>
   </vbox>
   <vbox id="customization-panel-container">
--- a/browser/components/customizableui/content/jar.mn
+++ b/browser/components/customizableui/content/jar.mn
@@ -1,11 +1,11 @@
 # 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/.
 
 browser.jar:
-  content/browser/customizableui/aboutCustomizing.xhtml
+  content/browser/customizableui/aboutCustomizing.xul
   content/browser/customizableui/panelUI.css
 * content/browser/customizableui/panelUI.js
   content/browser/customizableui/panelUI.xml
   content/browser/customizableui/toolbar.xml
 
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -217,13 +217,15 @@
     </svg:defs>
   </svg:svg>
 </panel>
 
 <panel id="widget-overflow"
        role="group"
        type="arrow"
        level="top"
+       context="toolbar-context-menu"
        hidden="true">
   <vbox id="widget-overflow-scroller">
-    <vbox id="widget-overflow-list" class="widget-overflow-list"/>
+    <vbox id="widget-overflow-list" class="widget-overflow-list"
+          overflowfortoolbar="nav-bar"/>
   </vbox>
 </panel>
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -31,16 +31,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
 
 const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 const kSpecialWidgetPfx = "customizableui-special-";
 
 const kPrefCustomizationState        = "browser.uiCustomization.state";
 const kPrefCustomizationAutoAdd      = "browser.uiCustomization.autoAdd";
 const kPrefCustomizationDebug        = "browser.uiCustomization.debug";
+const kPrefDrawInTitlebar            = "browser.tabs.drawInTitlebar";
 
 /**
  * The keys are the handlers that are fired when the event type (the value)
  * is fired on the subview. A widget that provides a subview has the option
  * of providing onViewShowing and onViewHiding event handlers.
  */
 const kSubviewEvents = [
   "ViewShowing",
@@ -123,17 +124,20 @@ let gBuildAreas = new Map();
  */
 let gBuildWindows = new Map();
 
 let gNewElementCount = 0;
 let gGroupWrapperCache = new Map();
 let gSingleWrapperCache = new WeakMap();
 let gListeners = new Set();
 
-let gUIStateBeforeReset = null;
+let gUIStateBeforeReset = {
+  uiCustomizationState: null,
+  drawInTitlebar: null,
+};
 
 let gModuleName = "[CustomizableUI]";
 #include logging.js
 
 let CustomizableUIInternal = {
   initialize: function() {
     LOG("Initializing");
 
@@ -692,17 +696,17 @@ let CustomizableUIInternal = {
 
     this.registerBuildArea(CustomizableUI.AREA_PANEL, aPanelContents);
   },
 
   onWidgetAdded: function(aWidgetId, aArea, aPosition) {
     this.insertNode(aWidgetId, aArea, aPosition, true);
 
     if (!gResetting) {
-      gUIStateBeforeReset = null;
+      this._clearPreviousUIState();
     }
   },
 
   onWidgetRemoved: function(aWidgetId, aArea) {
     let areaNodes = gBuildAreas.get(aArea);
     if (!areaNodes) {
       return;
     }
@@ -751,29 +755,29 @@ let CustomizableUIInternal = {
       }
 
       let windowCache = gSingleWrapperCache.get(window);
       if (windowCache) {
         windowCache.delete(aWidgetId);
       }
     }
     if (!gResetting) {
-      gUIStateBeforeReset = null;
+      this._clearPreviousUIState();
     }
   },
 
   onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
     this.insertNode(aWidgetId, aArea, aNewPosition);
     if (!gResetting) {
-      gUIStateBeforeReset = null;
+      this._clearPreviousUIState();
     }
   },
 
   onCustomizeEnd: function(aWindow) {
-    gUIStateBeforeReset = null;
+    this._clearPreviousUIState();
   },
 
   registerBuildArea: function(aArea, aNode) {
     // We ensure that the window is registered to have its customization data
     // cleaned up when unloading.
     let window = aNode.ownerDocument.defaultView;
     if (window.closed) {
       return;
@@ -2070,20 +2074,22 @@ let CustomizableUIInternal = {
     // was reset above.
     this._rebuildRegisteredAreas();
 
     gResetting = false;
   },
 
   _resetUIState: function() {
     try {
-      gUIStateBeforeReset = Services.prefs.getCharPref(kPrefCustomizationState);
+      gUIStateBeforeReset.drawInTitlebar = Services.prefs.getBoolPref(kPrefDrawInTitlebar);
+      gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState);
     } catch(e) { }
 
     Services.prefs.clearUserPref(kPrefCustomizationState);
+    Services.prefs.clearUserPref(kPrefDrawInTitlebar);
     LOG("State reset");
 
     // Reset placements to make restoring default placements possible.
     gPlacements = new Map();
     gDirtyAreaCache = new Set();
     gSeenWidgets = new Set();
     // Clear the saved state to ensure that defaults will be used.
     gSavedState = null;
@@ -2108,27 +2114,41 @@ let CustomizableUIInternal = {
       }
     }
   },
 
   /**
    * Undoes a previous reset, restoring the state of the UI to the state prior to the reset.
    */
   undoReset: function() {
-    if (!gUIStateBeforeReset) {
+    if (gUIStateBeforeReset.uiCustomizationState == null ||
+        gUIStateBeforeReset.drawInTitlebar == null) {
       return;
     }
-    Services.prefs.setCharPref(kPrefCustomizationState, gUIStateBeforeReset);
+    let uiCustomizationState = gUIStateBeforeReset.uiCustomizationState;
+    let drawInTitlebar = gUIStateBeforeReset.drawInTitlebar;
+
+    // Need to clear the previous state before setting the prefs
+    // because pref observers may check if there is a previous UI state.
+    this._clearPreviousUIState();
+
+    Services.prefs.setCharPref(kPrefCustomizationState, uiCustomizationState);
+    Services.prefs.setBoolPref(kPrefDrawInTitlebar, drawInTitlebar);
     this.loadSavedState();
     for (let areaId of Object.keys(gSavedState.placements)) {
       let placements = gSavedState.placements[areaId];
       gPlacements.set(areaId, placements);
     }
     this._rebuildRegisteredAreas();
-    gUIStateBeforeReset = null;
+  },
+
+  _clearPreviousUIState: function() {
+    Object.getOwnPropertyNames(gUIStateBeforeReset).forEach((prop) => {
+      gUIStateBeforeReset[prop] = null;
+    });
   },
 
   /**
    * @param {String|Node} aWidget - widget ID or a widget node (preferred for performance).
    * @return {Boolean} whether the widget is removable
    */
   isWidgetRemovable: function(aWidget) {
     let widgetId;
@@ -2265,16 +2285,21 @@ let CustomizableUIInternal = {
         if (currentPlacements[i] != defaultPlacements[i]) {
           LOG("Found " + currentPlacements[i] + " in " + areaId + " where " +
               defaultPlacements[i] + " was expected!");
           return false;
         }
       }
     }
 
+    if (Services.prefs.prefHasUserValue(kPrefDrawInTitlebar)) {
+      LOG(kPrefDrawInTitlebar + " pref is non-default");
+      return false;
+    }
+
     return true;
   }
 };
 Object.freeze(CustomizableUIInternal);
 
 this.CustomizableUI = {
   /**
    * Constant reference to the ID of the menu panel.
@@ -2888,17 +2913,18 @@ this.CustomizableUI = {
 
   /**
    * Can the last Restore Defaults operation be undone.
    *
    * @return A boolean stating whether an undo of the
    *         Restore Defaults can be performed.
    */
   get canUndoReset() {
-    return !!gUIStateBeforeReset;
+    return gUIStateBeforeReset.uiCustomizationState != null ||
+           gUIStateBeforeReset.drawInTitlebar != null;
   },
 
   /**
    * Get the placement of a widget. This is by far the best way to obtain
    * information about what the state of your widget is. The internals of
    * this call are cheap (no DOM necessary) and you will know where the user
    * has put your widget.
    *
@@ -3390,17 +3416,21 @@ OverflowableToolbar.prototype = {
   },
 
   handleEvent: function(aEvent) {
     switch(aEvent.type) {
       case "resize":
         this._onResize(aEvent);
         break;
       case "command":
-        this._onClickChevron(aEvent);
+        if (aEvent.target == this._chevron) {
+          this._onClickChevron(aEvent);
+        } else {
+          this._panel.hidePopup();
+        }
         break;
       case "popuphiding":
         this._onPanelHiding(aEvent);
         break;
       case "customizationstarting":
         this._disable();
         break;
       case "aftercustomization":
@@ -3425,29 +3455,34 @@ OverflowableToolbar.prototype = {
       this.removeEventListener("popupshown", onPopupShown);
       deferred.resolve();
     });
 
     return deferred.promise;
   },
 
   _onClickChevron: function(aEvent) {
-    if (this._chevron.open)
+    if (this._chevron.open) {
       this._panel.hidePopup();
-    else {
+    } else {
       let doc = aEvent.target.ownerDocument;
       this._panel.hidden = false;
+      let contextMenu = doc.getElementById(this._panel.getAttribute("context"));
+      gELS.addSystemEventListener(contextMenu, 'command', this, true);
       let anchor = doc.getAnonymousElementByAttribute(this._chevron, "class", "toolbarbutton-icon");
       this._panel.openPopup(anchor || this._chevron, "bottomcenter topright");
     }
     this._chevron.open = !this._chevron.open;
   },
 
   _onPanelHiding: function(aEvent) {
     this._chevron.open = false;
+    let doc = aEvent.target.ownerDocument;
+    let contextMenu = doc.getElementById(this._panel.getAttribute("context"));
+    gELS.removeSystemEventListener(contextMenu, 'command', this, true);
   },
 
   onOverflow: function(aEvent) {
     if (!this._enabled ||
         (aEvent && aEvent.target != this._toolbar.customizationTarget))
       return;
 
     let child = this._target.lastChild;
--- a/browser/components/customizableui/src/CustomizeMode.jsm
+++ b/browser/components/customizableui/src/CustomizeMode.jsm
@@ -492,22 +492,44 @@ CustomizeMode.prototype = {
 
   dispatchToolboxEvent: function(aEventType, aDetails={}) {
     let evt = this.document.createEvent("CustomEvent");
     evt.initCustomEvent(aEventType, true, true, {changed: this._changed});
     let result = this.window.gNavToolbox.dispatchEvent(evt);
   },
 
   _getCustomizableChildForNode: function(aNode) {
-    let area = this._getCustomizableParent(aNode);
-    area = area.customizationTarget || area;
-    while (aNode && aNode.parentNode != area) {
-      aNode = aNode.parentNode;
+    // NB: adjusted from _getCustomizableParent to keep that method fast
+    // (it's used during drags), and avoid multiple DOM loops
+    let areas = CustomizableUI.areas;
+    // Caching this length is important because otherwise we'll also iterate
+    // over items we add to the end from within the loop.
+    let numberOfAreas = areas.length;
+    for (let i = 0; i < numberOfAreas; i++) {
+      let area = areas[i];
+      let areaNode = aNode.ownerDocument.getElementById(area);
+      let customizationTarget = areaNode && areaNode.customizationTarget;
+      if (customizationTarget && customizationTarget != areaNode) {
+        areas.push(customizationTarget.id);
+      }
+      let overflowTarget = areaNode.getAttribute("overflowtarget");
+      if (overflowTarget) {
+        areas.push(overflowTarget);
+      }
     }
-    return aNode;
+    areas.push(kPaletteId);
+
+    while (aNode && aNode.parentNode) {
+      let parent = aNode.parentNode;
+      if (areas.indexOf(parent.id) != -1) {
+        return aNode;
+      }
+      aNode = parent;
+    }
+    return null;
   },
 
   addToToolbar: function(aNode) {
     aNode = this._getCustomizableChildForNode(aNode);
     if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
       aNode = aNode.firstChild;
     }
     CustomizableUI.addWidgetToArea(aNode.id, CustomizableUI.AREA_NAVBAR);
@@ -994,18 +1016,18 @@ CustomizeMode.prototype = {
   },
 
   _updateResetButton: function() {
     let btn = this.document.getElementById("customization-reset-button");
     btn.disabled = CustomizableUI.inDefaultState;
   },
 
   _updateUndoResetButton: function() {
-    let undoReset =  this.document.getElementById("customization-undo-reset");
-    undoReset.hidden = !CustomizableUI.canUndoReset;
+    let undoResetButton =  this.document.getElementById("customization-undo-reset-button");
+    undoResetButton.hidden = !CustomizableUI.canUndoReset;
   },
 
   handleEvent: function(aEvent) {
     switch(aEvent.type) {
       case "toolbarvisibilitychange":
         this._onToolbarVisibilityChange(aEvent);
         break;
       case "dragstart":
@@ -1047,17 +1069,19 @@ CustomizeMode.prototype = {
 #endif
     }
   },
 
 #ifdef CAN_DRAW_IN_TITLEBAR
   observe: function(aSubject, aTopic, aData) {
     switch (aTopic) {
       case "nsPref:changed":
+        this._updateResetButton();
         this._updateTitlebarButton();
+        this._updateUndoResetButton();
         break;
     }
   },
 
   _updateTitlebarButton: function() {
     let drawInTitlebar = true;
     try {
       drawInTitlebar = Services.prefs.getBoolPref(kDrawInTitlebarPref);
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -6,16 +6,18 @@ support-files =
 [browser_876926_customize_mode_wrapping.js]
 [browser_876944_customize_mode_create_destroy.js]
 [browser_877006_missing_view.js]
 [browser_877178_unregisterArea.js]
 [browser_877447_skip_missing_ids.js]
 [browser_878452_drag_to_panel.js]
 [browser_880164_customization_context_menus.js]
 [browser_880382_drag_wide_widgets_in_panel.js]
+[browser_884402_customize_from_overflow.js]
+skip-if = os == "linux"
 [browser_885052_customize_mode_observers_disabed.js]
 # Bug 951403 - Disabled on OSX for frequent failures
 skip-if = os == "mac"
 
 [browser_885530_showInPrivateBrowsing.js]
 [browser_886323_buildArea_removable_nodes.js]
 [browser_887438_currentset_shim.js]
 [browser_888817_currentset_updating.js]
--- a/browser/components/customizableui/test/browser_880164_customization_context_menus.js
+++ b/browser/components/customizableui/test/browser_880164_customization_context_menus.js
@@ -300,63 +300,8 @@ add_task(function() {
   contextMenu.hidePopup();
   yield hiddenContextPromise;
 
   let hiddenPromise = promisePanelHidden(window);
   PanelUI.hide();
   yield hiddenPromise;
 });
 
-function contextMenuShown(aContextMenu) {
-  let deferred = Promise.defer();
-  let win = aContextMenu.ownerDocument.defaultView;
-  let timeoutId = win.setTimeout(() => {
-    deferred.reject("Context menu (" + aContextMenu.id + ") did not show within 20 seconds.");
-  }, 20000);
-  function onPopupShown(e) {
-    aContextMenu.removeEventListener("popupshown", onPopupShown);
-    win.clearTimeout(timeoutId);
-    deferred.resolve();
-  };
-  aContextMenu.addEventListener("popupshown", onPopupShown);
-  return deferred.promise;
-}
-
-function contextMenuHidden(aContextMenu) {
-  let deferred = Promise.defer();
-  let win = aContextMenu.ownerDocument.defaultView;
-  let timeoutId = win.setTimeout(() => {
-    deferred.reject("Context menu (" + aContextMenu.id + ") did not hide within 20 seconds.");
-  }, 20000);
-  function onPopupHidden(e) {
-    win.clearTimeout(timeoutId);
-    aContextMenu.removeEventListener("popuphidden", onPopupHidden);
-    deferred.resolve();
-  };
-  aContextMenu.addEventListener("popuphidden", onPopupHidden);
-  return deferred.promise;
-}
-
-// This is a simpler version of the context menu check that
-// exists in contextmenu_common.js.
-function checkContextMenu(aContextMenu, aExpectedEntries, aWindow=window) {
-  let childNodes = aContextMenu.childNodes;
-  for (let i = 0; i < childNodes.length; i++) {
-    let menuitem = childNodes[i];
-    try {
-      if (aExpectedEntries[i][0] == "---") {
-        is(menuitem.localName, "menuseparator", "menuseparator expected");
-        continue;
-      }
-
-      let selector = aExpectedEntries[i][0];
-      ok(menuitem.mozMatchesSelector(selector), "menuitem should match " + selector + " selector");
-      let commandValue = menuitem.getAttribute("command");
-      let relatedCommand = commandValue ? aWindow.document.getElementById(commandValue) : null;
-      let menuItemDisabled = relatedCommand ?
-                               relatedCommand.getAttribute("disabled") == "true" :
-                               menuitem.getAttribute("disabled") == "true";
-      is(menuItemDisabled, !aExpectedEntries[i][1], "disabled state for " + selector);
-    } catch (e) {
-      ok(false, "Exception when checking context menu: " + e);
-    }
-  }
-}
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_884402_customize_from_overflow.js
@@ -0,0 +1,78 @@
+"use strict";
+
+let overflowPanel = document.getElementById("widget-overflow");
+
+const isOSX = (Services.appinfo.OS === "Darwin");
+
+let originalWindowWidth;
+registerCleanupFunction(function() {
+  window.resizeTo(originalWindowWidth, window.outerHeight);
+});
+
+// Right-click on an item within the overflow panel should
+// show a context menu with options to move it.
+add_task(function() {
+  originalWindowWidth = window.outerWidth;
+  let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+  ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
+  let oldChildCount = navbar.customizationTarget.childElementCount;
+  window.resizeTo(400, window.outerHeight);
+
+  yield waitForCondition(() => navbar.hasAttribute("overflowing"));
+  ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+
+  let chevron = document.getElementById("nav-bar-overflow-button");
+  let shownPanelPromise = promisePanelElementShown(window, overflowPanel);
+  chevron.click();
+  yield shownPanelPromise;
+
+  let contextMenu = document.getElementById("toolbar-context-menu");
+  let shownContextPromise = contextMenuShown(contextMenu);
+  let homeButton = document.getElementById("home-button");
+  ok(homeButton, "home-button was found");
+  ok(homeButton.classList.contains("overflowedItem"), "Home button is overflowing");
+  EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2});
+  yield shownContextPromise;
+
+  is(overflowPanel.state, "open", "The widget overflow panel should still be open.");
+
+  let expectedEntries = [
+    [".customize-context-moveToPanel", true],
+    [".customize-context-removeFromToolbar", true],
+    ["---"]
+  ];
+  if (!isOSX) {
+    expectedEntries.push(["#toggle_toolbar-menubar", true]);
+  }
+  expectedEntries.push(
+    ["#toggle_PersonalToolbar", true],
+    ["---"],
+    [".viewCustomizeToolbar", true]
+  );
+  checkContextMenu(contextMenu, expectedEntries);
+
+  let hiddenContextPromise = contextMenuHidden(contextMenu);
+  let hiddenPromise = promisePanelElementHidden(window, overflowPanel);
+  let moveToPanel = contextMenu.querySelector(".customize-context-moveToPanel");
+  if (moveToPanel) {
+    moveToPanel.click();
+  }
+  contextMenu.hidePopup();
+  yield hiddenContextPromise;
+  yield hiddenPromise;
+
+  let homeButtonPlacement = CustomizableUI.getPlacementOfWidget("home-button");
+  ok(homeButtonPlacement, "Home button should still have a placement");
+  is(homeButtonPlacement && homeButtonPlacement.area, "PanelUI-contents", "Home button should be in the panel now");
+  CustomizableUI.reset();
+
+  // In some cases, it can take a tick for the navbar to overflow again. Wait for it:
+  yield waitForCondition(() => navbar.hasAttribute("overflowing"));
+  ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+
+  let homeButtonPlacement = CustomizableUI.getPlacementOfWidget("home-button");
+  ok(homeButtonPlacement, "Home button should still have a placement");
+  is(homeButtonPlacement && homeButtonPlacement.area, "nav-bar", "Home button should be back in the navbar now");
+
+  ok(homeButton.classList.contains("overflowedItem"), "Home button should still be overflowed");
+});
--- a/browser/components/customizableui/test/browser_970511_undo_restore_default.js
+++ b/browser/components/customizableui/test/browser_970511_undo_restore_default.js
@@ -6,60 +6,102 @@
 
 // Restoring default should show an "undo" option which undoes the restoring operation.
 add_task(function() {
   let homeButtonId = "home-button";
   CustomizableUI.removeWidgetFromArea(homeButtonId);
   yield startCustomizing();
   ok(!CustomizableUI.inDefaultState, "Not in default state to begin with");
   is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette");
-  let undoReset = document.getElementById("customization-undo-reset");
-  is(undoReset.hidden, true, "The undo button is hidden before reset");
+  let undoResetButton = document.getElementById("customization-undo-reset-button");
+  is(undoResetButton.hidden, true, "The undo button is hidden before reset");
 
   yield gCustomizeMode.reset();
 
   ok(CustomizableUI.inDefaultState, "In default state after reset");
-  is(undoReset.hidden, false, "The undo button is visible after reset");
+  is(undoResetButton.hidden, false, "The undo button is visible after reset");
 
-  undoReset.click();
+  undoResetButton.click();
   yield waitForCondition(function() !gCustomizeMode.resetting);
   ok(!CustomizableUI.inDefaultState, "Not in default state after reset-undo");
-  is(undoReset.hidden, true, "The undo button is hidden after clicking on the undo button");
+  is(undoResetButton.hidden, true, "The undo button is hidden after clicking on the undo button");
   is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette");
 
   yield gCustomizeMode.reset();
 });
 
 // Performing an action after a reset will hide the reset button.
 add_task(function() {
   let homeButtonId = "home-button";
   CustomizableUI.removeWidgetFromArea(homeButtonId);
   ok(!CustomizableUI.inDefaultState, "Not in default state to begin with");
   is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette");
-  let undoReset = document.getElementById("customization-undo-reset");
-  is(undoReset.hidden, true, "The undo button is hidden before reset");
+  let undoResetButton = document.getElementById("customization-undo-reset-button");
+  is(undoResetButton.hidden, true, "The undo button is hidden before reset");
 
   yield gCustomizeMode.reset();
 
   ok(CustomizableUI.inDefaultState, "In default state after reset");
-  is(undoReset.hidden, false, "The undo button is visible after reset");
+  is(undoResetButton.hidden, false, "The undo button is visible after reset");
 
   CustomizableUI.addWidgetToArea(homeButtonId, CustomizableUI.AREA_PANEL);
-  is(undoReset.hidden, true, "The undo button is hidden after another change");
+  is(undoResetButton.hidden, true, "The undo button is hidden after another change");
 });
 
 // "Restore defaults", exiting customize, and re-entering shouldn't show the Undo button
 add_task(function() {
-  let undoReset = document.getElementById("customization-undo-reset");
-  is(undoReset.hidden, true, "The undo button is hidden before a reset");
+  let undoResetButton = document.getElementById("customization-undo-reset-button");
+  is(undoResetButton.hidden, true, "The undo button is hidden before a reset");
   ok(!CustomizableUI.inDefaultState, "The browser should not be in default state");
   yield gCustomizeMode.reset();
 
-  is(undoReset.hidden, false, "The undo button is hidden after a reset");
+  is(undoResetButton.hidden, false, "The undo button is visible after a reset");
   yield endCustomizing();
   yield startCustomizing();
-  is(undoReset.hidden, true, "The undo reset button should be hidden after entering customization mode");
+  is(undoResetButton.hidden, true, "The undo reset button should be hidden after entering customization mode");
+});
+
+// Bug 971626 - Restore Defaults should collapse the Title Bar
+add_task(function() {
+  if (Services.appinfo.OS != "WINNT" &&
+      Services.appinfo.OS != "Darwin") {
+    return;
+  }
+  let prefName = "browser.tabs.drawInTitlebar";
+  let defaultValue = Services.prefs.getBoolPref(prefName);
+  let restoreDefaultsButton = document.getElementById("customization-reset-button");
+  let titleBarButton = document.getElementById("customization-titlebar-visibility-button");
+  let undoResetButton = document.getElementById("customization-undo-reset-button");
+  ok(CustomizableUI.inDefaultState, "Should be in default state at start of test");
+  ok(restoreDefaultsButton.disabled, "Restore defaults button should be disabled when in default state");
+  is(titleBarButton.hasAttribute("checked"), !defaultValue, "Title bar button should reflect pref value");
+  is(undoResetButton.hidden, true, "Undo reset button should be hidden at start of test");
+
+  Services.prefs.setBoolPref(prefName, !defaultValue);
+  ok(!restoreDefaultsButton.disabled, "Restore defaults button should be enabled when pref changed");
+  is(titleBarButton.hasAttribute("checked"), defaultValue, "Title bar button should reflect changed pref value");
+  ok(!CustomizableUI.inDefaultState, "With titlebar flipped, no longer default");
+  is(undoResetButton.hidden, true, "Undo reset button should be hidden after pref change");
+
+  yield gCustomizeMode.reset();
+  ok(restoreDefaultsButton.disabled, "Restore defaults button should be disabled after reset");
+  is(titleBarButton.hasAttribute("checked"), !defaultValue, "Title bar button should reflect default value after reset");
+  is(Services.prefs.getBoolPref(prefName), defaultValue, "Reset should reset drawInTitlebar");
+  ok(CustomizableUI.inDefaultState, "In default state after titlebar reset");
+  is(undoResetButton.hidden, false, "Undo reset button should be visible after reset");
+  ok(!undoResetButton.disabled, "Undo reset button should be enabled after reset");
+
+  yield gCustomizeMode.undoReset();
+  ok(!restoreDefaultsButton.disabled, "Restore defaults button should be enabled after undo-reset");
+  is(titleBarButton.hasAttribute("checked"), defaultValue, "Title bar button should reflect undo-reset value");
+  ok(!CustomizableUI.inDefaultState, "No longer in default state after undo");
+  is(Services.prefs.getBoolPref(prefName), !defaultValue, "Undo-reset goes back to previous pref value");
+  is(undoResetButton.hidden, true, "Undo reset button should be hidden after undo-reset clicked");
+
+  Services.prefs.clearUserPref(prefName);
+  ok(CustomizableUI.inDefaultState, "In default state after pref cleared");
+  is(undoResetButton.hidden, true, "Undo reset button should be hidden at end of test");
 });
 
 add_task(function asyncCleanup() {
   yield gCustomizeMode.reset();
   yield endCustomizing();
 });
--- a/browser/components/customizableui/test/head.js
+++ b/browser/components/customizableui/test/head.js
@@ -371,8 +371,65 @@ function promiseTabHistoryNavigation(aDi
     }
   }
   gBrowser.addEventListener("pageshow", listener, true);
 
   content.history.go(aDirection);
 
   return deferred.promise;
 }
+
+function contextMenuShown(aContextMenu) {
+  let deferred = Promise.defer();
+  let win = aContextMenu.ownerDocument.defaultView;
+  let timeoutId = win.setTimeout(() => {
+    deferred.reject("Context menu (" + aContextMenu.id + ") did not show within 20 seconds.");
+  }, 20000);
+  function onPopupShown(e) {
+    aContextMenu.removeEventListener("popupshown", onPopupShown);
+    win.clearTimeout(timeoutId);
+    deferred.resolve();
+  };
+  aContextMenu.addEventListener("popupshown", onPopupShown);
+  return deferred.promise;
+}
+
+function contextMenuHidden(aContextMenu) {
+  let deferred = Promise.defer();
+  let win = aContextMenu.ownerDocument.defaultView;
+  let timeoutId = win.setTimeout(() => {
+    deferred.reject("Context menu (" + aContextMenu.id + ") did not hide within 20 seconds.");
+  }, 20000);
+  function onPopupHidden(e) {
+    win.clearTimeout(timeoutId);
+    aContextMenu.removeEventListener("popuphidden", onPopupHidden);
+    deferred.resolve();
+  };
+  aContextMenu.addEventListener("popuphidden", onPopupHidden);
+  return deferred.promise;
+}
+
+
+// This is a simpler version of the context menu check that
+// exists in contextmenu_common.js.
+function checkContextMenu(aContextMenu, aExpectedEntries, aWindow=window) {
+  let childNodes = aContextMenu.childNodes;
+  for (let i = 0; i < childNodes.length; i++) {
+    let menuitem = childNodes[i];
+    try {
+      if (aExpectedEntries[i][0] == "---") {
+        is(menuitem.localName, "menuseparator", "menuseparator expected");
+        continue;
+      }
+
+      let selector = aExpectedEntries[i][0];
+      ok(menuitem.mozMatchesSelector(selector), "menuitem should match " + selector + " selector");
+      let commandValue = menuitem.getAttribute("command");
+      let relatedCommand = commandValue ? aWindow.document.getElementById(commandValue) : null;
+      let menuItemDisabled = relatedCommand ?
+                               relatedCommand.getAttribute("disabled") == "true" :
+                               menuitem.getAttribute("disabled") == "true";
+      is(menuItemDisabled, !aExpectedEntries[i][1], "disabled state for " + selector);
+    } catch (e) {
+      ok(false, "Exception when checking context menu: " + e);
+    }
+  }
+}
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -42,16 +42,17 @@ support-files =
   doc_editor-mode.html
   doc_empty-tab-01.html
   doc_empty-tab-02.html
   doc_event-listeners.html
   doc_event-listeners-02.html
   doc_frame-parameters.html
   doc_function-display-name.html
   doc_function-search.html
+  doc_global-method-override.html
   doc_iframes.html
   doc_included-script.html
   doc_inline-debugger-statement.html
   doc_inline-script.html
   doc_large-array-buffer.html
   doc_minified.html
   doc_minified_bogus_map.html
   doc_no-page-sources.html
@@ -117,16 +118,17 @@ support-files =
 [browser_dbg_controller-evaluate-01.js]
 [browser_dbg_controller-evaluate-02.js]
 [browser_dbg_debugger-statement.js]
 [browser_dbg_editor-contextmenu.js]
 [browser_dbg_editor-mode.js]
 [browser_dbg_event-listeners.js]
 [browser_dbg_file-reload.js]
 [browser_dbg_function-display-name.js]
+[browser_dbg_global-method-override.js]
 [browser_dbg_globalactor.js]
 [browser_dbg_host-layout.js]
 [browser_dbg_iframes.js]
 [browser_dbg_instruments-pane-collapse.js]
 [browser_dbg_listaddons.js]
 [browser_dbg_listtabs-01.js]
 [browser_dbg_listtabs-02.js]
 [browser_dbg_location-changes-01-simple.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_global-method-override.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that scripts that override properties of the global object, like
+ * toString don't break the debugger. The test page used to cause the debugger
+ * to throw when trying to attach to the thread actor.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_global-method-override.html";
+
+function test() {
+  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+    let gDebugger = aPanel.panelWin;
+    ok(gDebugger, "Should have a debugger available.");
+    is(gDebugger.gThreadClient.state, "attached", "Debugger should be attached.");
+
+    closeDebuggerAndFinish(aPanel);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/doc_global-method-override.html
@@ -0,0 +1,16 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Debugger global method override test page</title>
+  </head>
+  <body>
+    <script type="text/javascript">
+      console.log( "Error: " + toString( { x: 0, y: 0 } ) );
+      function toString(v) { return "[ " + v.x + ", " + v.y + " ]"; }
+    </script>
+  </body>
+</html>
--- a/browser/devtools/netmonitor/netmonitor-controller.js
+++ b/browser/devtools/netmonitor/netmonitor-controller.js
@@ -55,17 +55,20 @@ const EVENTS = {
   REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable",
 
   // When the response body is displayed in the UI.
   RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable",
 
   // When the html response preview is displayed in the UI.
   RESPONSE_HTML_PREVIEW_DISPLAYED: "NetMonitor:ResponseHtmlPreviewAvailable",
 
-  // When `onTabSelect` is fired and subsequently rendered.
+  // When the image response thumbnail is displayed in the UI.
+  RESPONSE_IMAGE_THUMBNAIL_DISPLAYED: "NetMonitor:ResponseImageThumbnailAvailable",
+
+  // When a tab is selected in the NetworkDetailsView and subsequently rendered.
   TAB_UPDATED: "NetMonitor:TabUpdated",
 
   // Fired when Sidebar has finished being populated.
   SIDEBAR_POPULATED: "NetMonitor:SidebarPopulated",
 
   // Fired when NetworkDetailsView has finished being populated.
   NETWORKDETAILSVIEW_POPULATED: "NetMonitor:NetworkDetailsViewPopulated",
 
@@ -100,16 +103,17 @@ Cu.import("resource:///modules/devtools/
 Cu.import("resource:///modules/devtools/VariablesView.jsm");
 Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 
 const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
 const EventEmitter = require("devtools/shared/event-emitter");
 const Editor = require("devtools/sourceeditor/editor");
+const {Tooltip} = require("devtools/shared/widgets/Tooltip");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Chart",
   "resource:///modules/devtools/Chart.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -6,16 +6,18 @@
 "use strict";
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const EPSILON = 0.001;
 const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 102400; // 100 KB in bytes
 const RESIZE_REFRESH_RATE = 50; // ms
 const REQUESTS_REFRESH_RATE = 50; // ms
 const REQUESTS_HEADERS_SAFE_BOUNDS = 30; // px
+const REQUESTS_TOOLTIP_POSITION = "topcenter bottomleft";
+const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400; // px
 const REQUESTS_WATERFALL_SAFE_BOUNDS = 90; // px
 const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
 const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60; // px
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES = 3;
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32; // byte
@@ -313,17 +315,19 @@ ToolbarView.prototype = {
  * Functions handling the requests menu (containing details about each request,
  * like status, method, file, domain, as well as a waterfall representing
  * timing imformation).
  */
 function RequestsMenuView() {
   dumpn("RequestsMenuView was instantiated");
 
   this._flushRequests = this._flushRequests.bind(this);
+  this._onHover = this._onHover.bind(this);
   this._onSelect = this._onSelect.bind(this);
+  this._onSwap = this._onSwap.bind(this);
   this._onResize = this._onResize.bind(this);
   this._byFile = this._byFile.bind(this);
   this._byDomain = this._byDomain.bind(this);
   this._byType = this._byType.bind(this);
 }
 
 RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
   /**
@@ -340,16 +344,17 @@ RequestsMenuView.prototype = Heritage.ex
     Prefs.filters.forEach(type => this.filterOn(type));
     this.sortContents(this._byTiming);
 
     this.allowFocusOnRightClick = true;
     this.maintainSelectionVisible = true;
     this.widget.autoscrollWithAppendedItems = true;
 
     this.widget.addEventListener("select", this._onSelect, false);
+    this.widget.addEventListener("swap", this._onSwap, false);
     this._splitter.addEventListener("mousemove", this._onResize, false);
     window.addEventListener("resize", this._onResize, false);
 
     this.requestsMenuSortEvent = getKeyWithEvent(this.sortBy.bind(this));
     this.requestsMenuFilterEvent = getKeyWithEvent(this.filterOn.bind(this));
     this.reqeustsMenuClearEvent = this.clear.bind(this);
     this._onContextShowing = this._onContextShowing.bind(this);
     this._onContextNewTabCommand = this.openRequestInTab.bind(this);
@@ -385,16 +390,17 @@ RequestsMenuView.prototype = Heritage.ex
    * Destruction function, called when the network monitor is closed.
    */
   destroy: function() {
     dumpn("Destroying the SourcesView");
 
     Prefs.filters = this._activeFilters;
 
     this.widget.removeEventListener("select", this._onSelect, false);
+    this.widget.removeEventListener("swap", this._onSwap, false);
     this._splitter.removeEventListener("mousemove", this._onResize, false);
     window.removeEventListener("resize", this._onResize, false);
 
     $("#toolbar-labels").removeEventListener("click", this.requestsMenuSortEvent, false);
     $("#requests-menu-footer").removeEventListener("click", this.requestsMenuFilterEvent, false);
     $("#requests-menu-clear-button").removeEventListener("click", this.reqeustsMenuClearEvent, false);
     $("#network-request-popup").removeEventListener("popupshowing", this._onContextShowing, false);
     $("#request-menu-context-newtab").removeEventListener("command", this._onContextNewTabCommand, false);
@@ -458,21 +464,31 @@ RequestsMenuView.prototype = Heritage.ex
         startedDeltaMillis: unixTime - this._firstRequestStartedMillis,
         startedMillis: unixTime,
         method: aMethod,
         url: aUrl,
         isXHR: aIsXHR
       }
     });
 
+    // Create a tooltip for the newly appended network request item.
+    let requestTooltip = requestItem.attachment.tooltip = new Tooltip(document, {
+      closeOnEvents: [{
+        emitter: $("#requests-menu-contents"),
+        event: "scroll",
+        useCapture: true
+      }]
+    });
+
     $("#details-pane-toggle").disabled = false;
     $("#requests-menu-empty-notice").hidden = true;
 
     this.refreshSummary();
     this.refreshZebra();
+    this.refreshTooltip(requestItem);
 
     if (aId == this._preferredItemId) {
       this.selectedItem = requestItem;
     }
   },
 
   /**
    * Opens selected item in a new tab.
@@ -492,16 +508,17 @@ RequestsMenuView.prototype = Heritage.ex
   },
 
   /**
    * Copy image as data uri.
    */
   copyImageAsDataUri: function() {
     let selected = this.selectedItem.attachment;
     let { mimeType, text, encoding } = selected.responseContent.content;
+
     gNetwork.getString(text).then(aString => {
       let data = "data:" + mimeType + ";" + encoding + "," + aString;
       clipboardHelper.copyString(data, document);
     });
   },
 
   /**
    * Create a new custom request form populated with the data from
@@ -918,16 +935,29 @@ RequestsMenuView.prototype = Heritage.ex
       } else {
         requestTarget.setAttribute("odd", "");
         requestTarget.removeAttribute("even");
       }
     }
   },
 
   /**
+   * Refreshes the toggling anchor for the specified item's tooltip.
+   *
+   * @param object aItem
+   *        The network request item in this container.
+   */
+  refreshTooltip: function(aItem) {
+    let tooltip = aItem.attachment.tooltip;
+    tooltip.hide();
+    tooltip.startTogglingOnHover(aItem.target, this._onHover);
+    tooltip.defaultPosition = REQUESTS_TOOLTIP_POSITION;
+  },
+
+  /**
    * Schedules adding additional information to a network request.
    *
    * @param string aId
    *        An identifier coming from the network monitor controller.
    * @param object aData
    *        An object containing several { key: value } tuples of network info.
    *        Supported keys are "httpVersion", "status", "statusText" etc.
    */
@@ -1005,23 +1035,24 @@ RequestsMenuView.prototype = Heritage.ex
             requestItem.attachment.contentSize = value;
             this.updateMenuView(requestItem, key, value);
             break;
           case "mimeType":
             requestItem.attachment.mimeType = value;
             this.updateMenuView(requestItem, key, value);
             break;
           case "responseContent":
-            requestItem.attachment.responseContent = value;
             // If there's no mime type available when the response content
             // is received, assume text/plain as a fallback.
             if (!requestItem.attachment.mimeType) {
               requestItem.attachment.mimeType = "text/plain";
               this.updateMenuView(requestItem, "mimeType", "text/plain");
             }
+            requestItem.attachment.responseContent = value;
+            this.updateMenuView(requestItem, key, value);
             break;
           case "totalTime":
             requestItem.attachment.totalTime = value;
             requestItem.attachment.endedMillis = requestItem.attachment.startedMillis + value;
             this.updateMenuView(requestItem, key, value);
             this._registerLastRequestEnd(requestItem.attachment.endedMillis);
             break;
           case "eventTimings":
@@ -1107,19 +1138,19 @@ RequestsMenuView.prototype = Heritage.ex
         try {
           uri = nsIURL(aValue);
         } catch(e) {
           break; // User input may not make a well-formed url yet.
         }
         let nameWithQuery = this._getUriNameWithQuery(uri);
         let hostPort = this._getUriHostPort(uri);
 
-        let node = $(".requests-menu-file", target);
-        node.setAttribute("value", nameWithQuery);
-        node.setAttribute("tooltiptext", nameWithQuery);
+        let file = $(".requests-menu-file", target);
+        file.setAttribute("value", nameWithQuery);
+        file.setAttribute("tooltiptext", nameWithQuery);
 
         let domain = $(".requests-menu-domain", target);
         domain.setAttribute("value", hostPort);
         domain.setAttribute("tooltiptext", hostPort);
         break;
       }
       case "status": {
         let node = $(".requests-menu-status", target);
@@ -1143,16 +1174,31 @@ RequestsMenuView.prototype = Heritage.ex
       case "mimeType": {
         let type = this._getAbbreviatedMimeType(aValue);
         let node = $(".requests-menu-type", target);
         let text = CONTENT_MIME_TYPE_ABBREVIATIONS[type] || type;
         node.setAttribute("value", text);
         node.setAttribute("tooltiptext", aValue);
         break;
       }
+      case "responseContent": {
+        let { mimeType } = aItem.attachment;
+        let { text, encoding } = aValue.content;
+
+        if (mimeType.contains("image/")) {
+          gNetwork.getString(text).then(aString => {
+            let node = $(".requests-menu-icon", aItem.target);
+            node.src = "data:" + mimeType + ";" + encoding + "," + aString;
+            node.setAttribute("type", "thumbnail");
+            node.removeAttribute("hidden");
+            window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
+          });
+        }
+        break;
+      }
       case "totalTime": {
         let node = $(".requests-menu-timings-total", target);
         let text = L10N.getFormatStr("networkMenu.totalMS", aValue); // integer
         node.setAttribute("value", text);
         node.setAttribute("tooltiptext", text);
         break;
       }
     }
@@ -1421,16 +1467,59 @@ RequestsMenuView.prototype = Heritage.ex
       NetMonitorView.Sidebar.populate(item.attachment);
       NetMonitorView.Sidebar.toggle(true);
     } else {
       NetMonitorView.Sidebar.toggle(false);
     }
   },
 
   /**
+   * The swap listener for this container.
+   * Called when two items switch places, when the contents are sorted.
+   */
+  _onSwap: function({ detail: [firstItem, secondItem] }) {
+    // Sorting will create new anchor nodes for all the swapped request items
+    // in this container, so it's necessary to refresh the Tooltip instances.
+    this.refreshTooltip(firstItem);
+    this.refreshTooltip(secondItem);
+  },
+
+  /**
+   * The predicate used when deciding whether a popup should be shown
+   * over a request item or not.
+   *
+   * @param nsIDOMNode aTarget
+   *        The element node currently being hovered.
+   * @param object aTooltip
+   *        The current tooltip instance.
+   */
+  _onHover: function(aTarget, aTooltip) {
+    let requestItem = this.getItemForElement(aTarget);
+    if (!requestItem || !requestItem.attachment.responseContent) {
+      return;
+    }
+
+    let hovered = requestItem.attachment;
+    let { url } = hovered;
+    let { mimeType, text, encoding } = hovered.responseContent.content;
+
+    if (mimeType && mimeType.contains("image/") && (
+      aTarget.classList.contains("requests-menu-icon") ||
+      aTarget.classList.contains("requests-menu-file")))
+    {
+      return gNetwork.getString(text).then(aString => {
+        let anchor = $(".requests-menu-icon", requestItem.target);
+        let src = "data:" + mimeType + ";" + encoding + "," + aString;
+        aTooltip.setImageContent(src, { maxDim: REQUESTS_TOOLTIP_IMAGE_MAX_DIM });
+        return anchor;
+      });
+    }
+  },
+
+  /**
    * The resize listener for this container's window.
    */
   _onResize: function(e) {
     // Allow requests to settle down first.
     setNamedTimeout(
       "resize-events", RESIZE_REFRESH_RATE, () => this._flushWaterfallViews(true));
   },
 
@@ -1621,17 +1710,17 @@ SidebarView.prototype = {
    */
   populate: function(aData) {
     let isCustom = aData.isCustom;
     let view = isCustom ?
       NetMonitorView.CustomRequest :
       NetMonitorView.NetworkDetails;
 
     return view.populate(aData).then(() => {
-      $("#details-pane").selectedIndex = isCustom ? 0 : 1
+      $("#details-pane").selectedIndex = isCustom ? 0 : 1;
       window.emit(EVENTS.SIDEBAR_POPULATED);
     });
   }
 }
 
 /**
  * Functions handling the custom request view.
  */
@@ -1901,16 +1990,17 @@ NetworkDetailsView.prototype = {
           yield view._setTimingsInformation(src.eventTimings);
           break;
         case 5: // "Preview"
           yield view._setHtmlPreview(src.responseContent);
           break;
       }
       populated[tab] = true;
       window.emit(EVENTS.TAB_UPDATED);
+      NetMonitorView.RequestsMenu.ensureSelectedItemIsVisible();
     });
   },
 
   /**
    * Sets the network request summary shown in this view.
    *
    * @param object aData
    *        The data source (this should be the attachment of a request item).
--- a/browser/devtools/netmonitor/netmonitor.css
+++ b/browser/devtools/netmonitor/netmonitor.css
@@ -55,12 +55,12 @@
   #network-table[domain-overflows] .requests-menu-domain {
     display: none;
   }
 
   #network-table[type-overflows] .requests-menu-domain {
     -moz-box-flex: 1;
   }
 
-  #network-table[domain-overflows] .requests-menu-file {
+  #network-table[domain-overflows] .requests-menu-icon-and-file {
     -moz-box-flex: 1;
   }
 }
--- a/browser/devtools/netmonitor/netmonitor.xul
+++ b/browser/devtools/netmonitor/netmonitor.xul
@@ -63,18 +63,18 @@
                 </button>
                 <button id="requests-menu-method-button"
                         class="requests-menu-header-button requests-menu-method"
                         data-key="method"
                         label="&netmonitorUI.toolbar.method;"
                         flex="1">
                 </button>
               </hbox>
-              <hbox id="requests-menu-file-header-box"
-                    class="requests-menu-header requests-menu-file"
+              <hbox id="requests-menu-icon-and-file-header-box"
+                    class="requests-menu-header requests-menu-icon-and-file"
                     align="center">
                 <button id="requests-menu-file-button"
                         class="requests-menu-header-button requests-menu-file"
                         data-key="file"
                         label="&netmonitorUI.toolbar.file;"
                         flex="1">
                 </button>
               </hbox>
@@ -147,18 +147,23 @@
             <hbox id="requests-menu-item-template" hidden="true">
               <hbox class="requests-menu-subitem requests-menu-status-and-method"
                     align="center">
                 <box class="requests-menu-status"/>
                 <label class="plain requests-menu-method"
                        crop="end"
                        flex="1"/>
               </hbox>
-              <label class="plain requests-menu-subitem requests-menu-file"
-                     crop="end"/>
+              <hbox class="requests-menu-subitem requests-menu-icon-and-file"
+                    align="center">
+                <image class="requests-menu-icon" hidden="true"/>
+                <label class="plain requests-menu-file"
+                       crop="end"
+                       flex="1"/>
+              </hbox>
               <label class="plain requests-menu-subitem requests-menu-domain"
                      crop="end"/>
               <label class="plain requests-menu-subitem requests-menu-type"
                      crop="end"/>
               <label class="plain requests-menu-subitem requests-menu-size"
                      crop="end"/>
               <hbox class="requests-menu-subitem requests-menu-waterfall"
                     align="center"
--- a/browser/devtools/netmonitor/test/browser.ini
+++ b/browser/devtools/netmonitor/test/browser.ini
@@ -42,16 +42,18 @@ support-files =
 [browser_net_copy_url.js]
 [browser_net_cyrillic-01.js]
 [browser_net_cyrillic-02.js]
 [browser_net_filter-01.js]
 [browser_net_filter-02.js]
 [browser_net_filter-03.js]
 [browser_net_footer-summary.js]
 [browser_net_html-preview.js]
+[browser_net_icon-preview.js]
+[browser_net_image-tooltip.js]
 [browser_net_json-long.js]
 [browser_net_json-malformed.js]
 [browser_net_json_custom_mime.js]
 [browser_net_json_text_mime.js]
 [browser_net_jsonp.js]
 [browser_net_large-response.js]
 [browser_net_open_request_in_tab.js]
 [browser_net_page-nav.js]
--- a/browser/devtools/netmonitor/test/browser_net_complex-params.js
+++ b/browser/devtools/netmonitor/test/browser_net_complex-params.js
@@ -133,16 +133,14 @@ function test() {
         aQueryStringParamValue,
         "The first query string param value was incorrect.");
 
       return NetMonitorView.editor("#request-post-data-textarea").then((aEditor) => {
         is(aEditor.getText(), aRequestPayload,
           "The text shown in the source editor is incorrect.");
         is(aEditor.getMode(), Editor.modes[aEditorMode],
           "The mode active in the source editor is incorrect.");
-
-        teardown(aMonitor).then(finish);
       });
     }
 
     aDebuggee.performRequests();
   });
 }
--- a/browser/devtools/netmonitor/test/browser_net_copy_image_as_data_uri.js
+++ b/browser/devtools/netmonitor/test/browser_net_copy_image_as_data_uri.js
@@ -9,23 +9,21 @@ function test() {
   initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL).then(([aTab, aDebuggee, aMonitor]) => {
     info("Starting test... ");
 
     let { NetMonitorView } = aMonitor.panelWin;
     let { RequestsMenu } = NetMonitorView;
 
     RequestsMenu.lazyUpdate = false;
 
-    let imageDataUri = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
-
     waitForNetworkEvents(aMonitor, 6).then(() => {
       let requestItem = RequestsMenu.getItemAtIndex(5);
       RequestsMenu.selectedItem = requestItem;
 
-      waitForClipboard(imageDataUri, function setup() {
+      waitForClipboard(TEST_IMAGE_DATA_URI, function setup() {
         RequestsMenu.copyImageAsDataUri();
       }, function onSuccess() {
         ok(true, "Clipboard contains the currently selected image as data uri.");
         cleanUp();
       }, function onFailure() {
         ok(false, "Copying the currently selected image as data uri was unsuccessful.");
         cleanUp();
       });
--- a/browser/devtools/netmonitor/test/browser_net_html-preview.js
+++ b/browser/devtools/netmonitor/test/browser_net_html-preview.js
@@ -4,17 +4,17 @@
 /**
  * Tests if html responses show and properly populate a "Preview" tab.
  */
 
 function test() {
   initNetMonitor(CONTENT_TYPE_URL).then(([aTab, aDebuggee, aMonitor]) => {
     info("Starting test... ");
 
-    let { $, document, NetMonitorView } = aMonitor.panelWin;
+    let { $, document, EVENTS, NetMonitorView } = aMonitor.panelWin;
     let { RequestsMenu } = NetMonitorView;
 
     RequestsMenu.lazyUpdate = false;
 
     waitForNetworkEvents(aMonitor, 6).then(() => {
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
 
@@ -30,18 +30,17 @@ function test() {
 
       is($("#event-details-pane").selectedIndex, 5,
         "The fifth tab in the details pane should be selected.");
       is($("#preview-tab").hidden, false,
         "The preview tab should be visible now.");
       is($("#preview-tabpanel").hidden, false,
         "The preview tabpanel should be visible now.");
 
-      let RESPONSE_HTML_PREVIEW_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED;
-      waitFor(aMonitor.panelWin, RESPONSE_HTML_PREVIEW_DISPLAYED).then(() => {
+      waitFor(aMonitor.panelWin, EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED).then(() => {
         let iframe = $("#response-preview");
         ok(iframe,
           "There should be a response preview iframe available.");
         ok(iframe.contentDocument,
           "The iframe's content document should be available.");
         is(iframe.contentDocument.querySelector("blink").textContent, "Not Found",
           "The iframe's content document should be loaded and correct.");
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/browser_net_icon-preview.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if image responses show a thumbnail in the requests menu.
+ */
+
+function test() {
+  initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL).then(([aTab, aDebuggee, aMonitor]) => {
+    info("Starting test... ");
+
+    let { $, $all, EVENTS, ACTIVITY_TYPE, NetMonitorView, NetMonitorController } = aMonitor.panelWin;
+    let { RequestsMenu } = NetMonitorView;
+
+    promise.all([
+      waitForNetworkEvents(aMonitor, 6),
+      waitFor(aMonitor.panelWin, EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED)
+    ]).then(() => {
+      info("Checking the image thumbnail when all items are shown.");
+      checkImageThumbnail();
+
+      RequestsMenu.sortBy("size");
+      info("Checking the image thumbnail when all items are sorted.");
+      checkImageThumbnail();
+
+      RequestsMenu.filterOn("images");
+      info("Checking the image thumbnail when only images are shown.");
+      checkImageThumbnail();
+
+      info("Reloading the debuggee and performing all requests again...");
+      reloadAndPerformRequests();
+
+      return promise.all([
+        waitForNetworkEvents(aMonitor, 7), // 6 + 1
+        waitFor(aMonitor.panelWin, EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED)
+      ]);
+    }).then(() => {
+      info("Checking the image thumbnail after a reload.");
+      checkImageThumbnail();
+
+      teardown(aMonitor).then(finish);
+    });
+
+    function reloadAndPerformRequests() {
+      NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED).then(() => {
+        aDebuggee.performRequests();
+      });
+    }
+
+    function checkImageThumbnail() {
+      is($all(".requests-menu-icon[type=thumbnail]").length, 1,
+        "There should be only one image request with a thumbnail displayed.");
+      is($(".requests-menu-icon[type=thumbnail]").src, TEST_IMAGE_DATA_URI,
+        "The image requests-menu-icon thumbnail is displayed correctly.");
+      is($(".requests-menu-icon[type=thumbnail]").hidden, false,
+        "The image requests-menu-icon thumbnail should not be hidden.");
+    }
+
+    aDebuggee.performRequests();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/browser_net_image-tooltip.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if image responses show a popup in the requests menu when hovered.
+ */
+
+function test() {
+  initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL).then(([aTab, aDebuggee, aMonitor]) => {
+    info("Starting test... ");
+
+    let { $, EVENTS, ACTIVITY_TYPE, NetMonitorView, NetMonitorController } = aMonitor.panelWin;
+    let { RequestsMenu } = NetMonitorView;
+
+    promise.all([
+      waitForNetworkEvents(aMonitor, 6),
+      waitFor(aMonitor.panelWin, EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED)
+    ]).then(() => {
+      info("Checking the image thumbnail after a few requests were made...");
+      let requestItem = RequestsMenu.items[5];
+      let requestTooltip = requestItem.attachment.tooltip;
+      ok(requestTooltip, "There should be a tooltip instance for the image request.");
+
+      let anchor = $(".requests-menu-file", requestItem.target);
+      return showTooltipOn(requestTooltip, anchor);
+    }).then(aTooltip => {
+      ok(true,
+        "An tooltip was successfully opened for the image request.");
+      is(aTooltip.content.querySelector("image").src, TEST_IMAGE_DATA_URI,
+        "The tooltip's image content is displayed correctly.");
+
+      info("Reloading the debuggee and performing all requests again...");
+      reloadAndPerformRequests();
+
+      return promise.all([
+        waitForNetworkEvents(aMonitor, 7), // 6 + 1
+        waitFor(aMonitor.panelWin, EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED)
+      ]);
+    }).then(() => {
+      info("Checking the image thumbnail after a reload.");
+      let requestItem = RequestsMenu.items[6];
+      let requestTooltip = requestItem.attachment.tooltip;
+      ok(requestTooltip, "There should be a tooltip instance for the image request.");
+
+      let anchor = $(".requests-menu-file", requestItem.target);
+      return showTooltipOn(requestTooltip, anchor);
+    }).then(aTooltip => {
+      ok(true,
+        "An tooltip was successfully opened for the image request.");
+      is(aTooltip.content.querySelector("image").src, TEST_IMAGE_DATA_URI,
+        "The tooltip's image content is displayed correctly.");
+
+      teardown(aMonitor).then(finish);
+    });
+
+    function reloadAndPerformRequests() {
+      NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED).then(() => {
+        aDebuggee.performRequests();
+      });
+    }
+
+    function showTooltipOn(aTooltip, aTarget) {
+      let deferred = promise.defer();
+
+      aTooltip.panel.addEventListener("popupshown", function onEvent() {
+        aTooltip.panel.removeEventListener("popupshown", onEvent, true);
+        deferred.resolve(aTooltip);
+      }, true);
+
+      aTooltip._showOnHover(aTarget);
+      return deferred.promise;
+    }
+
+    aDebuggee.performRequests();
+  });
+}
--- a/browser/devtools/netmonitor/test/head.js
+++ b/browser/devtools/netmonitor/test/head.js
@@ -35,16 +35,17 @@ const CUSTOM_GET_URL = EXAMPLE_URL + "ht
 const STATISTICS_URL = EXAMPLE_URL + "html_statistics-test-page.html";
 
 const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
 const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";
 const STATUS_CODES_SJS = EXAMPLE_URL + "sjs_status-codes-test-server.sjs";
 const SORTING_SJS = EXAMPLE_URL + "sjs_sorting-test-server.sjs";
 
 const TEST_IMAGE = EXAMPLE_URL + "test-image.png";
+const TEST_IMAGE_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
 
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 // Enable logging for all the relevant tests.
 const gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", true);
 
--- a/browser/devtools/shared/widgets/Tooltip.js
+++ b/browser/devtools/shared/widgets/Tooltip.js
@@ -394,22 +394,22 @@ Tooltip.prototype = {
       setNamedTimeout(this.uid, this._showDelay, () => {
         this._showOnHover(event.target);
       });
     }
   },
 
   _showOnHover: function(target) {
     let res = this._targetNodeCb(target, this);
+    let show = arg => this.show(arg instanceof Ci.nsIDOMNode ? arg : target);
+
     if (res && res.then) {
-      res.then(() => {
-        this.show(target);
-      });
+      res.then(show);
     } else if (res) {
-      this.show(target);
+      show(res);
     }
   },
 
   _onBaseNodeMouseLeave: function() {
     clearNamedTimeout(this.uid);
     this._lastHovered = null;
     this.hide();
   },
--- a/browser/devtools/shared/widgets/ViewHelpers.jsm
+++ b/browser/devtools/shared/widgets/ViewHelpers.jsm
@@ -765,16 +765,23 @@ this.WidgetMethods = {
    * @param number aIndex
    *        The index of the item to bring into view.
    */
   ensureIndexIsVisible: function(aIndex) {
     this.ensureItemIsVisible(this.getItemAtIndex(aIndex));
   },
 
   /**
+   * Sugar for ensuring the selected item is visible in this container.
+   */
+  ensureSelectedItemIsVisible: function() {
+    this.ensureItemIsVisible(this.selectedItem);
+  },
+
+  /**
    * If supported by the widget, the label string temporarily added to this
    * container when there are no child items present.
    */
   set emptyText(aValue) {
     this._emptyText = aValue;
 
     // Apply the emptyText attribute right now if there are no child items.
     if (!this._itemsByElement.size) {
@@ -891,16 +898,19 @@ this.WidgetMethods = {
     this._insertItemAt.apply(this, i < j ? [j, aFirst] : [i, aSecond]);
 
     // 5. Restore the previous selection, if necessary.
     if (selectedIndex == i) {
       this._widget.selectedItem = aFirst._target;
     } else if (selectedIndex == j) {
       this._widget.selectedItem = aSecond._target;
     }
+
+    // 6. Let the outside world know that these two items were swapped.
+    ViewHelpers.dispatchEvent(aFirst.target, "swap", [aSecond, aFirst]);
   },
 
   /**
    * Visually swaps two items in this container at specific indices.
    *
    * @param number aFirst
    *        The index of the first item to be swapped.
    * @param number aSecond
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -192,17 +192,16 @@
 @BINPATH@/components/dom_messages.xpt
 #endif
 @BINPATH@/components/dom_apps.xpt
 @BINPATH@/components/dom_base.xpt
 @BINPATH@/components/dom_system.xpt
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #endif
-@BINPATH@/components/dom_camera.xpt
 @BINPATH@/components/dom_canvas.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
 @BINPATH@/components/dom_geolocation.xpt
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -609,17 +609,16 @@ you can use these alternative items. Oth
 
 <!ENTITY findOnCmd.label     "Find in This Page…">
 <!ENTITY findOnCmd.accesskey "F">
 <!ENTITY findOnCmd.commandkey "f">
 <!ENTITY findAgainCmd.label  "Find Again">
 <!ENTITY findAgainCmd.accesskey "g">
 <!ENTITY findAgainCmd.commandkey "g">
 <!ENTITY findAgainCmd.commandkey2 "VK_F3">
-<!ENTITY findSelectionCmd.commandkey "e">
 
 <!ENTITY spellAddDictionaries.label "Add Dictionaries…">
 <!ENTITY spellAddDictionaries.accesskey "A">
 
 <!ENTITY editBookmark.done.label                     "Done">
 <!ENTITY editBookmark.cancel.label                   "Cancel">
 <!ENTITY editBookmark.removeBookmark.accessKey       "R">
 
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1171,16 +1171,17 @@ toolbar .toolbarbutton-1:not([type="menu
     -moz-image-region: rect(0px, 160px, 32px, 128px);
   }
 
   #zoom-controls@inAnyPanel@ > #zoom-in-button,
   toolbarpaletteitem[place="palette"] > #zoom-controls > #zoom-in-button {
     -moz-image-region: rect(0px, 192px, 32px, 160px);
   }
 
+  #PanelUI-fxa-status > .toolbarbutton-icon,
   #PanelUI-quit > .toolbarbutton-icon,
   #PanelUI-customize > .toolbarbutton-icon,
   #PanelUI-help > .toolbarbutton-icon {
     width: 16px;
   }
 }
 
 toolbar .toolbarbutton-1:not([type="menu-button"]),
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -78,16 +78,20 @@
   padding: 2px 12px;
   background-color: rgb(251,251,251);
   color: rgb(71,71,71);
   box-shadow: 0 1px rgba(255, 255, 255, 0.5),
               inset 0 1px rgba(255, 255, 255, 0.5);
   -moz-appearance: none;
 }
 
+.customizationmode-button[disabled="true"] {
+  opacity: .5;
+}
+
 #customization-titlebar-visibility-button {
   list-style-image: url("chrome://browser/skin/customizableui/customize-titleBar-toggle.png");
   -moz-image-region: rect(0, 24px, 24px, 0);
   padding: 2px 7px;
   -moz-margin-end: 10px;
 }
 
 #customization-titlebar-visibility-button > .button-box > .button-text {
@@ -99,17 +103,17 @@
   -moz-image-region: rect(0, 48px, 24px, 24px);
   background-color: rgb(218, 218, 218);
   border-color: rgb(168, 168, 168);
   text-shadow: 0 1px rgb(236, 236, 236);
   box-shadow: 0 1px rgba(255, 255, 255, 0.5),
               inset 0 1px rgb(196, 196, 196);
 }
 
-#customization-undo-reset {
+#customization-undo-reset-button {
   -moz-margin-end: 10px;
 }
 
 #main-window[customize-entered] #customization-panel-container {
   background-image: url("chrome://browser/skin/customizableui/customizeMode-separatorHorizontal.png"),
                     url("chrome://browser/skin/customizableui/customizeMode-separatorVertical.png"),
                     url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png"),
                     url("chrome://browser/skin/customizableui/background-noise-toolbar.png"),
--- a/browser/themes/shared/devtools/netmonitor.inc.css
+++ b/browser/themes/shared/devtools/netmonitor.inc.css
@@ -121,21 +121,41 @@
   height: 10px;
 }
 
 .requests-menu-method {
   text-align: center;
   font-weight: 600;
 }
 
-.requests-menu-file {
+.requests-menu-icon-and-file {
   width: 20vw;
   min-width: 4em;
 }
 
+.requests-menu-icon {
+  background: #fff;
+  width: calc(1em + 4px);
+  height: calc(1em + 4px);
+  margin: -4px 0px;
+  -moz-margin-end: 4px;
+}
+
+.theme-dark .requests-menu-icon {
+  outline: 1px solid @table_itemDarkStartBorder@;
+}
+
+.theme-light .requests-menu-icon {
+  outline: 1px solid @table_itemLightStartBorder@;
+}
+
+.requests-menu-file {
+  text-align: start;
+}
+
 .requests-menu-domain {
   width: 14vw;
   min-width: 10em;
 }
 
 .requests-menu-type {
   text-align: center;
   width: 4em;
@@ -283,18 +303,16 @@ box.requests-menu-status {
 
 /* Network requests table: waterfall items */
 
 .requests-menu-subitem.requests-menu-waterfall {
   -moz-padding-start: 0px;
   -moz-padding-end: 4px;
   background-repeat: repeat-y; /* Background created on a <canvas> in js. */
   background-position: -1px center;
-  margin-top: -1px; /* Compensate borders. */
-  margin-bottom: -1px;
 }
 
 .requests-menu-subitem.requests-menu-waterfall:-moz-locale-dir(rtl) {
   background-position: right center;
 }
 
 .requests-menu-timings:-moz-locale-dir(ltr) {
   transform-origin: left center;
@@ -766,17 +784,20 @@ box.requests-menu-status {
     margin: 0 !important;
     /* To prevent all the margin hacks to hide the sidebar. */
   }
 
   .requests-menu-status-and-method {
     width: 16vw;
   }
 
-  .requests-menu-file,
+  .requests-menu-icon-and-file {
+    width: 30vw;
+  }
+
   .requests-menu-domain {
     width: 30vw;
   }
 
   .requests-menu-type {
     width: 8vw;
   }
 
@@ -792,15 +813,15 @@ box.requests-menu-status {
 @media (min-width: 701px) {
   #network-table[type-overflows] .requests-menu-domain {
     border-width: 0 !important;
     box-shadow: none !important;
     /* The "Type" header is not visible anymore, and thus the
        right border and box-shadow of "Domain" column should be hidden. */
   }
 
-  #network-table[domain-overflows] .requests-menu-file {
+  #network-table[domain-overflows] .requests-menu-icon-and-file {
     border-width: 0 !important;
     box-shadow: none !important;
     /* The "Domain" header is not visible anymore, and thus the
        right border and box-shadow of "File" column should be hidden. */
   }
 }
--- a/build/docs/test_manifests.rst
+++ b/build/docs/test_manifests.rst
@@ -171,17 +171,23 @@ expression syntax until it is documented
 
 File Installation
 -----------------
 
 Files referenced by manifests are automatically installed into the object
 directory into paths defined in
 :py:func:`mozbuild.frontend.emitter.TreeMetadataEmitter._process_test_manifest`.
 
-Referenced files in the manifest not in the same directory tree as the manifest
-file are **not** installed.
+Relative paths resolving to parent directory (e.g.
+``support-files = ../foo.txt`` have special behavior.
+
+For ``support-files``, the file will be installed to the default destination
+for that manifest. Only the file's base name is used to construct the final
+path: directories are irrelevant.
+
+For all other entry types, the file installation is skipped.
 
 .. _reftest_manifests:
 
 Reftest Manifests
 =================
 
 See `MDN <https://developer.mozilla.org/en-US/docs/Creating_reftest-based_unit_tests>`_.
--- a/configure.in
+++ b/configure.in
@@ -5028,16 +5028,18 @@ dnl enable once Signaling lands
     AC_DEFINE(MOZ_WEBRTC_SIGNALING)
 dnl enable once PeerConnection lands
     MOZ_PEERCONNECTION=1
     AC_DEFINE(MOZ_PEERCONNECTION)
     MOZ_SCTP=1
     MOZ_SRTP=1
     AC_DEFINE(MOZ_SCTP)
     AC_DEFINE(MOZ_SRTP)
+else
+    MOZ_SYNTH_PICO=
 fi
 
 AC_SUBST(MOZ_WEBRTC)
 AC_SUBST(MOZ_WEBRTC_LEAKING_TESTS)
 AC_SUBST(MOZ_WEBRTC_SIGNALING)
 AC_SUBST(MOZ_PEERCONNECTION)
 AC_SUBST(MOZ_WEBRTC_ASSERT_ALWAYS)
 AC_SUBST(MOZ_SCTP)
--- a/content/media/webrtc/MediaEngineWebRTC.cpp
+++ b/content/media/webrtc/MediaEngineWebRTC.cpp
@@ -37,76 +37,75 @@ GetUserMediaLog()
 #include "AndroidJNIWrapper.h"
 #include "AndroidBridge.h"
 #endif
 
 #undef LOG
 #define LOG(args) PR_LOG(GetUserMediaLog(), PR_LOG_DEBUG, args)
 
 namespace mozilla {
-#ifndef MOZ_B2G_CAMERA
+
 MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs &aPrefs)
   : mMutex("mozilla::MediaEngineWebRTC")
   , mVideoEngine(nullptr)
   , mVoiceEngine(nullptr)
   , mVideoEngineInit(false)
   , mAudioEngineInit(false)
   , mHasTabVideoSource(false)
 {
+#ifndef MOZ_B2G_CAMERA
   nsCOMPtr<nsIComponentRegistrar> compMgr;
   NS_GetComponentRegistrar(getter_AddRefs(compMgr));
   if (compMgr) {
     compMgr->IsContractIDRegistered(NS_TABSOURCESERVICE_CONTRACTID, &mHasTabVideoSource);
   }
+#else
+  AsyncLatencyLogger::Get()->AddRef();
+#endif
   if (aPrefs.mLoadAdapt) {
       mLoadMonitor = new LoadMonitor();
       mLoadMonitor->Init(mLoadMonitor);
   }
 }
-#endif
-
 
 void
 MediaEngineWebRTC::EnumerateVideoDevices(nsTArray<nsRefPtr<MediaEngineVideoSource> >* aVSources)
 {
 #ifdef MOZ_B2G_CAMERA
   MutexAutoLock lock(mMutex);
-  if (!mCameraManager) {
-    return;
-  }
 
   /**
    * We still enumerate every time, in case a new device was plugged in since
    * the last call. TODO: Verify that WebRTC actually does deal with hotplugging
    * new devices (with or without new engine creation) and accordingly adjust.
    * Enumeration is not neccessary if GIPS reports the same set of devices
    * for a given instance of the engine. Likewise, if a device was plugged out,
    * mVideoSources must be updated.
    */
   int num = 0;
   nsresult result;
-  result = mCameraManager->GetNumberOfCameras(num);
+  result = ICameraControl::GetNumberOfCameras(num);
   if (num <= 0 || result != NS_OK) {
     return;
   }
 
   for (int i = 0; i < num; i++) {
     nsCString cameraName;
-    result = mCameraManager->GetCameraName(i, cameraName);
+    result = ICameraControl::GetCameraName(i, cameraName);
     if (result != NS_OK) {
       continue;
     }
 
     nsRefPtr<MediaEngineWebRTCVideoSource> vSource;
     NS_ConvertUTF8toUTF16 uuid(cameraName);
     if (mVideoSources.Get(uuid, getter_AddRefs(vSource))) {
       // We've already seen this device, just append.
       aVSources->AppendElement(vSource.get());
     } else {
-      vSource = new MediaEngineWebRTCVideoSource(mCameraManager, i, mWindowId);
+      vSource = new MediaEngineWebRTCVideoSource(i);
       mVideoSources.Put(uuid, vSource); // Hashtable takes ownership.
       aVSources->AppendElement(vSource);
     }
   }
 
   return;
 #else
   webrtc::ViEBase* ptrViEBase;
--- a/content/media/webrtc/MediaEngineWebRTC.h
+++ b/content/media/webrtc/MediaEngineWebRTC.h
@@ -41,19 +41,18 @@
 #include "webrtc/voice_engine/include/voe_audio_processing.h"
 
 // Video Engine
 #include "webrtc/video_engine/include/vie_base.h"
 #include "webrtc/video_engine/include/vie_codec.h"
 #include "webrtc/video_engine/include/vie_render.h"
 #include "webrtc/video_engine/include/vie_capture.h"
 #ifdef MOZ_B2G_CAMERA
-#include "CameraPreviewMediaStream.h"
-#include "DOMCameraManager.h"
-#include "GonkCameraControl.h"
+#include "CameraControlListener.h"
+#include "ICameraControl.h"
 #include "ImageContainer.h"
 #include "nsGlobalWindow.h"
 #include "prprf.h"
 #endif
 
 #include "NullTransport.h"
 
 namespace mozilla {
@@ -68,55 +67,45 @@ class GetCameraNameRunnable;
  *
  * On B2G platform, member data may accessed from different thread after construction:
  *
  * MediaThread:
  *   mState, mImage, mWidth, mHeight, mCapability, mPrefs, mDeviceName, mUniqueId, mInitDone,
  *   mSources, mImageContainer, mSources, mState, mImage, mLastCapture
  *
  * MainThread:
- *   mDOMCameraControl, mCaptureIndex, mCameraThread, mWindowId, mCameraManager,
+ *   mCaptureIndex, mWindowId,
  *   mNativeCameraControl, mPreviewStream, mState, mLastCapture, mWidth, mHeight
  *
  * Where mWidth, mHeight, mImage are protected by mMonitor
  *       mState, mLastCapture is protected by mCallbackMonitor
  * Other variable is accessed only from single thread
  */
 class MediaEngineWebRTCVideoSource : public MediaEngineVideoSource
                                    , public nsRunnable
 #ifdef MOZ_B2G_CAMERA
-                                   , public nsICameraGetCameraCallback
-                                   , public nsICameraPreviewStreamCallback
-                                   , public nsICameraTakePictureCallback
-                                   , public nsICameraReleaseCallback
-                                   , public nsICameraErrorCallback
-                                   , public CameraPreviewFrameCallback
+                                   , public CameraControlListener
 #else
                                    , public webrtc::ExternalRenderer
 #endif
 {
 public:
 #ifdef MOZ_B2G_CAMERA
-  MediaEngineWebRTCVideoSource(nsDOMCameraManager* aCameraManager,
-    int aIndex, uint64_t aWindowId)
-    : mCameraManager(aCameraManager)
-    , mNativeCameraControl(nullptr)
-    , mPreviewStream(nullptr)
-    , mWindowId(aWindowId)
+  MediaEngineWebRTCVideoSource(int aIndex)
+    : mCameraControl(nullptr)
     , mCallbackMonitor("WebRTCCamera.CallbackMonitor")
     , mCaptureIndex(aIndex)
     , mMonitor("WebRTCCamera.Monitor")
     , mWidth(0)
     , mHeight(0)
     , mInitDone(false)
     , mInSnapshotMode(false)
     , mSnapshotPath(nullptr)
   {
     mState = kReleased;
-    NS_NewNamedThread("CameraThread", getter_AddRefs(mCameraThread));
     Init();
   }
 #else
   // ViEExternalRenderer.
   virtual int FrameSizeChange(unsigned int, unsigned int, unsigned int);
   virtual int DeliverFrame(unsigned char*,int, uint32_t , int64_t,
                            void *handle);
   /**
@@ -162,30 +151,27 @@ public:
                           TrackTicks &aLastEndTime);
 
   virtual bool IsFake() {
     return false;
   }
 
   NS_DECL_THREADSAFE_ISUPPORTS
 #ifdef MOZ_B2G_CAMERA
-  NS_DECL_NSICAMERAGETCAMERACALLBACK
-  NS_DECL_NSICAMERAPREVIEWSTREAMCALLBACK
-  NS_DECL_NSICAMERATAKEPICTURECALLBACK
-  NS_DECL_NSICAMERARELEASECALLBACK
-  NS_DECL_NSICAMERAERRORCALLBACK
+  void OnHardwareStateChange(HardwareState aState);
+  void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration);
+  bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);
+  void OnError(CameraErrorContext aContext, const nsACString& aError);
+  void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType);
 
   void AllocImpl();
   void DeallocImpl();
   void StartImpl(webrtc::CaptureCapability aCapability);
   void StopImpl();
   void SnapshotImpl();
-
-  virtual void OnNewFrame(const gfxIntSize& aIntrinsicSize, layers::Image* aImage);
-
 #endif
 
   // This runnable is for creating a temporary file on the main thread.
   NS_IMETHODIMP
   Run()
   {
     nsCOMPtr<nsIFile> tmp;
     nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmp));
@@ -207,30 +193,18 @@ private:
   static const unsigned int KMaxUniqueIdLength = 256;
 
   // Initialize the needed Video engine interfaces.
   void Init();
   void Shutdown();
 
   // Engine variables.
 #ifdef MOZ_B2G_CAMERA
-  // MediaEngine hold this DOM object, and the MediaEngine is hold by Navigator
-  // Their life time is always much longer than this object. Use a raw-pointer
-  // here should be safe.
-  // We need raw pointer here since such DOM-object should not addref/release on
-  // any thread other than main thread, but we must use this object for now. To
-  // avoid any bad thing do to addref/release DOM-object on other thread, we use
-  // raw-pointer for now.
-  nsDOMCameraManager* mCameraManager;
-  nsRefPtr<nsDOMCameraControl> mDOMCameraControl;
-  nsRefPtr<nsGonkCameraControl> mNativeCameraControl;
-  nsRefPtr<DOMCameraPreview> mPreviewStream;
-  uint64_t mWindowId;
+  nsRefPtr<ICameraControl> mCameraControl;
   mozilla::ReentrantMonitor mCallbackMonitor; // Monitor for camera callback handling
-  nsRefPtr<nsIThread> mCameraThread;
   nsRefPtr<nsIDOMFile> mLastCapture;
 #else
   webrtc::VideoEngine* mVideoEngine; // Weak reference, don't free.
   webrtc::ViEBase* mViEBase;
   webrtc::ViECapture* mViECapture;
   webrtc::ViERender* mViERender;
 #endif
   webrtc::CaptureCapability mCapability; // Doesn't work on OS X.
@@ -346,34 +320,17 @@ private:
   webrtc::NsModes  mNoiseSuppress;
 
   NullTransport *mNullTransport;
 };
 
 class MediaEngineWebRTC : public MediaEngine
 {
 public:
-#ifdef MOZ_B2G_CAMERA
-  MediaEngineWebRTC(nsDOMCameraManager* aCameraManager, uint64_t aWindowId)
-    : mMutex("mozilla::MediaEngineWebRTC")
-    , mVideoEngine(nullptr)
-    , mVoiceEngine(nullptr)
-    , mVideoEngineInit(false)
-    , mAudioEngineInit(false)
-    , mCameraManager(aCameraManager)
-    , mWindowId(aWindowId)
-    , mHasTabVideoSource(false)
-  {
-    AsyncLatencyLogger::Get(true)->AddRef();
-    mLoadMonitor = new LoadMonitor();
-    mLoadMonitor->Init(mLoadMonitor);
-  }
-#else
   MediaEngineWebRTC(MediaEnginePrefs &aPrefs);
-#endif
   ~MediaEngineWebRTC() {
     Shutdown();
 #ifdef MOZ_B2G_CAMERA
     AsyncLatencyLogger::Get()->Release();
 #endif
   }
 
   // Clients should ensure to clean-up sources video/audio sources
@@ -395,26 +352,14 @@ private:
   bool mAudioEngineInit;
   bool mHasTabVideoSource;
 
   // Store devices we've already seen in a hashtable for quick return.
   // Maps UUID to MediaEngineSource (one set for audio, one for video).
   nsRefPtrHashtable<nsStringHashKey, MediaEngineWebRTCVideoSource > mVideoSources;
   nsRefPtrHashtable<nsStringHashKey, MediaEngineWebRTCAudioSource > mAudioSources;
 
-#ifdef MOZ_B2G_CAMERA
-  // MediaEngine hold this DOM object, and the MediaEngine is hold by Navigator
-  // Their life time is always much longer than this object. Use a raw-pointer
-  // here should be safe.
-  // We need raw pointer here since such DOM-object should not addref/release on
-  // any thread other than main thread, but we must use this object for now. To
-  // avoid any bad thing do to addref/release DOM-object on other thread, we use
-  // raw-pointer for now.
-  nsDOMCameraManager* mCameraManager;
-  uint64_t mWindowId;
-#endif
-
-   nsRefPtr<LoadMonitor> mLoadMonitor;
+  nsRefPtr<LoadMonitor> mLoadMonitor;
 };
 
 }
 
 #endif /* NSMEDIAENGINEWEBRTC_H_ */
--- a/content/media/webrtc/MediaEngineWebRTCVideo.cpp
+++ b/content/media/webrtc/MediaEngineWebRTCVideo.cpp
@@ -307,17 +307,19 @@ MediaEngineWebRTCVideoSource::Deallocate
   }
   return NS_OK;
 }
 
 nsresult
 MediaEngineWebRTCVideoSource::Start(SourceMediaStream* aStream, TrackID aID)
 {
   LOG((__FUNCTION__));
+#ifndef MOZ_B2G_CAMERA
   int error = 0;
+#endif
   if (!mInitDone || !aStream) {
     return NS_ERROR_FAILURE;
   }
 
   mSources.AppendElement(aStream);
 
   aStream->AddTrack(aID, USECS_PER_S, 0, new VideoSegment());
   aStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
@@ -408,17 +410,17 @@ MediaEngineWebRTCVideoSource::Snapshot(u
  * constructor and destructor respectively.
  */
 
 void
 MediaEngineWebRTCVideoSource::Init()
 {
 #ifdef MOZ_B2G_CAMERA
   nsAutoCString deviceName;
-  mCameraManager->GetCameraName(mCaptureIndex, deviceName);
+  ICameraControl::GetCameraName(mCaptureIndex, deviceName);
   CopyUTF8toUTF16(deviceName, mDeviceName);
   CopyUTF8toUTF16(deviceName, mUniqueId);
 #else
   // fix compile warning for these being unused. (remove once used)
   (void) mFps;
   (void) mMinFps;
 
   LOG((__FUNCTION__));
@@ -487,131 +489,101 @@ MediaEngineWebRTCVideoSource::Shutdown()
 
 #ifdef MOZ_B2G_CAMERA
 
 // All these functions must be run on MainThread!
 void
 MediaEngineWebRTCVideoSource::AllocImpl() {
   MOZ_ASSERT(NS_IsMainThread());
 
-  mDOMCameraControl = new nsDOMCameraControl(mCaptureIndex,
-                                             mCameraThread,
-                                             this,
-                                             this,
-                                             nsGlobalWindow::GetInnerWindowWithId(mWindowId));
-  mCameraManager->Register(mDOMCameraControl);
+  mCameraControl = ICameraControl::Create(mCaptureIndex, nullptr);
+  mCameraControl->AddListener(this);
 }
 
 void
 MediaEngineWebRTCVideoSource::DeallocImpl() {
   MOZ_ASSERT(NS_IsMainThread());
 
-  mNativeCameraControl->ReleaseHardware(this, this);
-  mNativeCameraControl = nullptr;
+  mCameraControl->ReleaseHardware();
+  mCameraControl = nullptr;
 }
 
 void
 MediaEngineWebRTCVideoSource::StartImpl(webrtc::CaptureCapability aCapability) {
   MOZ_ASSERT(NS_IsMainThread());
 
-  idl::CameraSize size;
-  size.width = aCapability.width;
-  size.height = aCapability.height;
-  mNativeCameraControl->GetPreviewStream(size, this, this);
+  ICameraControl::Configuration config;
+  config.mMode = ICameraControl::kPictureMode;
+  config.mPreviewSize.width = aCapability.width;
+  config.mPreviewSize.height = aCapability.height;
+  mCameraControl->SetConfiguration(config);
+  mCameraControl->Set(CAMERA_PARAM_PICTURESIZE, config.mPreviewSize);
 }
 
 void
 MediaEngineWebRTCVideoSource::StopImpl() {
   MOZ_ASSERT(NS_IsMainThread());
 
-  mNativeCameraControl->StopPreview();
-  mPreviewStream = nullptr;
+  mCameraControl->StopPreview();
 }
 
 void
 MediaEngineWebRTCVideoSource::SnapshotImpl() {
-
   MOZ_ASSERT(NS_IsMainThread());
-
-  idl::CameraSize size;
-  size.width = mCapability.width;
-  size.height = mCapability.height;
-
-  idl::CameraPosition cameraPosition;
-  cameraPosition.latitude = NAN;
-  cameraPosition.longitude = NAN;
-  cameraPosition.altitude = NAN;
-  cameraPosition.timestamp = NAN;
-
-  mNativeCameraControl->TakePicture(size, 0, NS_LITERAL_STRING("jpeg"), cameraPosition, PR_Now() / 1000000, this, this);
+  mCameraControl->TakePicture();
 }
 
-// nsICameraGetCameraCallback
-nsresult
-MediaEngineWebRTCVideoSource::HandleEvent(nsISupports* /* unused */) {
-  MOZ_ASSERT(NS_IsMainThread());
+void
+MediaEngineWebRTCVideoSource::OnHardwareStateChange(HardwareState aState)
+{
   ReentrantMonitorAutoEnter sync(mCallbackMonitor);
-  mNativeCameraControl = static_cast<nsGonkCameraControl*>(mDOMCameraControl->GetNativeCameraControl().get());
-  mState = kAllocated;
+  if (aState == CameraControlListener::kHardwareOpen) {
+    mState = kAllocated;
+  } else {
+    mState = kReleased;
+    mCameraControl->RemoveListener(this);
+  }
   mCallbackMonitor.Notify();
-  return NS_OK;
 }
 
-// nsICameraPreviewStreamCallback
-nsresult
-MediaEngineWebRTCVideoSource::HandleEvent(nsIDOMMediaStream* stream) {
-  MOZ_ASSERT(NS_IsMainThread());
+void
+MediaEngineWebRTCVideoSource::OnConfigurationChange(const CameraListenerConfiguration& aConfiguration)
+{
   ReentrantMonitorAutoEnter sync(mCallbackMonitor);
-  mPreviewStream = static_cast<DOMCameraPreview*>(stream);
-  mPreviewStream->Start();
-  CameraPreviewMediaStream* cameraStream = static_cast<CameraPreviewMediaStream*>(mPreviewStream->GetStream());
-  cameraStream->SetFrameCallback(this);
   mState = kStarted;
   mCallbackMonitor.Notify();
-  return NS_OK;
 }
 
-// nsICameraTakePictureCallback
-nsresult
-MediaEngineWebRTCVideoSource::HandleEvent(nsIDOMBlob* picture) {
-  MOZ_ASSERT(NS_IsMainThread());
+void
+MediaEngineWebRTCVideoSource::OnError(CameraErrorContext aContext, const nsACString& aError)
+{
   ReentrantMonitorAutoEnter sync(mCallbackMonitor);
-  mLastCapture = static_cast<nsIDOMFile*>(picture);
   mCallbackMonitor.Notify();
-  return NS_OK;
-}
-
-// nsICameraReleaseCallback
-nsresult
-MediaEngineWebRTCVideoSource::HandleEvent() {
-  MOZ_ASSERT(NS_IsMainThread());
-  ReentrantMonitorAutoEnter sync(mCallbackMonitor);
-  mState = kReleased;
-  mCallbackMonitor.Notify();
-  return NS_OK;
 }
 
-// nsICameraErrorCallback
-nsresult
-MediaEngineWebRTCVideoSource::HandleEvent(const nsAString& error) {
-  MOZ_ASSERT(NS_IsMainThread());
+void
+MediaEngineWebRTCVideoSource::OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType)
+{
   ReentrantMonitorAutoEnter sync(mCallbackMonitor);
+  mLastCapture =
+    static_cast<nsIDOMFile*>(new nsDOMMemoryFile(static_cast<void*>(aData),
+                                                 static_cast<uint64_t>(aLength),
+                                                 aMimeType));
   mCallbackMonitor.Notify();
-  return NS_OK;
 }
 
-//Except this one. This callback should called on camera preview thread.
-void
-MediaEngineWebRTCVideoSource::OnNewFrame(const gfxIntSize& aIntrinsicSize, layers::Image* aImage) {
+bool
+MediaEngineWebRTCVideoSource::OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight) {
   MonitorAutoLock enter(mMonitor);
   if (mState == kStopped) {
-    return;
+    return false;
   }
   mImage = aImage;
-  if (mWidth != aIntrinsicSize.width || mHeight != aIntrinsicSize.height) {
-    mWidth = aIntrinsicSize.width;
-    mHeight = aIntrinsicSize.height;
+  if (mWidth != static_cast<int>(aWidth) || mHeight != static_cast<int>(aHeight)) {
+    mWidth = aWidth;
+    mHeight = aHeight;
     LOG(("Video FrameSizeChange: %ux%u", mWidth, mHeight));
   }
+  return true; // return true because we're accepting the frame
 }
 #endif
 
 }
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -3081,18 +3081,18 @@ onInstallSuccessAck: function onInstallS
       return [zipReader, isSigned];
 
     }).bind(this));
   },
 
   _openSignedPackage: function(aZipFile, aCertDb) {
     let deferred = Promise.defer();
 
-    aCertDb.openSignedJARFileAsync(
-       aZipFile,
+    aCertDb.openSignedAppFileAsync(
+       Ci.nsIX509CertDB.AppMarketplaceProdPublicRoot, aZipFile,
        function(aRv, aZipReader) {
          deferred.resolve([aRv, aZipReader]);
        }
     );
 
     return deferred.promise;
   },
 
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -155,17 +155,16 @@
 #include "nsIDOMIccManager.h"
 #include "nsIDOMMobileConnection.h"
 #endif // MOZ_B2G_RIL
 
 #ifdef MOZ_B2G_FM
 #include "FMRadio.h"
 #endif
 
-#include "nsIDOMCameraManager.h"
 #include "nsIDOMGlobalObjectConstructor.h"
 #include "nsIDOMLockedFile.h"
 #include "nsDebug.h"
 
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/Likely.h"
 #include "WindowNamedPropertiesHandler.h"
 #include "nsIInterfaceInfoManager.h"
@@ -465,19 +464,16 @@ static nsDOMClassInfoData sClassInfoData
   NS_DEFINE_CLASSINFO_DATA(CSSPageRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
 #ifdef MOZ_B2G_RIL
   NS_DEFINE_CLASSINFO_DATA(MozIccManager, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 #endif
 
-  NS_DEFINE_CLASSINFO_DATA(CameraCapabilities, nsDOMGenericSH,
-                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
-
   NS_DEFINE_CLASSINFO_DATA(LockedFile, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(CSSFontFeatureValuesRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(UserDataHandler, nsDOMGenericSH,
                                       DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(XPathNamespace, nsDOMGenericSH,
@@ -1171,20 +1167,16 @@ nsDOMClassInfo::Init()
 #ifdef MOZ_B2G_RIL
   DOM_CLASSINFO_MAP_BEGIN(MozIccManager, nsIDOMMozIccManager)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozIccManager)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
   DOM_CLASSINFO_MAP_END
 
 #endif
 
-  DOM_CLASSINFO_MAP_BEGIN(CameraCapabilities, nsICameraCapabilities)
-    DOM_CLASSINFO_MAP_ENTRY(nsICameraCapabilities)
-  DOM_CLASSINFO_MAP_END
-
   DOM_CLASSINFO_MAP_BEGIN(LockedFile, nsIDOMLockedFile)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMLockedFile)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(CSSFontFeatureValuesRule, nsIDOMCSSFontFeatureValuesRule)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSFontFeatureValuesRule)
   DOM_CLASSINFO_MAP_END
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -98,18 +98,16 @@ DOMCI_CLASS(MozCSSKeyframeRule)
 DOMCI_CLASS(MozCSSKeyframesRule)
 
 DOMCI_CLASS(CSSPageRule)
 
 #ifdef MOZ_B2G_RIL
 DOMCI_CLASS(MozIccManager)
 #endif
 
-DOMCI_CLASS(CameraCapabilities)
-
 DOMCI_CLASS(LockedFile)
 
 DOMCI_CLASS(CSSFontFeatureValuesRule)
 
 DOMCI_CLASS(UserDataHandler)
 DOMCI_CLASS(XPathNamespace)
 DOMCI_CLASS(XULControlElement)
 DOMCI_CLASS(XULLabeledControlElement)
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -175,16 +175,21 @@ DOMInterfaces = {
     'headerFile': 'BluetoothDevice.h'
 },
 
 'BluetoothManager': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothManager',
     'headerFile': 'BluetoothManager.h'
 },
 
+'CameraCapabilities': {
+    'nativeType': 'mozilla::dom::CameraCapabilities',
+    'headerFile': 'DOMCameraCapabilities.h'
+},
+
 'CameraControl': {
     'nativeType': 'mozilla::nsDOMCameraControl',
     'headerFile': 'DOMCameraControl.h',
     'binaryNames': {
         "release": "ReleaseHardware"
     }
 },
 
@@ -1962,20 +1967,8 @@ addExternalIface('SVGNumber')
 addExternalIface('URI', nativeType='nsIURI', headerFile='nsIURI.h',
                  notflattened=True)
 addExternalIface('UserDataHandler')
 addExternalIface('XPathResult', nativeType='nsISupports')
 addExternalIface('XPathExpression')
 addExternalIface('XPathNSResolver')
 addExternalIface('XULCommandDispatcher')
 addExternalIface('DataTransfer', notflattened=True)
-addExternalIface('GetCameraCallback', nativeType='nsICameraGetCameraCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraErrorCallback', nativeType='nsICameraErrorCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraCapabilities', nativeType='nsICameraCapabilities', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraAutoFocusCallback', nativeType='nsICameraAutoFocusCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraShutterCallback', nativeType='nsICameraShutterCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraClosedCallback', nativeType='nsICameraClosedCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraTakePictureCallback', nativeType='nsICameraTakePictureCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraReleaseCallback', nativeType='nsICameraReleaseCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraStartRecordingCallback', nativeType='nsICameraStartRecordingCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraPreviewStateChange', nativeType='nsICameraPreviewStateChange', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraPreviewStreamCallback', nativeType='nsICameraPreviewStreamCallback', headerFile='nsIDOMCameraManager.h')
-addExternalIface('CameraRecorderStateChange', nativeType='nsICameraRecorderStateChange', headerFile='nsIDOMCameraManager.h')
--- a/dom/bluetooth/BluetoothService.cpp
+++ b/dom/bluetooth/BluetoothService.cpp
@@ -560,17 +560,17 @@ BluetoothService::HandleStartup()
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!sToggleInProgress);
 
   nsCOMPtr<nsISettingsService> settings =
     do_GetService("@mozilla.org/settingsService;1");
   NS_ENSURE_TRUE(settings, NS_ERROR_UNEXPECTED);
 
   nsCOMPtr<nsISettingsServiceLock> settingsLock;
-  nsresult rv = settings->CreateLock(getter_AddRefs(settingsLock));
+  nsresult rv = settings->CreateLock(nullptr, getter_AddRefs(settingsLock));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsRefPtr<StartupTask> callback = new StartupTask();
   rv = settingsLock->Get(BLUETOOTH_ENABLED_SETTING, callback);
   NS_ENSURE_SUCCESS(rv, rv);
 
   sToggleInProgress = true;
   return NS_OK;
--- a/dom/bluetooth/bluedroid/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothHfpManager.cpp
@@ -398,17 +398,17 @@ BluetoothHfpManager::Init()
   mListener = new BluetoothRilListener();
   NS_ENSURE_TRUE(mListener->Listen(true), false);
 
   nsCOMPtr<nsISettingsService> settings =
     do_GetService("@mozilla.org/settingsService;1");
   NS_ENSURE_TRUE(settings, false);
 
   nsCOMPtr<nsISettingsServiceLock> settingsLock;
-  nsresult rv = settings->CreateLock(getter_AddRefs(settingsLock));
+  nsresult rv = settings->CreateLock(nullptr, getter_AddRefs(settingsLock));
   NS_ENSURE_SUCCESS(rv, false);
 
   nsRefPtr<GetVolumeTask> callback = new GetVolumeTask();
   rv = settingsLock->Get(AUDIO_VOLUME_BT_SCO_ID, callback);
   NS_ENSURE_SUCCESS(rv, false);
 
   return true;
 }
--- a/dom/bluetooth/bluez/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/bluez/BluetoothHfpManager.cpp
@@ -428,17 +428,17 @@ BluetoothHfpManager::Init()
   }
 #endif
 
   nsCOMPtr<nsISettingsService> settings =
     do_GetService("@mozilla.org/settingsService;1");
   NS_ENSURE_TRUE(settings, false);
 
   nsCOMPtr<nsISettingsServiceLock> settingsLock;
-  nsresult rv = settings->CreateLock(getter_AddRefs(settingsLock));
+  nsresult rv = settings->CreateLock(nullptr, getter_AddRefs(settingsLock));
   NS_ENSURE_SUCCESS(rv, false);
 
   nsRefPtr<GetVolumeTask> callback = new GetVolumeTask();
   rv = settingsLock->Get(AUDIO_VOLUME_BT_SCO_ID, callback);
   NS_ENSURE_SUCCESS(rv, false);
 
   Listen();
 
new file mode 100644
--- /dev/null
+++ b/dom/camera/AutoRwLock.h
@@ -0,0 +1,49 @@
+/* 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 RWLOCK_AUTO_ENTER_H
+#define RWLOCK_AUTO_ENTER_H
+
+#include "prrwlock.h"
+#include "mozilla/Assertions.h"
+
+class RwLockAutoEnterRead
+{
+public:
+  RwLockAutoEnterRead(PRRWLock* aRwLock)
+    : mRwLock(aRwLock)
+  {
+    MOZ_ASSERT(mRwLock);
+    PR_RWLock_Rlock(mRwLock);
+  }
+
+  ~RwLockAutoEnterRead()
+  {
+    PR_RWLock_Unlock(mRwLock);
+  }
+
+protected:
+  PRRWLock* mRwLock;
+};
+
+class RwLockAutoEnterWrite
+{
+public:
+  RwLockAutoEnterWrite(PRRWLock* aRwLock)
+    : mRwLock(aRwLock)
+  {
+    MOZ_ASSERT(mRwLock);
+    PR_RWLock_Wlock(mRwLock);
+  }
+
+  ~RwLockAutoEnterWrite()
+  {
+    PR_RWLock_Unlock(mRwLock);
+  }
+
+protected:
+  PRRWLock* mRwLock;
+};
+
+#endif // RWLOCK_AUTO_ENTER_H
--- a/dom/camera/CameraCommon.h
+++ b/dom/camera/CameraCommon.h
@@ -14,17 +14,16 @@
 #define __func__ __FILE__
 #endif
 #endif
 
 #ifndef NAN
 #define NAN std::numeric_limits<double>::quiet_NaN()
 #endif
 
-#include "nsIDOMCameraManager.h"
 #include "prlog.h"
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* GetCameraLog();
 #define DOM_CAMERA_LOG( type, ... ) PR_LOG(GetCameraLog(), (PRLogModuleLevel)type, ( __VA_ARGS__ ))
 #else
 #define DOM_CAMERA_LOG( type, ... )
 #endif
@@ -57,54 +56,16 @@ enum {
 #else
 #define DOM_CAMERA_LOGR( ... )
 #endif
 #define DOM_CAMERA_LOGT( ... )      DOM_CAMERA_LOG( DOM_CAMERA_LOG_TRACE, __VA_ARGS__ )
 #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__ )
 
-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_PICTURESIZE,
-  CAMERA_PARAM_THUMBNAILSIZE,
-  CAMERA_PARAM_THUMBNAILQUALITY,
-  CAMERA_PARAM_SENSORANGLE,
-
-  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,
-  CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES
-};
-
 #ifdef PR_LOGGING
 
 static inline void nsLogAddRefCamera(const char *file, uint32_t line, void* p, uint32_t count, const char *clazz, uint32_t size)
 {
   if (count == 1) {
     DOM_CAMERA_LOGR("++++++++++++++++++++++++++++++++++++++++");
   }
   DOM_CAMERA_LOGR("%s:%d : CAMREF-ADD(%s): this=%p, mRefCnt=%d\n", file, line, clazz, p, count);
--- a/dom/camera/CameraControlImpl.cpp
+++ b/dom/camera/CameraControlImpl.cpp
@@ -1,523 +1,576 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "CameraControlImpl.h"
 #include "base/basictypes.h"
 #include "mozilla/Assertions.h"
-#include "DOMCameraPreview.h"
+#include "mozilla/unused.h"
+#include "nsIWeakReferenceUtils.h"
 #include "CameraRecorderProfiles.h"
-#include "CameraControlImpl.h"
 #include "CameraCommon.h"
 #include "nsGlobalWindow.h"
 #include "DeviceStorageFileDescriptor.h"
+#include "CameraControlListener.h"
 
 using namespace mozilla;
-using namespace mozilla::dom;
-using namespace mozilla::idl;
 
-CameraControlImpl::CameraControlImpl(uint32_t aCameraId, nsIThread* aCameraThread, uint64_t aWindowId)
+nsWeakPtr CameraControlImpl::sCameraThread;
+
+CameraControlImpl::CameraControlImpl(uint32_t aCameraId)
   : mCameraId(aCameraId)
-  , mCameraThread(aCameraThread)
-  , mWindowId(aWindowId)
-  , mFileFormat()
-  , mMaxMeteringAreas(0)
-  , mMaxFocusAreas(0)
-  , mPreviewState(PREVIEW_STOPPED)
-  , mDOMPreview(nullptr)
-  , mAutoFocusOnSuccessCb(nullptr)
-  , mAutoFocusOnErrorCb(nullptr)
-  , mTakePictureOnSuccessCb(nullptr)
-  , mTakePictureOnErrorCb(nullptr)
-  , mOnShutterCb(nullptr)
-  , mOnClosedCb(nullptr)
-  , mOnRecorderStateChangeCb(nullptr)
-  , mOnPreviewStateChangeCb(nullptr)
+  , mPreviewState(CameraControlListener::kPreviewStopped)
+  , mHardwareState(CameraControlListener::kHardwareClosed)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+
+  // reuse the same camera thread to conserve resources
+  nsCOMPtr<nsIThread> ct = do_QueryReferent(sCameraThread);
+  if (ct) {
+    mCameraThread = ct.forget();
+  } else {
+    nsresult rv = NS_NewNamedThread("CameraThread", getter_AddRefs(mCameraThread));
+    unused << rv; // swallow rv to suppress a compiler warning when the macro
+                  // is #defined to nothing (i.e. in non-DEBUG builds).
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    // keep a weak reference to the new thread
+    sCameraThread = do_GetWeakReference(mCameraThread);
+  }
+
+  mListenerLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "CameraControlImpl.Listeners.Lock");
 }
 
 CameraControlImpl::~CameraControlImpl()
 {
-  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-}
-
-// Helpers for string properties.
-nsresult
-CameraControlImpl::Set(uint32_t aKey, const nsAString& aValue)
-{
-  SetParameter(aKey, NS_ConvertUTF16toUTF8(aValue).get());
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(uint32_t aKey, nsAString& aValue)
-{
-  const char* value = GetParameterConstChar(aKey);
-  if (!value) {
-    return NS_ERROR_FAILURE;
-  }
-
-  aValue.AssignASCII(value);
-  return NS_OK;
-}
-
-// Helpers for doubles.
-nsresult
-CameraControlImpl::Set(uint32_t aKey, double aValue)
-{
-  SetParameter(aKey, aValue);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(uint32_t aKey, double* aValue)
-{
-  MOZ_ASSERT(aValue);
-  *aValue = GetParameterDouble(aKey);
-  return NS_OK;
-}
-
-// Helper for weighted regions.
-nsresult
-CameraControlImpl::Set(JSContext* aCx, uint32_t aKey, const JS::Value& aValue, uint32_t 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;
-
-  JS::Rooted<JSObject*> regions(aCx, &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 (uint32_t i = 0; i < length; ++i) {
-    JS::Rooted<JS::Value> v(aCx);
-
-    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.address());
-    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
-CameraControlImpl::Get(JSContext* aCx, uint32_t aKey, JS::Value* aValue)
-{
-  nsTArray<CameraRegion> regionArray;
-
-  GetParameter(aKey, regionArray);
-
-  JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, 0));
-  if (!array) {
-    return NS_ERROR_OUT_OF_MEMORY;
+  if (mListenerLock) {
+    PR_DestroyRWLock(mListenerLock);
+    mListenerLock = nullptr;
   }
-
-  uint32_t length = regionArray.Length();
-  DOM_CAMERA_LOGI("%s:%d : got %d regions\n", __func__, __LINE__, length);
-
-  for (uint32_t i = 0; i < length; ++i) {
-    CameraRegion* r = &regionArray[i];
-    JS::Rooted<JS::Value> v(aCx);
-
-    JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::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;
-    }
-
-    if (!JS_SetElement(aCx, array, i, o)) {
-      return NS_ERROR_FAILURE;
-    }
-  }
-
-  *aValue = JS::ObjectValue(*array);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Set(nsICameraShutterCallback* aOnShutter)
-{
-  mOnShutterCb = new nsMainThreadPtrHolder<nsICameraShutterCallback>(aOnShutter);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(nsICameraShutterCallback** aOnShutter)
-{
-  *aOnShutter = mOnShutterCb;
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Set(nsICameraClosedCallback* aOnClosed)
-{
-  mOnClosedCb = new nsMainThreadPtrHolder<nsICameraClosedCallback>(aOnClosed);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(nsICameraClosedCallback** aOnClosed)
-{
-  *aOnClosed = mOnClosedCb;
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Set(nsICameraRecorderStateChange* aOnRecorderStateChange)
-{
-  mOnRecorderStateChangeCb = new nsMainThreadPtrHolder<nsICameraRecorderStateChange>(aOnRecorderStateChange);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(nsICameraRecorderStateChange** aOnRecorderStateChange)
-{
-  *aOnRecorderStateChange = mOnRecorderStateChangeCb;
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Set(nsICameraPreviewStateChange* aOnPreviewStateChange)
-{
-  mOnPreviewStateChangeCb = new nsMainThreadPtrHolder<nsICameraPreviewStateChange>(aOnPreviewStateChange);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(nsICameraPreviewStateChange** aOnPreviewStateChange)
-{
-  *aOnPreviewStateChange = mOnPreviewStateChangeCb;
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Set(uint32_t aKey, const idl::CameraSize& aSize)
-{
-  SetParameter(aKey, aSize);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(uint32_t aKey, idl::CameraSize& aSize)
-{
-  GetParameter(aKey, aSize);
-  return NS_OK;
-}
-
-nsresult
-CameraControlImpl::Get(uint32_t aKey, int32_t* aValue)
-{
-  MOZ_ASSERT(aValue);
-  *aValue = GetParameterInt32(aKey);
-  return NS_OK;
 }
 
 already_AddRefed<RecorderProfileManager>
 CameraControlImpl::GetRecorderProfileManager()
 {
   return GetRecorderProfileManagerImpl();
 }
 
 void
 CameraControlImpl::Shutdown()
 {
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-  mAutoFocusOnSuccessCb = nullptr;
-  mAutoFocusOnErrorCb = nullptr;
-  mTakePictureOnSuccessCb = nullptr;
-  mTakePictureOnErrorCb = nullptr;
-  mOnShutterCb = nullptr;
-  mOnClosedCb = nullptr;
-  mOnRecorderStateChangeCb = nullptr;
-  mOnPreviewStateChangeCb = nullptr;
+}
+
+void
+CameraControlImpl::OnHardwareStateChange(CameraControlListener::HardwareState aNewState)
+{
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread. On Gonk, it may be called from the camera's
+  //  local binder thread, should the mediaserver process die.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  if (aNewState == mHardwareState) {
+    DOM_CAMERA_LOGI("OnHardwareStateChange: state did not change from %d\n", mHardwareState);
+    return;
+  }
+
+#ifdef PR_LOGGING
+  const char* state[] = { "open", "closed", "failed" };
+  MOZ_ASSERT(aNewState >= 0);
+  if (static_cast<unsigned int>(aNewState) < sizeof(state) / sizeof(state[0])) {
+    DOM_CAMERA_LOGI("New hardware state is '%s'\n", state[aNewState]);
+  } else {
+    DOM_CAMERA_LOGE("OnHardwareStateChange: got invalid HardwareState value %d\n", aNewState);
+  }
+#endif
+
+  mHardwareState = aNewState;
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnHardwareStateChange(mHardwareState);
+  }
 }
 
 void
-CameraControlImpl::OnShutterInternal()
+CameraControlImpl::OnConfigurationChange()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  DOM_CAMERA_LOGI("OnConfigurationChange : %d listeners\n", mListeners.Length());
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnConfigurationChange(mCurrentConfiguration);
+  }
+}
+
+void
+CameraControlImpl::OnAutoFocusComplete(bool aAutoFocusSucceeded)
 {
-  DOM_CAMERA_LOGI("** SNAP **\n");
-  if (mOnShutterCb.get()) {
-    mOnShutterCb->HandleEvent();
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread. On Gonk, it is called from the camera
+  //  library's auto focus thread.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnAutoFocusComplete(aAutoFocusSucceeded);
+  }
+}
+
+void
+CameraControlImpl::OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType)
+{
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread. On Gonk, it is called from the camera
+  //  library's snapshot thread.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnTakePictureComplete(aData, aLength, aMimeType);
   }
 }
 
 void
 CameraControlImpl::OnShutter()
 {
-  nsCOMPtr<nsIRunnable> onShutter = NS_NewRunnableMethod(this, &CameraControlImpl::OnShutterInternal);
-  nsresult rv = NS_DispatchToMainThread(onShutter);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGW("Failed to dispatch onShutter event to main thread (%d)\n", rv);
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread. On Gonk, it is called from the camera driver's
+  //  preview thread.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnShutter();
   }
 }
 
-class OnClosedTask : public nsRunnable
-{
-public:
-  OnClosedTask(nsMainThreadPtrHandle<nsICameraClosedCallback> onClosed, uint64_t aWindowId)
-    : mOnClosedCb(onClosed)
-    , mWindowId(aWindowId)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~OnClosedTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnClosedCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      mOnClosedCb->HandleEvent();
-    }
-    return NS_OK;
-  }
-
-protected:
-  nsMainThreadPtrHandle<nsICameraClosedCallback> mOnClosedCb;
-  uint64_t mWindowId;
-};
-
 void
 CameraControlImpl::OnClosed()
 {
-  nsCOMPtr<nsIRunnable> onClosed = new OnClosedTask(mOnClosedCb, mWindowId);
-  nsresult rv = NS_DispatchToMainThread(onClosed);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGW("Failed to dispatch onClosed event to main thread (%d)\n", rv);
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnHardwareStateChange(CameraControlListener::kHardwareClosed);
   }
 }
 
 void
-CameraControlImpl::OnRecorderStateChange(const nsString& aStateMsg, int32_t aStatus, int32_t aTrackNumber)
+CameraControlImpl::OnRecorderStateChange(CameraControlListener::RecorderState aState,
+                                         int32_t aStatus, int32_t aTrackNumber)
 {
-  DOM_CAMERA_LOGI("OnRecorderStateChange: '%s'\n", NS_ConvertUTF16toUTF8(aStateMsg).get());
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread. On Gonk, it is called from the media encoder
+  //  thread.
+  RwLockAutoEnterRead lock(mListenerLock);
 
-  nsCOMPtr<nsIRunnable> onRecorderStateChange = new CameraRecorderStateChange(mOnRecorderStateChangeCb, aStateMsg, aStatus, aTrackNumber, mWindowId);
-  nsresult rv = NS_DispatchToMainThread(onRecorderStateChange);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGE("Failed to dispatch onRecorderStateChange event to main thread (%d)\n", rv);
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnRecorderStateChange(aState, aStatus, aTrackNumber);
   }
 }
 
 void
-CameraControlImpl::OnPreviewStateChange(PreviewState aNewState)
+CameraControlImpl::OnPreviewStateChange(CameraControlListener::PreviewState aNewState)
 {
+  // This callback runs on the Main Thread and the Camera Thread, and
+  //  may run on the local binder thread, should the mediaserver
+  //  process die.
+  RwLockAutoEnterRead lock(mListenerLock);
+
   if (aNewState == mPreviewState) {
     DOM_CAMERA_LOGI("OnPreviewStateChange: state did not change from %d\n", mPreviewState);
     return;
   }
 
-  nsString msg;
-  switch (aNewState) {
-    case PREVIEW_STOPPED:
-      msg = NS_LITERAL_STRING("stopped");
-      break;
+#ifdef PR_LOGGING
+  const char* state[] = { "stopped", "paused", "started" };
+  MOZ_ASSERT(aNewState >= 0);
+  if (static_cast<unsigned int>(aNewState) < sizeof(state) / sizeof(state[0])) {
+    DOM_CAMERA_LOGI("New preview state is '%s'\n", state[aNewState]);
+  } else {
+    DOM_CAMERA_LOGE("OnPreviewStateChange: got unknown PreviewState value %d\n", aNewState);
+  }
+#endif
 
-    case PREVIEW_STARTED:
-      msg = NS_LITERAL_STRING("started");
-      break;
-
-    default:
-      MOZ_ASSUME_UNREACHABLE("Preview state can only be PREVIEW_STOPPED or _STARTED!");
-  }
-
-  // const nsString& aStateMsg)
-  DOM_CAMERA_LOGI("OnPreviewStateChange: '%s'\n", NS_ConvertUTF16toUTF8(msg).get());
   mPreviewState = aNewState;
 
-  nsCOMPtr<nsIRunnable> onPreviewStateChange = new CameraPreviewStateChange(mOnPreviewStateChangeCb, msg, mWindowId);
-  nsresult rv = NS_DispatchToMainThread(onPreviewStateChange);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGE("Failed to dispatch onPreviewStateChange event to main thread (%d)\n", rv);
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnPreviewStateChange(mPreviewState);
+  }
+}
+
+bool
+CameraControlImpl::OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight)
+{
+  // This function runs on neither the Main Thread nor the Camera Thread.
+  //  On Gonk, it is called from the camera driver's preview thread.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+  DOM_CAMERA_LOGI("OnNewPreviewFrame: we have %d preview frame listener(s)\n",
+    mListeners.Length());
+
+  bool consumed = false;
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    consumed = l->OnNewPreviewFrame(aImage, aWidth, aHeight) || consumed;
+  }
+  return consumed;
+}
+
+void
+CameraControlImpl::OnError(CameraControlListener::CameraErrorContext aContext,
+                           CameraControlListener::CameraError aError)
+{
+  // This callback can run on threads other than the Main Thread and
+  //  the Camera Thread.
+  RwLockAutoEnterRead lock(mListenerLock);
+
+#ifdef PR_LOGGING
+  const char* error[] = { "camera-service-failed", "unknown" };
+  if (static_cast<unsigned int>(aError) < sizeof(error) / sizeof(error[0])) {
+    DOM_CAMERA_LOGW("CameraControlImpl::OnError : aContext=%u, msg='%s'\n",
+      aContext, error[aError]);
+  } else {
+    DOM_CAMERA_LOGE("CameraControlImpl::OnError : aContext=%u, unknown error=%d\n",
+      aContext, aError);
+  }
+#endif
+
+  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    CameraControlListener* l = mListeners[i];
+    l->OnError(aContext, aError);
   }
 }
 
-nsresult
-CameraControlImpl::GetPreviewStream(CameraSize aSize, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError)
+// Camera control asynchronous message; these are dispatched from
+//  the Main Thread to the Camera Thread, where they are consumed.
+
+class CameraControlImpl::ControlMessage : public nsRunnable
 {
-  nsCOMPtr<nsIRunnable> getPreviewStreamTask = new GetPreviewStreamTask(this, aSize, onSuccess, onError);
-  return mCameraThread->Dispatch(getPreviewStreamTask, NS_DISPATCH_NORMAL);
+public:
+  ControlMessage(CameraControlImpl* aCameraControl,
+                 CameraControlListener::CameraErrorContext aContext)
+    : mCameraControl(aCameraControl)
+    , mContext(aContext)
+  {
+    MOZ_COUNT_CTOR(CameraControlImpl::ControlMessage);
+  }
+
+  virtual ~ControlMessage()
+  {
+    MOZ_COUNT_DTOR(CameraControlImpl::ControlMessage);
+  }
+
+  virtual nsresult RunImpl() = 0;
+
+  NS_IMETHOD
+  Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(mCameraControl);
+    MOZ_ASSERT(NS_GetCurrentThread() == mCameraControl->mCameraThread);
+
+    nsresult rv = RunImpl();
+    if (NS_FAILED(rv)) {
+      DOM_CAMERA_LOGW("Camera control API failed at %d with 0x%x\n", mContext, rv);
+      // XXXmikeh - do we want to report a more specific error code?
+      mCameraControl->OnError(mContext, CameraControlListener::kErrorApiFailed);
+    }
+
+    return NS_OK;
+  }
+
+protected:
+  nsRefPtr<CameraControlImpl> mCameraControl;
+  CameraControlListener::CameraErrorContext mContext;
+};
+
+nsresult
+CameraControlImpl::SetConfiguration(const Configuration& aConfig)
+{
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext,
+            const Configuration& aConfig)
+      : ControlMessage(aCameraControl, aContext)
+      , mConfig(aConfig)
+    { }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->SetConfigurationImpl(mConfig);
+    }
+
+  protected:
+    Configuration mConfig;
+  };
+
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInSetConfiguration, aConfig), NS_DISPATCH_NORMAL);
 }
 
 nsresult
-CameraControlImpl::AutoFocus(nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError)
+CameraControlImpl::AutoFocus(bool aCancelExistingCall)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  bool cancel = false;
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext,
+            bool aCancelExistingCall)
+      : ControlMessage(aCameraControl, aContext)
+      , mCancelExistingCall(aCancelExistingCall)
+    { }
 
-  nsCOMPtr<nsICameraAutoFocusCallback> cb = mAutoFocusOnSuccessCb.get();
-  if (cb) {
-    /**
-     * We already have a callback, so someone has already
-     * called autoFocus() -- cancel it.
-     */
-    mAutoFocusOnSuccessCb = nullptr;
-    mAutoFocusOnErrorCb = nullptr;
-    cancel = true;
-  }
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->AutoFocusImpl(mCancelExistingCall);
+    }
 
-  nsCOMPtr<nsIRunnable> autoFocusTask = new AutoFocusTask(this, cancel, onSuccess, onError);
-  return mCameraThread->Dispatch(autoFocusTask, NS_DISPATCH_NORMAL);
+  protected:
+    bool mCancelExistingCall;
+  };
+
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInAutoFocus, aCancelExistingCall), NS_DISPATCH_NORMAL);
 }
 
 nsresult
-CameraControlImpl::TakePicture(const CameraSize& aSize, int32_t aRotation, const nsAString& aFileFormat, CameraPosition aPosition, uint64_t aDateTime, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError)
+CameraControlImpl::TakePicture()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  bool cancel = false;
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext)
+      : ControlMessage(aCameraControl, aContext)
+    { }
 
-  nsCOMPtr<nsICameraTakePictureCallback> cb = mTakePictureOnSuccessCb.get();
-  if (cb) {
-    /**
-     * We already have a callback, so someone has already
-     * called takePicture() -- cancel it.
-     */
-    mTakePictureOnSuccessCb = nullptr;
-    mTakePictureOnErrorCb = nullptr;
-    cancel = true;
-  }
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->TakePictureImpl();
+    }
+  };
 
-  nsCOMPtr<nsIRunnable> takePictureTask = new TakePictureTask(this, cancel, aSize, aRotation, aFileFormat, aPosition, aDateTime, onSuccess, onError);
-  return mCameraThread->Dispatch(takePictureTask, NS_DISPATCH_NORMAL);
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInTakePicture), NS_DISPATCH_NORMAL);
 }
 
 nsresult
-CameraControlImpl::StartRecording(CameraStartRecordingOptions* aOptions, DeviceStorageFileDescriptor* aFileDescriptor, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError)
+CameraControlImpl::StartRecording(DeviceStorageFileDescriptor* aFileDescriptor,
+                                  const StartRecordingOptions* aOptions)
 {
-  nsCOMPtr<nsIRunnable> startRecordingTask = new StartRecordingTask(this, *aOptions, aFileDescriptor, onSuccess, onError, mWindowId);
-  return mCameraThread->Dispatch(startRecordingTask, NS_DISPATCH_NORMAL);
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext,
+            const StartRecordingOptions* aOptions,
+            DeviceStorageFileDescriptor* aFileDescriptor)
+      : ControlMessage(aCameraControl, aContext)
+      , mOptionsPassed(false)
+      , mFileDescriptor(aFileDescriptor)
+    {
+      if (aOptions) {
+        mOptions = *aOptions;
+        mOptionsPassed = true;
+      }
+    }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->StartRecordingImpl(mFileDescriptor,
+        mOptionsPassed ? &mOptions : nullptr);
+    }
+
+  protected:
+    StartRecordingOptions mOptions;
+    bool mOptionsPassed;
+    nsRefPtr<DeviceStorageFileDescriptor> mFileDescriptor;
+  };
+
+
+  return mCameraThread->Dispatch(new Message(this, CameraControlListener::kInStartRecording,
+    aOptions, aFileDescriptor), NS_DISPATCH_NORMAL);
 }
 
 nsresult
 CameraControlImpl::StopRecording()
 {
-  nsCOMPtr<nsIRunnable> stopRecordingTask = new StopRecordingTask(this);
-  return mCameraThread->Dispatch(stopRecordingTask, NS_DISPATCH_NORMAL);
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext)
+      : ControlMessage(aCameraControl, aContext)
+    { }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->StopRecordingImpl();
+    }
+  };
+
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInStopRecording), NS_DISPATCH_NORMAL);
 }
 
 nsresult
-CameraControlImpl::StartPreview(DOMCameraPreview* aDOMPreview)
+CameraControlImpl::StartPreview()
 {
-  nsCOMPtr<nsIRunnable> startPreviewTask = new StartPreviewTask(this, aDOMPreview);
-  return mCameraThread->Dispatch(startPreviewTask, NS_DISPATCH_NORMAL);
-}
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext)
+      : ControlMessage(aCameraControl, aContext)
+    { }
 
-void
-CameraControlImpl::StopPreview()
-{
-  nsCOMPtr<nsIRunnable> stopPreviewTask = new StopPreviewTask(this);
-  mCameraThread->Dispatch(stopPreviewTask, NS_DISPATCH_NORMAL);
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->StartPreviewImpl();
+    }
+  };
+
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInStartPreview), NS_DISPATCH_NORMAL);
 }
 
 nsresult
-CameraControlImpl::GetPreviewStreamVideoMode(CameraRecorderOptions* aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError)
+CameraControlImpl::StopPreview()
 {
-  nsCOMPtr<nsIRunnable> getPreviewStreamVideoModeTask = new GetPreviewStreamVideoModeTask(this, *aOptions, onSuccess, onError);
-  return mCameraThread->Dispatch(getPreviewStreamVideoModeTask, NS_DISPATCH_NORMAL);
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext)
+      : ControlMessage(aCameraControl, aContext)
+    { }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->StopPreviewImpl();
+    }
+  };
+
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInStopPreview), NS_DISPATCH_NORMAL);
 }
 
 nsresult
-CameraControlImpl::ReleaseHardware(nsICameraReleaseCallback* onSuccess, nsICameraErrorCallback* onError)
+CameraControlImpl::ReleaseHardware()
 {
-  nsCOMPtr<nsIRunnable> releaseHardwareTask = new ReleaseHardwareTask(this, onSuccess, onError);
-  return mCameraThread->Dispatch(releaseHardwareTask, NS_DISPATCH_NORMAL);
+  class Message : public ControlMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener::CameraErrorContext aContext)
+      : ControlMessage(aCameraControl, aContext)
+    { }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mCameraControl->ReleaseHardwareImpl();
+    }
+  };
+
+  return mCameraThread->Dispatch(
+    new Message(this, CameraControlListener::kInReleaseHardware), NS_DISPATCH_NORMAL);
 }
 
-bool
-CameraControlImpl::ReceiveFrame(void* aBuffer, ImageFormat aFormat, FrameBuilder aBuilder)
+class CameraControlImpl::ListenerMessage : public CameraControlImpl::ControlMessage
 {
-  if (!mDOMPreview) {
-    return false;
-  }
+public:
+  ListenerMessage(CameraControlImpl* aCameraControl,
+                  CameraControlListener* aListener)
+    : ControlMessage(aCameraControl, CameraControlListener::kInUnspecified)
+    , mListener(aListener)
+  { }
+
+protected:
+  nsRefPtr<CameraControlListener> mListener;
+};
 
-  return mDOMPreview->ReceiveFrame(aBuffer, aFormat, aBuilder);
+void
+CameraControlImpl::AddListenerImpl(already_AddRefed<CameraControlListener> aListener)
+{
+  RwLockAutoEnterWrite lock(mListenerLock);
+
+  CameraControlListener* l = *mListeners.AppendElement() = aListener;
+
+  // Update the newly-added listener's state
+  l->OnConfigurationChange(mCurrentConfiguration);
+  l->OnHardwareStateChange(mHardwareState);
+  l->OnPreviewStateChange(mPreviewState);
 }
 
-NS_IMETHODIMP
-GetPreviewStreamResult::Run()
-{
-  /**
-   * The camera preview stream object is DOM-facing, and as such
-   * must be a cycle-collection participant created on the main
-   * thread.
-   */
-  MOZ_ASSERT(NS_IsMainThread());
+void
+CameraControlImpl::AddListener(CameraControlListener* aListener)
+ {
+  class Message : public ListenerMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl,
+            CameraControlListener* aListener)
+      : ListenerMessage(aCameraControl, aListener)
+    { }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      mCameraControl->AddListenerImpl(mListener.forget());
+      return NS_OK;
+    }
+  };
+
+  mCameraThread->Dispatch(new Message(this, aListener), NS_DISPATCH_NORMAL);
+}
 
-  nsCOMPtr<nsICameraPreviewStreamCallback> onSuccess = mOnSuccessCb.get();
-  nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId);
-  if (onSuccess && nsDOMCameraManager::IsWindowStillActive(mWindowId) && window) {
-    nsCOMPtr<nsIDOMMediaStream> stream =
-      new DOMCameraPreview(window, mCameraControl, mWidth, mHeight,
-	                         mFramesPerSecond);
-    onSuccess->HandleEvent(stream);
-  }
-  return NS_OK;
+void
+CameraControlImpl::RemoveListenerImpl(CameraControlListener* aListener)
+{
+  RwLockAutoEnterWrite lock(mListenerLock);
+
+  nsRefPtr<CameraControlListener> l(aListener);
+  mListeners.RemoveElement(l);
+  // XXXmikeh - do we want to notify the listener that it has been removed?
 }
+
+void
+CameraControlImpl::RemoveListener(CameraControlListener* aListener)
+ {
+  class Message : public ListenerMessage
+  {
+  public:
+    Message(CameraControlImpl* aCameraControl, CameraControlListener* aListener)
+      : ListenerMessage(aCameraControl, aListener)
+    { }
+
+    nsresult
+    RunImpl() MOZ_OVERRIDE
+    {
+      mCameraControl->RemoveListenerImpl(mListener);
+      return NS_OK;
+    }
+  };
+
+  mCameraThread->Dispatch(new Message(this, aListener), NS_DISPATCH_NORMAL);
+}
--- a/dom/camera/CameraControlImpl.h
+++ b/dom/camera/CameraControlImpl.h
@@ -1,750 +1,123 @@
 /* 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_CAMERACONTROLIMPL_H
 #define DOM_CAMERA_CAMERACONTROLIMPL_H
 
+#include "nsTArray.h"
+#include "nsWeakPtr.h"
 #include "mozilla/Attributes.h"
-#include "nsDOMFile.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "nsIFile.h"
 #include "nsProxyRelease.h"
-#include "DictionaryHelpers.h"
+#include "AutoRwLock.h"
 #include "nsIDOMDeviceStorage.h"
-#include "DOMCameraManager.h"
-#include "DOMCameraPreview.h"
 #include "ICameraControl.h"
 #include "CameraCommon.h"
 #include "DeviceStorage.h"
 #include "DeviceStorageFileDescriptor.h"
+#include "CameraControlListener.h"
 
 class DeviceStorageFileDescriptor;
 
 namespace mozilla {
 
-class GetPreviewStreamTask;
-class StartPreviewTask;
-class StopPreviewTask;
-class AutoFocusTask;
-class TakePictureTask;
-class StartRecordingTask;
-class StopRecordingTask;
-class SetParameterTask;
-class GetParameterTask;
-class GetPreviewStreamVideoModeTask;
-class ReleaseHardwareTask;
+namespace layers {
+  class Image;
+}
 
-class DOMCameraPreview;
 class RecorderProfileManager;
 
 class CameraControlImpl : public ICameraControl
 {
-  friend class GetPreviewStreamTask;
-  friend class StartPreviewTask;
-  friend class StopPreviewTask;
-  friend class AutoFocusTask;
-  friend class TakePictureTask;
-  friend class StartRecordingTask;
-  friend class StopRecordingTask;
-  friend class SetParameterTask;
-  friend class GetParameterTask;
-  friend class GetPreviewStreamVideoModeTask;
-  friend class ReleaseHardwareTask;
-
 public:
-  CameraControlImpl(uint32_t aCameraId, nsIThread* aCameraThread, uint64_t aWindowId);
-
-  nsresult GetPreviewStream(idl::CameraSize aSize, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError);
-  nsresult StartPreview(DOMCameraPreview* aDOMPreview);
-  void StopPreview();
-  nsresult AutoFocus(nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError);
-  nsresult TakePicture(const idl::CameraSize& aSize, int32_t aRotation, const nsAString& aFileFormat, idl::CameraPosition aPosition, uint64_t aDateTime, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError);
-  nsresult StartRecording(idl::CameraStartRecordingOptions* aOptions, DeviceStorageFileDescriptor *aDSFileDescriptor, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError);
-  nsresult StopRecording();
-  nsresult GetPreviewStreamVideoMode(idl::CameraRecorderOptions* aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError);
-  nsresult ReleaseHardware(nsICameraReleaseCallback* onSuccess, nsICameraErrorCallback* onError);
+  CameraControlImpl(uint32_t aCameraId);
+  virtual void AddListener(CameraControlListener* aListener) MOZ_OVERRIDE;
+  virtual void RemoveListener(CameraControlListener* aListener) MOZ_OVERRIDE;
 
-  nsresult Set(uint32_t aKey, const nsAString& aValue);
-  nsresult Get(uint32_t aKey, nsAString& aValue);
-  nsresult Set(uint32_t aKey, double aValue);
-  nsresult Get(uint32_t aKey, double* aValue);
-  nsresult Set(JSContext* aCx, uint32_t aKey, const JS::Value& aValue, uint32_t aLimit);
-  nsresult Get(JSContext* aCx, uint32_t aKey, JS::Value* aValue);
-  nsresult Set(nsICameraShutterCallback* aOnShutter);
-  nsresult Get(nsICameraShutterCallback** aOnShutter);
-  nsresult Set(nsICameraClosedCallback* aOnClosed);
-  nsresult Get(nsICameraClosedCallback** aOnClosed);
-  nsresult Set(nsICameraRecorderStateChange* aOnRecorderStateChange);
-  nsresult Get(nsICameraRecorderStateChange** aOnRecorderStateChange);
-  nsresult Set(nsICameraPreviewStateChange* aOnPreviewStateChange);
-  nsresult Get(nsICameraPreviewStateChange** aOnPreviewStateChange);
-  nsresult Set(uint32_t aKey, const idl::CameraSize& aSize);
-  nsresult Get(uint32_t aKey, idl::CameraSize& aSize);
-  nsresult Get(uint32_t aKey, int32_t* aValue);
-
-  nsresult SetFocusAreas(JSContext* aCx, const JS::Value& aValue)
-  {
-    return Set(aCx, CAMERA_PARAM_FOCUSAREAS, aValue, mMaxFocusAreas);
-  }
-
-  nsresult SetMeteringAreas(JSContext* aCx, const JS::Value& aValue)
-  {
-    return Set(aCx, CAMERA_PARAM_METERINGAREAS, aValue, mMaxMeteringAreas);
-  }
+  virtual nsresult SetConfiguration(const Configuration& aConfig) MOZ_OVERRIDE;
+  virtual nsresult StartPreview() MOZ_OVERRIDE;
+  virtual nsresult StopPreview() MOZ_OVERRIDE;
+  virtual nsresult AutoFocus(bool aCancelExistingCall) MOZ_OVERRIDE;
+  virtual nsresult TakePicture() MOZ_OVERRIDE;
+  virtual nsresult StartRecording(DeviceStorageFileDescriptor* aFileDescriptor,
+                                  const StartRecordingOptions* aOptions) MOZ_OVERRIDE;
+  virtual nsresult StopRecording() MOZ_OVERRIDE;
+  virtual nsresult ReleaseHardware() MOZ_OVERRIDE;
 
   already_AddRefed<RecorderProfileManager> GetRecorderProfileManager();
   uint32_t GetCameraId() { return mCameraId; }
 
-  virtual const char* GetParameter(const char* aKey) = 0;
-  virtual const char* GetParameterConstChar(uint32_t aKey) = 0;
-  virtual double GetParameterDouble(uint32_t aKey) = 0;
-  virtual int32_t GetParameterInt32(uint32_t aKey) = 0;
-  virtual void GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions) = 0;
-  virtual void GetParameter(uint32_t aKey, idl::CameraSize& aSize) = 0;
-  virtual void SetParameter(const char* aKey, const char* aValue) = 0;
-  virtual void SetParameter(uint32_t aKey, const char* aValue) = 0;
-  virtual void SetParameter(uint32_t aKey, double aValue) = 0;
-  virtual void SetParameter(uint32_t aKey, const nsTArray<idl::CameraRegion>& aRegions) = 0;
-  virtual void SetParameter(uint32_t aKey, const idl::CameraSize& aSize) = 0;
-  virtual nsresult GetVideoSizes(nsTArray<idl::CameraSize>& aVideoSizes) = 0;
-  virtual nsresult PushParameters() = 0;
-  virtual void Shutdown();
+  virtual void Shutdown() MOZ_OVERRIDE;
 
-  bool ReceiveFrame(void* aBuffer, ImageFormat aFormat, FrameBuilder aBuilder);
   void OnShutter();
   void OnClosed();
-  void OnRecorderStateChange(const nsString& aStateMsg, int32_t aStatus, int32_t aTrackNumber);
-
-  enum PreviewState {
-    PREVIEW_STOPPED,
-    PREVIEW_STARTED
-  };
-  void OnPreviewStateChange(PreviewState aNewState);
-
-  uint64_t GetWindowId()
-  {
-    return mWindowId;
-  }
+  void OnError(CameraControlListener::CameraErrorContext aContext,
+               CameraControlListener::CameraError aError);
 
 protected:
+  // Event handlers.
+  void OnAutoFocusComplete(bool aAutoFocusSucceeded);
+  void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType);
+
+  bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);
+  void OnRecorderStateChange(CameraControlListener::RecorderState aState,
+                             int32_t aStatus, int32_t aTrackNumber);
+  void OnPreviewStateChange(CameraControlListener::PreviewState aState);
+  void OnHardwareStateChange(CameraControlListener::HardwareState aState);
+  void OnConfigurationChange();
+
+  // When we create a new CameraThread, we keep a static reference to it so
+  // that multiple CameraControl instances can find and reuse it; but we
+  // don't want that reference to keep the thread object around unnecessarily,
+  // so we make it a weak reference. The strong dynamic references will keep
+  // the thread object alive as needed.
+  static nsWeakPtr sCameraThread;
+  nsCOMPtr<nsIThread> mCameraThread;
+
   virtual ~CameraControlImpl();
 
-  virtual nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream) = 0;
-  virtual nsresult StartPreviewImpl(StartPreviewTask* aStartPreview) = 0;
-  virtual nsresult StopPreviewImpl(StopPreviewTask* aStopPreview) = 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 void BeginBatchParameterSet() MOZ_OVERRIDE { }
+  virtual void EndBatchParameterSet() MOZ_OVERRIDE { }
+
+  // Manage camera event listeners.
+  void AddListenerImpl(already_AddRefed<CameraControlListener> aListener);
+  void RemoveListenerImpl(CameraControlListener* aListener);
+  nsTArray<nsRefPtr<CameraControlListener> > mListeners;
+  PRRWLock* mListenerLock;
+
+  class ControlMessage;
+  class ListenerMessage;
+
+  virtual nsresult SetConfigurationImpl(const Configuration& aConfig) = 0;
+  virtual nsresult StartPreviewImpl() = 0;
+  virtual nsresult StopPreviewImpl() = 0;
+  virtual nsresult AutoFocusImpl(bool aCancelExistingCall) = 0;
+  virtual nsresult TakePictureImpl() = 0;
+  virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
+                                      const StartRecordingOptions* aOptions) = 0;
+  virtual nsresult StopRecordingImpl() = 0;
   virtual nsresult PushParametersImpl() = 0;
   virtual nsresult PullParametersImpl() = 0;
-  virtual nsresult GetPreviewStreamVideoModeImpl(GetPreviewStreamVideoModeTask* aGetPreviewStreamVideoMode) = 0;
-  virtual nsresult ReleaseHardwareImpl(ReleaseHardwareTask* aReleaseHardware) = 0;
+  virtual nsresult ReleaseHardwareImpl() = 0;
   virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl() = 0;
 
   void OnShutterInternal();
   void OnClosedInternal();
 
-  uint32_t            mCameraId;
-  nsCOMPtr<nsIThread> mCameraThread;
-  uint64_t            mWindowId;
-  nsString            mFileFormat;
-  uint32_t            mMaxMeteringAreas;
-  uint32_t            mMaxFocusAreas;
-  PreviewState        mPreviewState;
+  uint32_t mCameraId;
 
-  /**
-   * 'mDOMPreview' is a raw pointer to the object that will receive incoming
-   * preview frames.  This is guaranteed to be valid, or null.
-   *
-   * It is set by a call to StartPreview(), and set to null on StopPreview().
-   * It is up to the caller to ensure that the object will not disappear
-   * out from under this pointer--usually by calling NS_ADDREF().
-   */
-  DOMCameraPreview*   mDOMPreview;
+  CameraControlListener::CameraListenerConfiguration mCurrentConfiguration;
 
-  nsMainThreadPtrHandle<nsICameraAutoFocusCallback>   mAutoFocusOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback>       mAutoFocusOnErrorCb;
-  nsMainThreadPtrHandle<nsICameraTakePictureCallback> mTakePictureOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback>       mTakePictureOnErrorCb;
-  nsMainThreadPtrHandle<nsICameraShutterCallback>     mOnShutterCb;
-  nsMainThreadPtrHandle<nsICameraClosedCallback>      mOnClosedCb;
-  nsMainThreadPtrHandle<nsICameraRecorderStateChange> mOnRecorderStateChangeCb;
-  nsMainThreadPtrHandle<nsICameraPreviewStateChange>  mOnPreviewStateChangeCb;
+  CameraControlListener::PreviewState   mPreviewState;
+  CameraControlListener::HardwareState  mHardwareState;
 
 private:
   CameraControlImpl(const CameraControlImpl&) MOZ_DELETE;
   CameraControlImpl& operator=(const CameraControlImpl&) MOZ_DELETE;
 };
 
-// Error result runnable
-class CameraErrorResult : public nsRunnable
-{
-public:
-  CameraErrorResult(nsMainThreadPtrHandle<nsICameraErrorCallback> onError, const nsString& aErrorMsg, uint64_t aWindowId)
-    : mOnErrorCb(onError)
-    , mErrorMsg(aErrorMsg)
-    , mWindowId(aWindowId)
-  { }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnErrorCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      mOnErrorCb->HandleEvent(mErrorMsg);
-    }
-    return NS_OK;
-  }
-
-protected:
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-  const nsString mErrorMsg;
-  uint64_t mWindowId;
-};
-
-// Return the resulting preview stream to JS.  Runs on the main thread.
-class GetPreviewStreamResult : public nsRunnable
-{
-public:
-  GetPreviewStreamResult(CameraControlImpl* aCameraControl, uint32_t aWidth, uint32_t aHeight, uint32_t aFramesPerSecond, nsMainThreadPtrHandle<nsICameraPreviewStreamCallback>& onSuccess, uint64_t aWindowId)
-    : mCameraControl(aCameraControl)
-    , mWidth(aWidth)
-    , mHeight(aHeight)
-    , mFramesPerSecond(aFramesPerSecond)
-    , mOnSuccessCb(onSuccess)
-    , mWindowId(aWindowId)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~GetPreviewStreamResult()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  // Run() method is implementation specific.
-  NS_IMETHOD Run() MOZ_OVERRIDE;
-
-protected:
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  uint32_t mWidth;
-  uint32_t mHeight;
-  uint32_t mFramesPerSecond;
-  nsMainThreadPtrHandle<nsICameraPreviewStreamCallback> mOnSuccessCb;
-  uint64_t mWindowId;
-};
-
-// Get the desired preview stream.
-class GetPreviewStreamTask : public nsRunnable
-{
-public:
-  GetPreviewStreamTask(CameraControlImpl* aCameraControl, idl::CameraSize aSize, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError)
-    : mSize(aSize)
-    , mCameraControl(aCameraControl)
-    , mOnSuccessCb(new nsMainThreadPtrHolder<nsICameraPreviewStreamCallback>(onSuccess))
-    , mOnErrorCb(new nsMainThreadPtrHolder<nsICameraErrorCallback>(onError))
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~GetPreviewStreamTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    nsresult rv = mCameraControl->GetPreviewStreamImpl(this);
-
-    if (NS_FAILED(rv)) {
-      nsCOMPtr<nsIRunnable> cameraErrorResult = new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId());
-      rv = NS_DispatchToMainThread(cameraErrorResult);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    return rv;
-  }
-
-  idl::CameraSize mSize;
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  nsMainThreadPtrHandle<nsICameraPreviewStreamCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-};
-
-// Return the autofocus status to JS.  Runs on the main thread.
-class AutoFocusResult : public nsRunnable
-{
-public:
-  AutoFocusResult(bool aSuccess, nsMainThreadPtrHandle<nsICameraAutoFocusCallback> onSuccess, uint64_t aWindowId)
-    : mSuccess(aSuccess)
-    , mOnSuccessCb(onSuccess)
-    , mWindowId(aWindowId)
-  { }
-
-  virtual ~AutoFocusResult() { }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnSuccessCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      mOnSuccessCb->HandleEvent(mSuccess);
-    }
-    return NS_OK;
-  }
-
-protected:
-  bool mSuccess;
-  nsMainThreadPtrHandle<nsICameraAutoFocusCallback> mOnSuccessCb;
-  uint64_t mWindowId;
-};
-
-// Autofocus the camera.
-class AutoFocusTask : public nsRunnable
-{
-public:
-  AutoFocusTask(CameraControlImpl* aCameraControl, bool aCancel, nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError)
-    : mCameraControl(aCameraControl)
-    , mCancel(aCancel)
-    , mOnSuccessCb(new nsMainThreadPtrHolder<nsICameraAutoFocusCallback>(onSuccess))
-    , mOnErrorCb(new nsMainThreadPtrHolder<nsICameraErrorCallback>(onError))
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~AutoFocusTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-    nsresult rv = mCameraControl->AutoFocusImpl(this);
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-
-    if (NS_FAILED(rv)) {
-      nsCOMPtr<nsIRunnable> cameraErrorResult = new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId());
-      rv = NS_DispatchToMainThread(cameraErrorResult);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    return rv;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  bool mCancel;
-  nsMainThreadPtrHandle<nsICameraAutoFocusCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-};
-
-// Return the captured picture to JS.  Runs on the main thread.
-class TakePictureResult : public nsRunnable
-{
-public:
-  TakePictureResult(uint8_t* aData, uint64_t aLength, const nsAString& aMimeType, nsMainThreadPtrHandle<nsICameraTakePictureCallback> onSuccess, uint64_t aWindowId)
-    : mData(aData)
-    , mLength(aLength)
-    , mMimeType(aMimeType)
-    , mOnSuccessCb(onSuccess)
-    , mWindowId(aWindowId)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~TakePictureResult()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-    if (mOnSuccessCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      nsCOMPtr<nsIDOMBlob> image = new nsDOMMemoryFile(static_cast<void*>(mData), static_cast<uint64_t>(mLength), mMimeType);
-      mOnSuccessCb->HandleEvent(image);
-    } else {
-      delete[] mData;
-    }
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-    return NS_OK;
-  }
-
-protected:
-  uint8_t* mData;
-  uint64_t mLength;
-  nsString mMimeType;
-  nsMainThreadPtrHandle<nsICameraTakePictureCallback> mOnSuccessCb;
-  uint64_t mWindowId;
-};
-
-// Capture a still image with the camera.
-class TakePictureTask : public nsRunnable
-{
-public:
-  TakePictureTask(CameraControlImpl* aCameraControl, bool aCancel, const idl::CameraSize& aSize, int32_t aRotation, const nsAString& aFileFormat, idl::CameraPosition aPosition, uint64_t aDateTime, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError)
-    : mCameraControl(aCameraControl)
-    , mCancel(aCancel)
-    , mSize(aSize)
-    , mRotation(aRotation)
-    , mFileFormat(aFileFormat)
-    , mPosition(aPosition)
-    , mDateTime(aDateTime)
-    , mOnSuccessCb(new nsMainThreadPtrHolder<nsICameraTakePictureCallback>(onSuccess))
-    , mOnErrorCb(new nsMainThreadPtrHolder<nsICameraErrorCallback>(onError))
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~TakePictureTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-    nsresult rv = mCameraControl->TakePictureImpl(this);
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-
-    if (NS_FAILED(rv)) {
-      nsCOMPtr<nsIRunnable> cameraErrorResult = new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId());
-      rv = NS_DispatchToMainThread(cameraErrorResult);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    return rv;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  bool mCancel;
-  idl::CameraSize mSize;
-  int32_t mRotation;
-  nsString mFileFormat;
-  idl::CameraPosition mPosition;
-  uint64_t mDateTime;
-  nsMainThreadPtrHandle<nsICameraTakePictureCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-};
-
-// Return the result of starting recording.  Runs on the main thread.
-class StartRecordingResult : public nsRunnable
-{
-public:
-  StartRecordingResult(nsMainThreadPtrHandle<nsICameraStartRecordingCallback> onSuccess, uint64_t aWindowId)
-    : mOnSuccessCb(onSuccess)
-    , mWindowId(aWindowId)
-  { }
-
-  virtual ~StartRecordingResult() { }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnSuccessCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      mOnSuccessCb->HandleEvent();
-    }
-    return NS_OK;
-  }
-
-protected:
-  nsMainThreadPtrHandle<nsICameraStartRecordingCallback> mOnSuccessCb;
-  uint64_t mWindowId;
-};
-
-// Start video recording.
-class StartRecordingTask : public nsRunnable
-{
-public:
-  StartRecordingTask(CameraControlImpl* aCameraControl,
-                     idl::CameraStartRecordingOptions aOptions,
-                     DeviceStorageFileDescriptor *aDSFileDescriptor,
-                     nsICameraStartRecordingCallback* onSuccess,
-                     nsICameraErrorCallback* onError,
-                     uint64_t aWindowId)
-    : mCameraControl(aCameraControl)
-    , mOptions(aOptions)
-    , mDSFileDescriptor(aDSFileDescriptor)
-    , mOnSuccessCb(new nsMainThreadPtrHolder<nsICameraStartRecordingCallback>(onSuccess))
-    , mOnErrorCb(new nsMainThreadPtrHolder<nsICameraErrorCallback>(onError))
-    , mWindowId(aWindowId)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~StartRecordingTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-    nsresult rv = mCameraControl->StartRecordingImpl(this);
-    DOM_CAMERA_LOGT("%s:%d : result %d\n", __func__, __LINE__, rv);
-
-    // dispatch the callback
-    nsCOMPtr<nsIRunnable> startRecordingResult;
-    if (NS_SUCCEEDED(rv)) {
-      startRecordingResult = new StartRecordingResult(mOnSuccessCb, mWindowId);
-    } else {
-      startRecordingResult = new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mWindowId);
-    }
-    rv = NS_DispatchToMainThread(startRecordingResult);
-    if (NS_FAILED(rv)) {
-      DOM_CAMERA_LOGE("Failed to dispatch start recording result to main thread (%d)!", rv);
-    }
-    return rv;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  idl::CameraStartRecordingOptions mOptions;
-  nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
-  nsMainThreadPtrHandle<nsICameraStartRecordingCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-  uint64_t mWindowId;
-};
-
-// Stop video recording.
-class StopRecordingTask : public nsRunnable
-{
-public:
-  StopRecordingTask(CameraControlImpl* aCameraControl)
-    : mCameraControl(aCameraControl)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~StopRecordingTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-    nsresult rv = mCameraControl->StopRecordingImpl(this);
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-
-    NS_ENSURE_SUCCESS(rv, rv);
-    return NS_OK;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-};
-
-// Start the preview.
-class StartPreviewTask : public nsRunnable
-{
-public:
-  StartPreviewTask(CameraControlImpl* aCameraControl, DOMCameraPreview* aDOMPreview)
-    : mCameraControl(aCameraControl)
-    , mDOMPreview(aDOMPreview)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~StartPreviewTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-    nsresult rv = mCameraControl->StartPreviewImpl(this);
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-
-    NS_ENSURE_SUCCESS(rv, rv);
-    return NS_OK;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  DOMCameraPreview* mDOMPreview; // DOMCameraPreview NS_ADDREFs itself for us
-};
-
-// Stop the preview.
-class StopPreviewTask : public nsRunnable
-{
-public:
-  StopPreviewTask(CameraControlImpl* aCameraControl)
-    : mCameraControl(aCameraControl)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~StopPreviewTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-    mCameraControl->StopPreviewImpl(this);
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-
-    return NS_OK;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-};
-
-// Get the video mode preview stream.
-class GetPreviewStreamVideoModeTask : public nsRunnable
-{
-public:
-  GetPreviewStreamVideoModeTask(CameraControlImpl* aCameraControl, idl::CameraRecorderOptions aOptions,  nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError)
-    : mCameraControl(aCameraControl)
-    , mOptions(aOptions)
-    , mOnSuccessCb(new nsMainThreadPtrHolder<nsICameraPreviewStreamCallback>(onSuccess))
-    , mOnErrorCb(new nsMainThreadPtrHolder<nsICameraErrorCallback>(onError))
-  { }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGI("%s:%d -- BEFORE IMPL\n", __func__, __LINE__);
-    nsresult rv = mCameraControl->GetPreviewStreamVideoModeImpl(this);
-    DOM_CAMERA_LOGI("%s:%d -- AFTER IMPL : rv = %d\n", __func__, __LINE__, rv);
-
-    if (NS_FAILED(rv)) {
-      nsCOMPtr<nsIRunnable> cameraErrorResult = new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId());
-      rv = NS_DispatchToMainThread(cameraErrorResult);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    return NS_OK;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  idl::CameraRecorderOptions mOptions;
-  nsMainThreadPtrHandle<nsICameraPreviewStreamCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-};
-
-// Return the result of releasing the camera hardware.  Runs on the main thread.
-class ReleaseHardwareResult : public nsRunnable
-{
-public:
-  ReleaseHardwareResult(nsMainThreadPtrHandle<nsICameraReleaseCallback> onSuccess, uint64_t aWindowId)
-    : mOnSuccessCb(onSuccess)
-    , mWindowId(aWindowId)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~ReleaseHardwareResult()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnSuccessCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      mOnSuccessCb->HandleEvent();
-    }
-    return NS_OK;
-  }
-
-protected:
-  nsMainThreadPtrHandle<nsICameraReleaseCallback> mOnSuccessCb;
-  uint64_t mWindowId;
-};
-
-// Release the camera hardware.
-class ReleaseHardwareTask : public nsRunnable
-{
-public:
-  ReleaseHardwareTask(CameraControlImpl* aCameraControl, nsICameraReleaseCallback* onSuccess, nsICameraErrorCallback* onError)
-    : mCameraControl(aCameraControl)
-    , mOnSuccessCb(new nsMainThreadPtrHolder<nsICameraReleaseCallback>(onSuccess))
-    , mOnErrorCb(new nsMainThreadPtrHolder<nsICameraErrorCallback>(onError))
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  virtual ~ReleaseHardwareTask()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-    nsresult rv = mCameraControl->ReleaseHardwareImpl(this);
-    DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-
-    if (NS_FAILED(rv)) {
-      nsCOMPtr<nsIRunnable> cameraErrorResult = new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId());
-      rv = NS_DispatchToMainThread(cameraErrorResult);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    return rv;
-  }
-
-  nsRefPtr<CameraControlImpl> mCameraControl;
-  nsMainThreadPtrHandle<nsICameraReleaseCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-};
-
-// Report that the video recorder state has changed.
-class CameraRecorderStateChange : public nsRunnable
-{
-public:
-  CameraRecorderStateChange(nsMainThreadPtrHandle<nsICameraRecorderStateChange> onStateChange, const nsString& aStateMsg, int32_t aStatus, int32_t aTrackNumber, uint64_t aWindowId)
-    : mOnStateChangeCb(onStateChange)
-    , mStateMsg(aStateMsg)
-    , mStatus(aStatus)
-    , mTrackNumber(aTrackNumber)
-    , mWindowId(aWindowId)
-  { }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnStateChangeCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      // For now, just pass the state message and swallow mStatus and mTrackNumber
-      mOnStateChangeCb->HandleStateChange(mStateMsg);
-    }
-    return NS_OK;
-  }
-
-protected:
-  nsMainThreadPtrHandle<nsICameraRecorderStateChange> mOnStateChangeCb;
-  const nsString mStateMsg;
-  int32_t mStatus;
-  int32_t mTrackNumber;
-  uint64_t mWindowId;
-};
-
-// Report that the preview stream state has changed.
-class CameraPreviewStateChange : public nsRunnable
-{
-public:
-  CameraPreviewStateChange(nsMainThreadPtrHandle<nsICameraPreviewStateChange> onStateChange, const nsString& aStateMsg, uint64_t aWindowId)
-    : mOnStateChangeCb(onStateChange)
-    , mStateMsg(aStateMsg)
-    , mWindowId(aWindowId)
-  { }
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnStateChangeCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      mOnStateChangeCb->HandleStateChange(mStateMsg);
-    }
-    return NS_OK;
-  }
-
-protected:
-  nsMainThreadPtrHandle<nsICameraPreviewStateChange> mOnStateChangeCb;
-  const nsString mStateMsg;
-  uint64_t mWindowId;
-};
-
 } // namespace mozilla
 
 #endif // DOM_CAMERA_CAMERACONTROLIMPL_H
new file mode 100644
--- /dev/null
+++ b/dom/camera/CameraControlListener.h
@@ -0,0 +1,100 @@
+/* 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_CAMERACONTROLLISTENER_H
+#define DOM_CAMERA_CAMERACONTROLLISTENER_H
+
+#include <stdint.h>
+#include "ICameraControl.h"
+
+namespace mozilla {
+
+namespace layers {
+  class Image;
+}
+
+class CameraControlListener
+{
+public:
+  virtual ~CameraControlListener() { }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CameraControlListener);
+
+  enum HardwareState
+  {
+    kHardwareOpen,
+    kHardwareClosed
+  };
+  virtual void OnHardwareStateChange(HardwareState aState) { }
+
+  enum PreviewState
+  {
+    kPreviewStopped,
+    kPreviewPaused,
+    kPreviewStarted
+  };
+  virtual void OnPreviewStateChange(PreviewState aState) { }
+
+  enum RecorderState
+  {
+    kRecorderStopped,
+    kRecorderStarted,
+#ifdef MOZ_B2G_CAMERA
+    kFileSizeLimitReached,
+    kVideoLengthLimitReached,
+    kTrackCompleted,
+    kTrackFailed,
+    kMediaRecorderFailed,
+    kMediaServerFailed
+#endif
+  };
+  enum { kNoTrackNumber = -1 };
+  virtual void OnRecorderStateChange(RecorderState aState, int32_t aStatus, int32_t aTrackNum) { }
+
+  virtual void OnShutter() { }
+  virtual bool OnNewPreviewFrame(layers::Image* aFrame, uint32_t aWidth, uint32_t aHeight)
+  {
+    return false;
+  }
+
+  class CameraListenerConfiguration : public ICameraControl::Configuration
+  {
+  public:
+    uint32_t mMaxMeteringAreas;
+    uint32_t mMaxFocusAreas;
+  };
+  virtual void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration) { }
+
+  virtual void OnAutoFocusComplete(bool aAutoFocusSucceeded) { }
+  virtual void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType) { }
+
+  enum CameraErrorContext
+  {
+    kInGetCamera,
+    kInAutoFocus,
+    kInTakePicture,
+    kInStartRecording,
+    kInStopRecording,
+    kInSetConfiguration,
+    kInReleaseHardware,
+    kInStartPreview,
+    kInStopPreview,
+    kInUnspecified
+  };
+  enum CameraError
+  {
+    kErrorApiFailed,
+    kErrorInitFailed,
+    kErrorInvalidConfiguration,
+    kErrorServiceFailed,
+    kErrorSetPictureSizeFailed,
+    kErrorSetThumbnailSizeFailed,
+    kErrorUnknown
+  };
+  virtual void OnError(CameraErrorContext aContext, CameraError aError) { }
+};
+
+} // namespace mozilla
+
+#endif // DOM_CAMERA_CAMERACONTROLLISTENER_H
--- a/dom/camera/CameraRecorderProfiles.cpp
+++ b/dom/camera/CameraRecorderProfiles.cpp
@@ -1,14 +1,14 @@
 /* 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 "CameraRecorderProfiles.h"
 #include "jsapi.h"
-#include "CameraRecorderProfiles.h"
 #include "CameraCommon.h"
 
 using namespace mozilla;
 
 /**
  * Video profile implementation.
  */
 RecorderVideoProfile::RecorderVideoProfile(uint32_t aCameraId, uint32_t aQualityIndex)
--- a/dom/camera/CameraRecorderProfiles.h
+++ b/dom/camera/CameraRecorderProfiles.h
@@ -5,20 +5,18 @@
 #ifndef DOM_CAMERA_CAMERA_RECORDER_PROFILES_H
 #define DOM_CAMERA_CAMERA_RECORDER_PROFILES_H
 
 #include "nsISupportsImpl.h"
 #include "nsMimeTypes.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 #include "jsapi.h"
-#include "DictionaryHelpers.h"
 #include "CameraCommon.h"
 
-
 namespace mozilla {
 
 class CameraControlImpl;
 
 class RecorderVideoProfile
 {
 public:
   RecorderVideoProfile(uint32_t aCameraId, uint32_t aQualityIndex);
--- a/dom/camera/DOMCameraCapabilities.cpp
+++ b/dom/camera/DOMCameraCapabilities.cpp
@@ -1,424 +1,282 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+ * 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 <cstring>
-#include <cstdlib>
-#include "base/basictypes.h"
-#include "nsDOMClassInfo.h"
-#include "jsapi.h"
-#include "CameraRecorderProfiles.h"
-#include "DOMCameraControl.h"
 #include "DOMCameraCapabilities.h"
+#include "nsPIDOMWindow.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/CameraManagerBinding.h"
+#include "mozilla/dom/CameraCapabilitiesBinding.h"
 #include "CameraCommon.h"
+#include "ICameraControl.h"
+#include "CameraRecorderProfiles.h"
 
-using namespace mozilla;
-using namespace mozilla::dom;
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CameraCapabilities)
 
-DOMCI_DATA(CameraCapabilities, nsICameraCapabilities)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CameraCapabilities)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  tmp->mRecorderProfiles = JS::UndefinedValue();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CameraCapabilities)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
-NS_INTERFACE_MAP_BEGIN(DOMCameraCapabilities)
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CameraCapabilities)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mRecorderProfiles)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CameraCapabilities)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(CameraCapabilities)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CameraCapabilities)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
-  NS_INTERFACE_MAP_ENTRY(nsICameraCapabilities)
-  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraCapabilities)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_ADDREF(DOMCameraCapabilities)
-NS_IMPL_RELEASE(DOMCameraCapabilities)
+CameraCapabilities::CameraCapabilities(nsPIDOMWindow* aWindow)
+  : mRecorderProfiles(JS::UndefinedValue())
+  , mWindow(aWindow)
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  MOZ_COUNT_CTOR(CameraCapabilities);
+  mozilla::HoldJSObjects(this);
+  SetIsDOMBinding();
+}
 
-static nsresult
-ParseZoomRatioItemAndAdd(JSContext* aCx, JS::Handle<JSObject*> aArray,
-                         uint32_t aIndex, const char* aStart, char** aEnd)
+CameraCapabilities::~CameraCapabilities()
 {
-  if (!*aEnd) {
-    // make 'aEnd' follow the same semantics as strchr().
-    aEnd = nullptr;
-  }
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  mRecorderProfiles = JS::UndefinedValue();
+  mozilla::DropJSObjects(this);
+  MOZ_COUNT_DTOR(CameraCapabilities);
+}
 
-  /**
-   * The by-100 divisor is Gonk-specific.  For now, assume other platforms
-   * return actual fractional multipliers.
-   */
-  double d = strtod(aStart, aEnd);
-#if MOZ_WIDGET_GONK
-  d /= 100;
-#endif
+JSObject*
+CameraCapabilities::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return CameraCapabilitiesBinding::Wrap(aCx, aScope, this);
+}
 
-  if (!JS_SetElement(aCx, aArray, aIndex, d)) {
-    return NS_ERROR_FAILURE;
+#define LOG_IF_ERROR(rv, param)                               \
+  do {                                                        \
+    if (NS_FAILED(rv)) {                                      \
+      DOM_CAMERA_LOGW("Error %x trying to get " #param "\n",  \
+        (rv));                                                \
+    }                                                         \
+  } while(0)
+
+nsresult
+CameraCapabilities::TranslateToDictionary(ICameraControl* aCameraControl,
+                                          uint32_t aKey, nsTArray<CameraSize>& aSizes)
+{
+  nsresult rv;
+  nsTArray<ICameraControl::Size> sizes;
+
+  rv = aCameraControl->Get(aKey, sizes);
+  if (NS_FAILED(rv)) {
+    return rv;
   }
 
-  return NS_OK;
-}
-
-static nsresult
-ParseStringItemAndAdd(JSContext* aCx, JS::Handle<JSObject*> aArray,
-                      uint32_t aIndex, const char* aStart, char** aEnd)
-{
-  JS::Rooted<JSString*> s(aCx);
-
-  if (*aEnd) {
-    s = JS_NewStringCopyN(aCx, aStart, *aEnd - aStart);
-  } else {
-    s = JS_NewStringCopyZ(aCx, aStart);
-  }
-  if (!s) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (!JS_SetElement(aCx, aArray, aIndex, s)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return NS_OK;
-}
-
-static nsresult
-ParseDimensionItemAndAdd(JSContext* aCx, JS::Handle<JSObject*> aArray,
-                         uint32_t aIndex, const char* aStart, char** aEnd)
-{
-  char* x;
-
-  if (!*aEnd) {
-    // make 'aEnd' follow the same semantics as strchr().
-    aEnd = nullptr;
-  }
-
-  JS::Rooted<JS::Value> w(aCx, INT_TO_JSVAL(strtol(aStart, &x, 10)));
-  JS::Rooted<JS::Value> h(aCx, INT_TO_JSVAL(strtol(x + 1, aEnd, 10)));
-
-  JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::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;
-  }
-
-  if (!JS_SetElement(aCx, aArray, aIndex, o)) {
-    return NS_ERROR_FAILURE;
+  aSizes.Clear();
+  aSizes.SetCapacity(sizes.Length());
+  for (uint32_t i = 0; i < sizes.Length(); ++i) {
+    CameraSize* s = aSizes.AppendElement();
+    s->mWidth = sizes[i].width;
+    s->mHeight = sizes[i].height;
   }
 
   return NS_OK;
 }
 
 nsresult
-DOMCameraCapabilities::ParameterListToNewArray(JSContext* aCx,
-                                               JS::MutableHandle<JSObject*> aArray,
-                                               uint32_t aKey,
-                                               ParseItemAndAddFunc aParseItemAndAdd)
+CameraCapabilities::Populate(ICameraControl* aCameraControl)
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+  NS_ENSURE_TRUE(aCameraControl, NS_ERROR_INVALID_ARG);
+
+  nsresult rv;
+
+  rv = TranslateToDictionary(aCameraControl, CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, mPreviewSizes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PREVIEWSIZES);
+
+  rv = TranslateToDictionary(aCameraControl, CAMERA_PARAM_SUPPORTED_PICTURESIZES, mPictureSizes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PICTURESIZES);
+
+  rv = TranslateToDictionary(aCameraControl, CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES, mThumbnailSizes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES);
+
+  rv = TranslateToDictionary(aCameraControl, CAMERA_PARAM_SUPPORTED_VIDEOSIZES, mVideoSizes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_VIDEOSIZES);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_PICTUREFORMATS, mFileFormats);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_PICTUREFORMATS);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_WHITEBALANCES, mWhiteBalanceModes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_WHITEBALANCES);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_SCENEMODES, mSceneModes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_SCENEMODES);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_EFFECTS, mEffects);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_EFFECTS);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_FLASHMODES, mFlashModes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_FLASHMODES);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_FOCUSMODES, mFocusModes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_FOCUSMODES);
 
-  const char* value = mCamera->GetParameterConstChar(aKey);
-  if (!value) {
-    // in case we get nonsense data back
-    aArray.set(nullptr);
-    return NS_OK;
-  }
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_ZOOMRATIOS, mZoomRatios);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_ZOOMRATIOS);
+
+  int32_t areas;
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS, areas);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS);
+  mMaxFocusAreas = areas < 0 ? 0 : areas;
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS, areas);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS);
+  mMaxMeteringAreas = areas < 0 ? 0 : areas;
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION, mMinExposureCompensation);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION);
+
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION, mMaxExposureCompensation);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION);
 
-  aArray.set(JS_NewArrayObject(aCx, 0));
-  if (!aArray) {
-    return NS_ERROR_OUT_OF_MEMORY;
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP, mExposureCompensationStep);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP);
+
+  mRecorderProfileManager = aCameraControl->GetRecorderProfileManager();
+  if (!mRecorderProfileManager) {
+    DOM_CAMERA_LOGW("Unable to get recorder profile manager\n");
+  } else {
+    AutoJSContext js;
+
+    JS::Rooted<JSObject*> o(js);
+    nsresult rv = mRecorderProfileManager->GetJsObject(js, o.address());
+    if (NS_FAILED(rv)) {
+      DOM_CAMERA_LOGE("Failed to JS-objectify profile manager (%d)\n", rv);
+      return rv;
+    }
+
+    mRecorderProfiles = JS::ObjectValue(*o);
   }
 
-  const char* p = value;
-  uint32_t index = 0;
-  nsresult rv;
-  char* q;
-
-  while (p) {
-    /**
-     * In C's string.h, strchr() is declared as returning 'char*'; in C++'s
-     * cstring, it is declared as returning 'const char*', _except_ in MSVC,
-     * where the C version is declared to return const like the C++ version.
-     *
-     * Unfortunately, for both cases, strtod() and strtol() take a 'char**' as
-     * the end-of-conversion pointer, so we need to cast away strchr()'s
-     * const-ness here to make the MSVC build everything happy.
-     */
-    q = const_cast<char*>(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
-DOMCameraCapabilities::StringListToNewObject(JSContext* aCx,
-                                             JS::MutableHandle<JS::Value> aArray,
-                                             uint32_t aKey)
-{
-  JS::Rooted<JSObject*> array(aCx);
-
-  nsresult rv = ParameterListToNewArray(aCx, &array, aKey, ParseStringItemAndAdd);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aArray.setObjectOrNull(array);
-  return NS_OK;
-}
-
-nsresult
-DOMCameraCapabilities::DimensionListToNewObject(JSContext* aCx,
-                                                JS::MutableHandle<JS::Value> aArray,
-                                                uint32_t aKey)
-{
-  JS::Rooted<JSObject*> array(aCx);
-
-  nsresult rv = ParameterListToNewArray(aCx, &array, aKey, ParseDimensionItemAndAdd);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aArray.setObjectOrNull(array);
+  // For now, always return success, since the presence or absence of capabilities
+  // indicates whether or not they are supported.
   return NS_OK;
 }
 
-/* readonly attribute jsval previewSizes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetPreviewSizes(JSContext* cx,
-                                       JS::MutableHandle<JS::Value> aPreviewSizes)
+void
+CameraCapabilities::GetPreviewSizes(nsTArray<dom::CameraSize>& retval) const
 {
-  return DimensionListToNewObject(cx, aPreviewSizes, CAMERA_PARAM_SUPPORTED_PREVIEWSIZES);
-}
-
-/* readonly attribute jsval pictureSizes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetPictureSizes(JSContext* cx,
-                                       JS::MutableHandle<JS::Value> aPictureSizes)
-{
-  return DimensionListToNewObject(cx, aPictureSizes, CAMERA_PARAM_SUPPORTED_PICTURESIZES);
+  retval = mPreviewSizes;
 }
 
-/* readonly attribute jsval thumbnailSizes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetThumbnailSizes(JSContext* cx,
-                                         JS::MutableHandle<JS::Value> aThumbnailSizes)
+void
+CameraCapabilities::GetPictureSizes(nsTArray<dom::CameraSize>& retval) const
 {
-  return DimensionListToNewObject(cx, aThumbnailSizes, CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES);
+  retval = mPictureSizes;
 }
 
-/* readonly attribute jsval fileFormats; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetFileFormats(JSContext* cx,
-                                      JS::MutableHandle<JS::Value> aFileFormats)
+void
+CameraCapabilities::GetThumbnailSizes(nsTArray<dom::CameraSize>& retval) const
 {
-  return StringListToNewObject(cx, aFileFormats, CAMERA_PARAM_SUPPORTED_PICTUREFORMATS);
-}
-
-/* readonly attribute jsval whiteBalanceModes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetWhiteBalanceModes(JSContext* cx,
-                                            JS::MutableHandle<JS::Value> aWhiteBalanceModes)
-{
-  return StringListToNewObject(cx, aWhiteBalanceModes, CAMERA_PARAM_SUPPORTED_WHITEBALANCES);
-}
-
-/* readonly attribute jsval sceneModes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetSceneModes(JSContext* cx,
-                                     JS::MutableHandle<JS::Value> aSceneModes)
-{
-  return StringListToNewObject(cx, aSceneModes, CAMERA_PARAM_SUPPORTED_SCENEMODES);
+  retval = mThumbnailSizes;
 }
 
-/* readonly attribute jsval effects; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetEffects(JSContext* cx,
-                                  JS::MutableHandle<JS::Value> aEffects)
+void
+CameraCapabilities::GetVideoSizes(nsTArray<dom::CameraSize>& retval) const
 {
-  return StringListToNewObject(cx, aEffects, CAMERA_PARAM_SUPPORTED_EFFECTS);
+  retval = mVideoSizes;
 }
 
-/* readonly attribute jsval flashModes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetFlashModes(JSContext* cx,
-                                     JS::MutableHandle<JS::Value> aFlashModes)
+void
+CameraCapabilities::GetFileFormats(nsTArray<nsString>& retval) const
 {
-  return StringListToNewObject(cx, aFlashModes, CAMERA_PARAM_SUPPORTED_FLASHMODES);
-}
-
-/* readonly attribute jsval focusModes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetFocusModes(JSContext* cx,
-                                     JS::MutableHandle<JS::Value> aFocusModes)
-{
-  return StringListToNewObject(cx, aFocusModes, CAMERA_PARAM_SUPPORTED_FOCUSMODES);
+  retval = mFileFormats;
 }
 
-/* readonly attribute long maxFocusAreas; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetMaxFocusAreas(JSContext* cx, int32_t* aMaxFocusAreas)
+void
+CameraCapabilities::GetWhiteBalanceModes(nsTArray<nsString>& retval) const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
-
-  const char* value = mCamera->GetParameterConstChar(CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS);
-  if (!value) {
-    // in case we get nonsense data back
-    *aMaxFocusAreas = 0;
-    return NS_OK;
-  }
-
-  *aMaxFocusAreas = atoi(value);
-  return NS_OK;
+  retval = mWhiteBalanceModes;
 }
 
-/* readonly attribute double minExposureCompensation; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetMinExposureCompensation(JSContext* cx, double* aMinExposureCompensation)
+void
+CameraCapabilities::GetSceneModes(nsTArray<nsString>& retval) const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+  retval = mSceneModes;
+}
 
-  const char* value = mCamera->GetParameterConstChar(CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION);
-  if (!value) {
-    // in case we get nonsense data back
-    *aMinExposureCompensation = 0;
-    return NS_OK;
-  }
-
-  *aMinExposureCompensation = atof(value);
-  return NS_OK;
+void
+CameraCapabilities::GetEffects(nsTArray<nsString>& retval) const
+{
+  retval = mEffects;
 }
 
-/* readonly attribute double maxExposureCompensation; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetMaxExposureCompensation(JSContext* cx, double* aMaxExposureCompensation)
+void
+CameraCapabilities::GetFlashModes(nsTArray<nsString>& retval) const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
-
-  const char* value = mCamera->GetParameterConstChar(CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION);
-  if (!value) {
-    // in case we get nonsense data back
-    *aMaxExposureCompensation = 0;
-    return NS_OK;
-  }
-
-  *aMaxExposureCompensation = atof(value);
-  return NS_OK;
+  retval = mFlashModes;
 }
 
-/* readonly attribute double stepExposureCompensation; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetStepExposureCompensation(JSContext* cx, double* aStepExposureCompensation)
+void
+CameraCapabilities::GetFocusModes(nsTArray<nsString>& retval) const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
-
-  const char* value = mCamera->GetParameterConstChar(CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP);
-  if (!value) {
-    // in case we get nonsense data back
-    *aStepExposureCompensation = 0;
-    return NS_OK;
-  }
-
-  *aStepExposureCompensation = atof(value);
-  return NS_OK;
+  retval = mFocusModes;
 }
 
-/* readonly attribute long maxMeteringAreas; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetMaxMeteringAreas(JSContext* cx, int32_t* aMaxMeteringAreas)
+void
+CameraCapabilities::GetZoomRatios(nsTArray<double>& retval) const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+  retval = mZoomRatios;
+}
 
-  const char* value = mCamera->GetParameterConstChar(CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS);
-  if (!value) {
-    // in case we get nonsense data back
-    *aMaxMeteringAreas = 0;
-    return NS_OK;
-  }
-
-  *aMaxMeteringAreas = atoi(value);
-  return NS_OK;
+uint32_t
+CameraCapabilities::MaxFocusAreas() const
+{
+  return mMaxFocusAreas;
 }
 
-/* readonly attribute jsval zoomRatios; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetZoomRatios(JSContext* cx, JS::MutableHandle<JS::Value> aZoomRatios)
+uint32_t
+CameraCapabilities::MaxMeteringAreas() const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+  return mMaxMeteringAreas;
+}
 
-  const char* value = mCamera->GetParameterConstChar(CAMERA_PARAM_SUPPORTED_ZOOM);
-  if (!value || strcmp(value, "true") != 0) {
-    // if zoom is not supported, return a null object
-    aZoomRatios.setNull();
-    return NS_OK;
-  }
-
-  JS::Rooted<JSObject*> array(cx);
-
-  nsresult rv = ParameterListToNewArray(cx, &array, CAMERA_PARAM_SUPPORTED_ZOOMRATIOS, ParseZoomRatioItemAndAdd);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aZoomRatios.setObjectOrNull(array);
-  return NS_OK;
+double
+CameraCapabilities::MinExposureCompensation() const
+{
+  return mMinExposureCompensation;
 }
 
-/* readonly attribute jsval videoSizes; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetVideoSizes(JSContext* cx, JS::MutableHandle<JS::Value> aVideoSizes)
+double
+CameraCapabilities::MaxExposureCompensation() const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
-
-  nsTArray<mozilla::idl::CameraSize> sizes;
-  nsresult rv = mCamera->GetVideoSizes(sizes);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (sizes.Length() == 0) {
-    // video recording not supported, return null
-    aVideoSizes.setNull();
-    return NS_OK;
-  }
-
-  JS::Rooted<JSObject*> array(cx, JS_NewArrayObject(cx, 0));
-  if (!array) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  for (uint32_t i = 0; i < sizes.Length(); ++i) {
-    JS::Rooted<JSObject*> o(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
-    JS::Rooted<JS::Value> v(cx, INT_TO_JSVAL(sizes[i].width));
-    if (!JS_SetProperty(cx, o, "width", v)) {
-      return NS_ERROR_FAILURE;
-    }
-    v = INT_TO_JSVAL(sizes[i].height);
-    if (!JS_SetProperty(cx, o, "height", v)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    if (!JS_SetElement(cx, array, i, o)) {
-      return NS_ERROR_FAILURE;
-    }
-  }
-
-  aVideoSizes.setObject(*array);
-  return NS_OK;
+  return mMaxExposureCompensation;
 }
 
-/* readonly attribute jsval recorderProfiles; */
-NS_IMETHODIMP
-DOMCameraCapabilities::GetRecorderProfiles(JSContext* cx, JS::MutableHandle<JS::Value> aRecorderProfiles)
+double
+CameraCapabilities::ExposureCompensationStep() const
 {
-  NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE);
+  return mExposureCompensationStep;
+}
 
-  nsRefPtr<RecorderProfileManager> profileMgr = mCamera->GetRecorderProfileManager();
-  if (!profileMgr) {
-    aRecorderProfiles.setNull();
-    return NS_OK;
-  }
+JS::Value
+CameraCapabilities::RecorderProfiles(JSContext* aCx) const
+{
+  return mRecorderProfiles;
+}
 
-  JS::Rooted<JSObject*> o(cx);
-  nsresult rv = profileMgr->GetJsObject(cx, o.address());
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aRecorderProfiles.setObject(*o);
-  return NS_OK;
-}
+} // namespace dom
+} // namespace mozilla
--- a/dom/camera/DOMCameraCapabilities.h
+++ b/dom/camera/DOMCameraCapabilities.h
@@ -1,59 +1,96 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+ * 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 mozilla_dom_CameraCapabilities_h__
+#define mozilla_dom_CameraCapabilities_h__
 
-#ifndef DOM_CAMERA_DOMCAMERACAPABILITIES_H
-#define DOM_CAMERA_DOMCAMERACAPABILITIES_H
+#include "nsString.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/CameraManagerBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
 
-#include "ICameraControl.h"
-#include "nsAutoPtr.h"
-#include "CameraCommon.h"
+struct JSContext;
+class nsPIDOMWindow;
 
 namespace mozilla {
 
-typedef nsresult (*ParseItemAndAddFunc)(JSContext* aCx, JS::Handle<JSObject*> aArray,
-                                        uint32_t aIndex, const char* aStart, char** aEnd);
+class ICameraControl;
+class RecorderProfileManager;
 
-class DOMCameraCapabilities MOZ_FINAL : public nsICameraCapabilities
+namespace dom {
+
+class CameraCapabilities MOZ_FINAL : public nsISupports
+                                   , public nsWrapperCache
 {
 public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSICAMERACAPABILITIES
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CameraCapabilities)
+
+  CameraCapabilities(nsPIDOMWindow* aWindow);
+  ~CameraCapabilities();
 
-  DOMCameraCapabilities(ICameraControl* aCamera)
-    : mCamera(aCamera)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
+  nsresult Populate(ICameraControl* aCameraControl);
+
+  nsPIDOMWindow* GetParentObject() const { return mWindow; }
+
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
-  nsresult ParameterListToNewArray(
-    JSContext* cx,
-    JS::MutableHandle<JSObject*> aArray,
-    uint32_t aKey,
-    ParseItemAndAddFunc aParseItemAndAdd
-  );
-  nsresult StringListToNewObject(JSContext* aCx,
-                                 JS::MutableHandle<JS::Value> aArray,
-                                 uint32_t aKey);
-  nsresult DimensionListToNewObject(JSContext* aCx,
-                                    JS::MutableHandle<JS::Value> aArray,
-                                    uint32_t aKey);
-
-private:
-  DOMCameraCapabilities(const DOMCameraCapabilities&) MOZ_DELETE;
-  DOMCameraCapabilities& operator=(const DOMCameraCapabilities&) MOZ_DELETE;
+  void GetPreviewSizes(nsTArray<CameraSize>& aRetVal) const;
+  void GetPictureSizes(nsTArray<CameraSize>& aRetVal) const;
+  void GetThumbnailSizes(nsTArray<CameraSize>& aRetVal) const;
+  void GetVideoSizes(nsTArray<CameraSize>& aRetVal) const;
+  void GetFileFormats(nsTArray<nsString>& aRetVal) const;
+  void GetWhiteBalanceModes(nsTArray<nsString>& aRetVal) const;
+  void GetSceneModes(nsTArray<nsString>& aRetVal) const;
+  void GetEffects(nsTArray<nsString>& aRetVal) const;
+  void GetFlashModes(nsTArray<nsString>& aRetVal) const;
+  void GetFocusModes(nsTArray<nsString>& aRetVal) const;
+  void GetZoomRatios(nsTArray<double>& aRetVal) const;
+  uint32_t MaxFocusAreas() const;
+  uint32_t MaxMeteringAreas() const;
+  double MinExposureCompensation() const;
+  double MaxExposureCompensation() const;
+  double ExposureCompensationStep() const;
+  JS::Value RecorderProfiles(JSContext* cx) const;
 
 protected:
-  /* additional members */
-  ~DOMCameraCapabilities()
-  {
-    // destructor code
-    DOM_CAMERA_LOGT("%s:%d : this=%p, mCamera=%p\n", __func__, __LINE__, this, mCamera.get());
-  }
+  nsresult TranslateToDictionary(ICameraControl* aCameraControl,
+                                 uint32_t aKey, nsTArray<CameraSize>& aSizes);
+
+  nsTArray<CameraSize> mPreviewSizes;
+  nsTArray<CameraSize> mPictureSizes;
+  nsTArray<CameraSize> mThumbnailSizes;
+  nsTArray<CameraSize> mVideoSizes;
 
-  nsRefPtr<ICameraControl> mCamera;
+  nsTArray<nsString> mFileFormats;
+  nsTArray<nsString> mWhiteBalanceModes;
+  nsTArray<nsString> mSceneModes;
+  nsTArray<nsString> mEffects;
+  nsTArray<nsString> mFlashModes;
+  nsTArray<nsString> mFocusModes;
+
+  nsTArray<double> mZoomRatios;
+
+  uint32_t mMaxFocusAreas;
+  uint32_t mMaxMeteringAreas;
+
+  double mMinExposureCompensation;
+  double mMaxExposureCompensation;
+  double mExposureCompensationStep;
+
+  nsRefPtr<RecorderProfileManager> mRecorderProfileManager;
+  JS::Heap<JS::Value> mRecorderProfiles;
+
+  nsRefPtr<nsPIDOMWindow> mWindow;
 };
 
+} // namespace dom
 } // namespace mozilla
 
-#endif // DOM_CAMERA_DOMCAMERACAPABILITIES_H
+#endif // mozilla_dom_CameraCapabilities_h__
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -1,170 +1,478 @@
 /* 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 "DOMCameraControl.h"
 #include "base/basictypes.h"
 #include "nsCOMPtr.h"
 #include "nsDOMClassInfo.h"
 #include "nsHashPropertyBag.h"
 #include "nsThread.h"
 #include "DeviceStorage.h"
 #include "DeviceStorageFileDescriptor.h"
-#include "mozilla/dom/CameraControlBinding.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/MediaManager.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "nsIAppsService.h"
 #include "nsIObserverService.h"
 #include "nsIDOMDeviceStorage.h"
+#include "nsIDOMEventListener.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsXULAppAPI.h"
 #include "DOMCameraManager.h"
 #include "DOMCameraCapabilities.h"
-#include "DOMCameraControl.h"
 #include "CameraCommon.h"
+#include "DictionaryHelpers.h"
+#include "nsGlobalWindow.h"
+#include "CameraPreviewMediaStream.h"
+#include "mozilla/dom/CameraControlBinding.h"
 #include "mozilla/dom/CameraManagerBinding.h"
+#include "mozilla/dom/CameraCapabilitiesBinding.h"
 #include "mozilla/dom/BindingUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::idl;
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_2(nsDOMCameraControl, mDOMCapabilities, mWindow)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCameraControl)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMCameraControl)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
+  NS_INTERFACE_MAP_ENTRY(nsIDOMMediaStream)
+NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
+
+NS_IMPL_ADDREF_INHERITED(nsDOMCameraControl, DOMMediaStream)
+NS_IMPL_RELEASE_INHERITED(nsDOMCameraControl, DOMMediaStream)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMCameraControl)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMCameraControl, DOMMediaStream)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCapabilities)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mGetCameraOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mGetCameraOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAutoFocusOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAutoFocusOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTakePictureOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTakePictureOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartRecordingOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartRecordingOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mReleaseOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mReleaseOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetConfigurationOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetConfigurationOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnShutterCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnClosedCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnRecorderStateChangeCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnPreviewStateChangeCb)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMCameraControl, DOMMediaStream)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCapabilities)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGetCameraOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGetCameraOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAutoFocusOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAutoFocusOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTakePictureOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTakePictureOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartRecordingOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartRecordingOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReleaseOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReleaseOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSetConfigurationOnSuccessCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSetConfigurationOnErrorCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnShutterCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnClosedCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnRecorderStateChangeCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnPreviewStateChangeCb)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDOMCameraControl)
+
+class mozilla::StartRecordingHelper : public nsIDOMEventListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMEVENTLISTENER
+
+  StartRecordingHelper(nsDOMCameraControl* aDOMCameraControl)
+    : mDOMCameraControl(aDOMCameraControl)
+  {
+    MOZ_COUNT_CTOR(StartRecordingHelper);
+  }
+
+protected:
+  virtual ~StartRecordingHelper()
+  {
+    MOZ_COUNT_DTOR(StartRecordingHelper);
+  }
+
+protected:
+  nsRefPtr<nsDOMCameraControl> mDOMCameraControl;
+};
+
+NS_IMETHODIMP
+StartRecordingHelper::HandleEvent(nsIDOMEvent* aEvent)
+{
+  nsString eventType;
+  aEvent->GetType(eventType);
+
+  mDOMCameraControl->OnCreatedFileDescriptor(eventType.EqualsLiteral("success"));
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS0(mozilla::StartRecordingHelper)
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCameraControl)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCameraControl)
+nsDOMCameraControl::DOMCameraConfiguration::DOMCameraConfiguration()
+  : CameraConfiguration()
+  , mMaxFocusAreas(0)
+  , mMaxMeteringAreas(0)
+{
+  MOZ_COUNT_CTOR(nsDOMCameraControl::DOMCameraConfiguration);
+}
+
+nsDOMCameraControl::DOMCameraConfiguration::DOMCameraConfiguration(const CameraConfiguration& aConfiguration)
+  : CameraConfiguration(aConfiguration)
+  , mMaxFocusAreas(0)
+  , mMaxMeteringAreas(0)
+{
+  MOZ_COUNT_CTOR(nsDOMCameraControl::DOMCameraConfiguration);
+}
+
+nsDOMCameraControl::DOMCameraConfiguration::~DOMCameraConfiguration()
+{
+  MOZ_COUNT_DTOR(nsDOMCameraControl::DOMCameraConfiguration);
+}
+
+nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
+                                       const CameraConfiguration& aInitialConfig,
+                                       GetCameraCallback* aOnSuccess,
+                                       CameraErrorCallback* aOnError,
+                                       nsPIDOMWindow* aWindow)
+  : DOMMediaStream()
+  , mCameraControl(nullptr)
+  , mAudioChannelAgent(nullptr)
+  , mGetCameraOnSuccessCb(aOnSuccess)
+  , mGetCameraOnErrorCb(aOnError)
+  , mAutoFocusOnSuccessCb(nullptr)
+  , mAutoFocusOnErrorCb(nullptr)
+  , mTakePictureOnSuccessCb(nullptr)
+  , mTakePictureOnErrorCb(nullptr)
+  , mStartRecordingOnSuccessCb(nullptr)
+  , mStartRecordingOnErrorCb(nullptr)
+  , mReleaseOnSuccessCb(nullptr)
+  , mReleaseOnErrorCb(nullptr)
+  , mSetConfigurationOnSuccessCb(nullptr)
+  , mSetConfigurationOnErrorCb(nullptr)
+  , mOnShutterCb(nullptr)
+  , mOnClosedCb(nullptr)
+  , mOnRecorderStateChangeCb(nullptr)
+  , mOnPreviewStateChangeCb(nullptr)
+  , mWindow(aWindow)
+{
+  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  mInput = new CameraPreviewMediaStream(this);
+
+  SetIsDOMBinding();
+
+  nsRefPtr<DOMCameraConfiguration> initialConfig =
+    new DOMCameraConfiguration(aInitialConfig);
+
+  // Create and initialize the underlying camera.
+  ICameraControl::Configuration config;
+
+  switch (aInitialConfig.mMode) {
+    case CameraMode::Picture:
+      config.mMode = ICameraControl::kPictureMode;
+      break;
+
+    case CameraMode::Video:
+      config.mMode = ICameraControl::kVideoMode;
+      break;
+
+    default:
+      MOZ_ASSUME_UNREACHABLE("Unanticipated camera mode!");
+  }
+
+  config.mPreviewSize.width = aInitialConfig.mPreviewSize.mWidth;
+  config.mPreviewSize.height = aInitialConfig.mPreviewSize.mHeight;
+  config.mRecorderProfile = aInitialConfig.mRecorderProfile;
+
+  mCameraControl = ICameraControl::Create(aCameraId, &config);
+  mCurrentConfiguration = initialConfig.forget();
+
+  // Attach our DOM-facing media stream to our viewfinder stream.
+  mStream = mInput;
+  MOZ_ASSERT(mWindow, "Shouldn't be created with a null window!");
+  if (mWindow->GetExtantDoc()) {
+    CombineWithPrincipal(mWindow->GetExtantDoc()->NodePrincipal());
+  }
+
+  // Register a listener for camera events.
+  mListener = new DOMCameraControlListener(this, mInput);
+  mCameraControl->AddListener(mListener);
+}
 
 nsDOMCameraControl::~nsDOMCameraControl()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
 }
 
 JSObject*
 nsDOMCameraControl::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return CameraControlBinding::Wrap(aCx, aScope, this);
 }
 
-nsICameraCapabilities*
-nsDOMCameraControl::Capabilities()
+bool
+nsDOMCameraControl::IsWindowStillActive()
+{
+  return nsDOMCameraManager::IsWindowStillActive(mWindow->WindowID());
+}
+
+// JS-to-native helpers
+// Setter for weighted regions: { top, bottom, left, right, weight }
+nsresult
+nsDOMCameraControl::Set(JSContext* aCx, uint32_t aKey, const JS::Value& aValue, uint32_t aLimit)
 {
-  if (!mDOMCapabilities) {
-    mDOMCapabilities = new DOMCameraCapabilities(mCameraControl);
+  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;
+
+  JS::Rooted<JSObject*> regions(aCx, &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;
   }
 
-  return mDOMCapabilities;
+  nsTArray<ICameraControl::Region> regionArray;
+  regionArray.SetCapacity(length);
+
+  for (uint32_t i = 0; i < length; ++i) {
+    JS::Rooted<JS::Value> v(aCx);
+
+    if (!JS_GetElement(aCx, regions, i, &v)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    CameraRegion region;
+    if (!region.Init(aCx, v)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    ICameraControl::Region* r = regionArray.AppendElement();
+    r->top = region.mTop;
+    r->left = region.mLeft;
+    r->bottom = region.mBottom;
+    r->right = region.mRight;
+    r->weight = region.mWeight;
+
+    DOM_CAMERA_LOGI("region %d: top=%d, left=%d, bottom=%d, right=%d, weight=%u\n",
+      i,
+      r->top,
+      r->left,
+      r->bottom,
+      r->right,
+      r->weight
+    );
+  }
+  return mCameraControl->Set(aKey, regionArray);
+}
+
+// Getter for weighted regions: { top, bottom, left, right, weight }
+nsresult
+nsDOMCameraControl::Get(JSContext* aCx, uint32_t aKey, JS::Value* aValue)
+{
+  nsTArray<ICameraControl::Region> regionArray;
+
+  nsresult rv = mCameraControl->Get(aKey, regionArray);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, 0));
+  if (!array) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  uint32_t length = regionArray.Length();
+  DOM_CAMERA_LOGI("%s:%d : got %d regions\n", __func__, __LINE__, length);
+
+  for (uint32_t i = 0; i < length; ++i) {
+    ICameraControl::Region* r = &regionArray[i];
+    JS::Rooted<JS::Value> v(aCx);
+
+    JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::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;
+    }
+
+    if (!JS_SetElement(aCx, array, i, o)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  *aValue = JS::ObjectValue(*array);
+  return NS_OK;
 }
 
 void
 nsDOMCameraControl::GetEffect(nsString& aEffect, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Get(CAMERA_PARAM_EFFECT, aEffect);
 }
 void
 nsDOMCameraControl::SetEffect(const nsAString& aEffect, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Set(CAMERA_PARAM_EFFECT, aEffect);
 }
 
 void
 nsDOMCameraControl::GetWhiteBalanceMode(nsString& aWhiteBalanceMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Get(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode);
 }
 void
 nsDOMCameraControl::SetWhiteBalanceMode(const nsAString& aWhiteBalanceMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Set(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode);
 }
 
 void
 nsDOMCameraControl::GetSceneMode(nsString& aSceneMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Get(CAMERA_PARAM_SCENEMODE, aSceneMode);
 }
 void
 nsDOMCameraControl::SetSceneMode(const nsAString& aSceneMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Set(CAMERA_PARAM_SCENEMODE, aSceneMode);
 }
 
 void
 nsDOMCameraControl::GetFlashMode(nsString& aFlashMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Get(CAMERA_PARAM_FLASHMODE, aFlashMode);
 }
 void
 nsDOMCameraControl::SetFlashMode(const nsAString& aFlashMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Set(CAMERA_PARAM_FLASHMODE, aFlashMode);
 }
 
 void
 nsDOMCameraControl::GetFocusMode(nsString& aFocusMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSMODE, aFocusMode);
 }
 void
 nsDOMCameraControl::SetFocusMode(const nsAString& aFocusMode, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Set(CAMERA_PARAM_FOCUSMODE, aFocusMode);
 }
 
 double
 nsDOMCameraControl::GetZoom(ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
+
   double zoom;
-  aRv = mCameraControl->Get(CAMERA_PARAM_ZOOM, &zoom);
+  aRv = mCameraControl->Get(CAMERA_PARAM_ZOOM, zoom);
   return zoom;
 }
 
 void
 nsDOMCameraControl::SetZoom(double aZoom, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Set(CAMERA_PARAM_ZOOM, aZoom);
 }
 
 /* attribute jsval meteringAreas; */
 JS::Value
 nsDOMCameraControl::GetMeteringAreas(JSContext* cx, ErrorResult& aRv)
 {
   JS::Rooted<JS::Value> areas(cx);
-  aRv = mCameraControl->Get(cx, CAMERA_PARAM_METERINGAREAS, areas.address());
+  aRv = Get(cx, CAMERA_PARAM_METERINGAREAS, areas.address());
   return areas;
 }
 
 void
 nsDOMCameraControl::SetMeteringAreas(JSContext* cx, JS::Handle<JS::Value> aMeteringAreas, ErrorResult& aRv)
 {
-  aRv = mCameraControl->SetMeteringAreas(cx, aMeteringAreas);
+  aRv = Set(cx, CAMERA_PARAM_METERINGAREAS, aMeteringAreas,
+            mCurrentConfiguration->mMaxMeteringAreas);
 }
 
 JS::Value
 nsDOMCameraControl::GetFocusAreas(JSContext* cx, ErrorResult& aRv)
 {
   JS::Rooted<JS::Value> value(cx);
-  aRv = mCameraControl->Get(cx, CAMERA_PARAM_FOCUSAREAS, value.address());
+  aRv = Get(cx, CAMERA_PARAM_FOCUSAREAS, value.address());
   return value;
 }
 void
 nsDOMCameraControl::SetFocusAreas(JSContext* cx, JS::Handle<JS::Value> aFocusAreas, ErrorResult& aRv)
 {
-  aRv = mCameraControl->SetFocusAreas(cx, aFocusAreas);
+  aRv = Set(cx, CAMERA_PARAM_FOCUSAREAS, aFocusAreas,
+            mCurrentConfiguration->mMaxFocusAreas);
 }
 
 static nsresult
-GetSize(JSContext* aCx, JS::Value* aValue, const CameraSize& aSize)
+GetSize(JSContext* aCx, JS::Value* aValue, const ICameraControl::Size& aSize)
 {
   JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(), JS::NullPtr()));
   if (!o) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   JS::Rooted<JS::Value> v(aCx);
 
@@ -181,448 +489,464 @@ GetSize(JSContext* aCx, JS::Value* aValu
   return NS_OK;
 }
 
 /* attribute any pictureSize */
 JS::Value
 nsDOMCameraControl::GetPictureSize(JSContext* cx, ErrorResult& aRv)
 {
   JS::Rooted<JS::Value> value(cx);
-  
-  CameraSize size;
+
+  ICameraControl::Size size;
   aRv = mCameraControl->Get(CAMERA_PARAM_PICTURESIZE, size);
   if (aRv.Failed()) {
     return value;
   }
 
   aRv = GetSize(cx, value.address(), size);
   return value;
 }
 void
-nsDOMCameraControl::SetPictureSize(JSContext* cx, JS::Handle<JS::Value> aSize, ErrorResult& aRv)
+nsDOMCameraControl::SetPictureSize(JSContext* aCx, JS::Handle<JS::Value> aSize, ErrorResult& aRv)
 {
   CameraSize size;
-  aRv = size.Init(cx, aSize.address());
-  if (aRv.Failed()) {
+  if (!size.Init(aCx, aSize)) {
+    aRv = NS_ERROR_FAILURE;
     return;
   }
 
-  aRv = mCameraControl->Set(CAMERA_PARAM_PICTURESIZE, size);
+  ICameraControl::Size s = { size.mWidth, size.mHeight };
+  aRv = mCameraControl->Set(CAMERA_PARAM_PICTURESIZE, s);
 }
 
 /* attribute any thumbnailSize */
 JS::Value
-nsDOMCameraControl::GetThumbnailSize(JSContext* cx, ErrorResult& aRv)
+nsDOMCameraControl::GetThumbnailSize(JSContext* aCx, ErrorResult& aRv)
 {
-  JS::Rooted<JS::Value> value(cx);
-  
-  CameraSize size;
+  JS::Rooted<JS::Value> value(aCx);
+
+  ICameraControl::Size size;
   aRv = mCameraControl->Get(CAMERA_PARAM_THUMBNAILSIZE, size);
   if (aRv.Failed()) {
     return value;
   }
 
-  aRv = GetSize(cx, value.address(), size);
+  aRv = GetSize(aCx, value.address(), size);
   return value;
 }
 void
-nsDOMCameraControl::SetThumbnailSize(JSContext* cx, JS::Handle<JS::Value> aSize, ErrorResult& aRv)
+nsDOMCameraControl::SetThumbnailSize(JSContext* aCx, JS::Handle<JS::Value> aSize, ErrorResult& aRv)
 {
   CameraSize size;
-  aRv = size.Init(cx, aSize.address());
-  if (aRv.Failed()) {
+  if (!size.Init(aCx, aSize)) {
+    aRv = NS_ERROR_FAILURE;
     return;
   }
 
-  aRv = mCameraControl->Set(CAMERA_PARAM_THUMBNAILSIZE, size);
+  ICameraControl::Size s = { size.mWidth, size.mHeight };
+  aRv = mCameraControl->Set(CAMERA_PARAM_THUMBNAILSIZE, s);
 }
 
 double
 nsDOMCameraControl::GetFocalLength(ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
+
   double focalLength;
-  aRv = mCameraControl->Get(CAMERA_PARAM_FOCALLENGTH, &focalLength);
+  aRv = mCameraControl->Get(CAMERA_PARAM_FOCALLENGTH, focalLength);
   return focalLength;
 }
 
 double
 nsDOMCameraControl::GetFocusDistanceNear(ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
+
   double distance;
-  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCENEAR, &distance);
+  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCENEAR, distance);
   return distance;
 }
 
 double
 nsDOMCameraControl::GetFocusDistanceOptimum(ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
+
   double distance;
-  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, &distance);
+  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, distance);
   return distance;
 }
 
 double
 nsDOMCameraControl::GetFocusDistanceFar(ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
+
   double distance;
-  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEFAR, &distance);
+  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEFAR, distance);
   return distance;
 }
 
 void
 nsDOMCameraControl::SetExposureCompensation(const Optional<double>& aCompensation, ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
+
   if (!aCompensation.WasPassed()) {
     // use NaN to switch the camera back into auto mode
     aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, NAN);
+    return;
   }
 
   aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, aCompensation.Value());
 }
 
 double
 nsDOMCameraControl::GetExposureCompensation(ErrorResult& aRv)
 {
+  MOZ_ASSERT(mCameraControl);
+
   double compensation;
-  aRv = mCameraControl->Get(CAMERA_PARAM_EXPOSURECOMPENSATION, &compensation);
+  aRv = mCameraControl->Get(CAMERA_PARAM_EXPOSURECOMPENSATION, compensation);
   return compensation;
 }
 
 int32_t
 nsDOMCameraControl::SensorAngle()
 {
+  MOZ_ASSERT(mCameraControl);
+
   int32_t angle;
-  mCameraControl->Get(CAMERA_PARAM_SENSORANGLE, &angle);
+  mCameraControl->Get(CAMERA_PARAM_SENSORANGLE, angle);
   return angle;
 }
 
-already_AddRefed<nsICameraShutterCallback>
-nsDOMCameraControl::GetOnShutter(ErrorResult& aRv)
+already_AddRefed<CameraShutterCallback>
+nsDOMCameraControl::GetOnShutter()
 {
-  nsCOMPtr<nsICameraShutterCallback> cb;
-  aRv = mCameraControl->Get(getter_AddRefs(cb));
+  nsCOMPtr<CameraShutterCallback> cb = mOnShutterCb;
   return cb.forget();
 }
 
 void
-nsDOMCameraControl::SetOnShutter(nsICameraShutterCallback* aOnShutter,
-                                 ErrorResult& aRv)
+nsDOMCameraControl::SetOnShutter(CameraShutterCallback* aCb)
 {
-  aRv = mCameraControl->Set(aOnShutter);
+  mOnShutterCb = aCb;
 }
 
-/* attribute nsICameraClosedCallback onClosed; */
-already_AddRefed<nsICameraClosedCallback>
-nsDOMCameraControl::GetOnClosed(ErrorResult& aRv)
+/* attribute CameraClosedCallback onClosed; */
+already_AddRefed<CameraClosedCallback>
+nsDOMCameraControl::GetOnClosed()
 {
-  nsCOMPtr<nsICameraClosedCallback> onClosed;
-  aRv = mCameraControl->Get(getter_AddRefs(onClosed));
+  nsCOMPtr<CameraClosedCallback> onClosed = mOnClosedCb;
   return onClosed.forget();
 }
 
 void
-nsDOMCameraControl::SetOnClosed(nsICameraClosedCallback* aOnClosed,
-                                ErrorResult& aRv)
+nsDOMCameraControl::SetOnClosed(CameraClosedCallback* aCb)
 {
-  aRv = mCameraControl->Set(aOnClosed);
+  mOnClosedCb = aCb;
 }
 
-already_AddRefed<nsICameraRecorderStateChange>
-nsDOMCameraControl::GetOnRecorderStateChange(ErrorResult& aRv)
+already_AddRefed<CameraRecorderStateChange>
+nsDOMCameraControl::GetOnRecorderStateChange()
 {
-  nsCOMPtr<nsICameraRecorderStateChange> cb;
-  aRv = mCameraControl->Get(getter_AddRefs(cb));
+  nsCOMPtr<CameraRecorderStateChange> cb = mOnRecorderStateChangeCb;
   return cb.forget();
 }
 
 void
-nsDOMCameraControl::SetOnRecorderStateChange(nsICameraRecorderStateChange* aOnRecorderStateChange,
-                                             ErrorResult& aRv)
+nsDOMCameraControl::SetOnRecorderStateChange(CameraRecorderStateChange* aCb)
 {
-  aRv = mCameraControl->Set(aOnRecorderStateChange);
+  mOnRecorderStateChangeCb = aCb;
+}
+
+/* attribute CameraPreviewStateChange onPreviewStateChange; */
+already_AddRefed<CameraPreviewStateChange>
+nsDOMCameraControl::GetOnPreviewStateChange()
+{
+  nsCOMPtr<CameraPreviewStateChange> cb = mOnPreviewStateChangeCb;
+  return cb.forget();
+}
+void
+nsDOMCameraControl::SetOnPreviewStateChange(CameraPreviewStateChange* aCb)
+{
+  mOnPreviewStateChangeCb = aCb;
 }
 
+already_AddRefed<dom::CameraCapabilities>
+nsDOMCameraControl::Capabilities()
+{
+  nsRefPtr<CameraCapabilities> caps = mCapabilities;
+
+  if (!caps) {
+    caps = new CameraCapabilities(mWindow);
+    nsresult rv = caps->Populate(mCameraControl);
+    if (NS_FAILED(rv)) {
+      DOM_CAMERA_LOGW("Failed to populate camera capabilities (%d)\n", rv);
+      return nullptr;
+    }
+    mCapabilities = caps;
+  }
+
+  return caps.forget();
+}
+
+// Methods.
 void
-nsDOMCameraControl::StartRecording(JSContext* aCx,
-                                   JS::Handle<JS::Value> aOptions,
-                                   nsDOMDeviceStorage& storageArea,
-                                   const nsAString& filename,
-                                   nsICameraStartRecordingCallback* onSuccess,
-                                   const Optional<nsICameraErrorCallback*>& onError,
+nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions,
+                                   nsDOMDeviceStorage& aStorageArea,
+                                   const nsAString& aFilename,
+                                   CameraStartRecordingCallback& aOnSuccess,
+                                   const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
                                    ErrorResult& aRv)
 {
-  MOZ_ASSERT(onSuccess, "no onSuccess handler passed");
+  MOZ_ASSERT(mCameraControl);
 
-  // Default values, until the dictionary parser can handle them.
-  mOptions.rotation = 0;
-  mOptions.maxFileSizeBytes = 0;
-  mOptions.maxVideoLengthMs = 0;
-  aRv = mOptions.Init(aCx, aOptions.address());
-  if (aRv.Failed()) {
-    return;
-  }
+  NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
 
-  aRv = NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
-
-  #ifdef MOZ_B2G
+#ifdef MOZ_B2G
   if (!mAudioChannelAgent) {
     mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
     if (mAudioChannelAgent) {
       // Camera app will stop recording when it falls to the background, so no callback is necessary.
       mAudioChannelAgent->Init(AUDIO_CHANNEL_CONTENT, nullptr);
       // Video recording doesn't output any sound, so it's not necessary to check canPlay.
       int32_t canPlay;
       mAudioChannelAgent->StartPlaying(&canPlay);
     }
   }
-  #endif
+#endif
 
   nsCOMPtr<nsIDOMDOMRequest> request;
   mDSFileDescriptor = new DeviceStorageFileDescriptor();
-  aRv = storageArea.CreateFileDescriptor(filename, mDSFileDescriptor.get(),
+  aRv = aStorageArea.CreateFileDescriptor(aFilename, mDSFileDescriptor.get(),
                                          getter_AddRefs(request));
   if (aRv.Failed()) {
     return;
   }
 
-  mOnSuccessCb = onSuccess;
-  mOnErrorCb = onError.WasPassed() ? onError.Value() : nullptr;
+  mOptions = aOptions;
+  mStartRecordingOnSuccessCb = &aOnSuccess;
+  mStartRecordingOnErrorCb = nullptr;
+  if (aOnError.WasPassed()) {
+    mStartRecordingOnErrorCb = &aOnError.Value();
+  }
 
-  request->AddEventListener(NS_LITERAL_STRING("success"), this, false);
-  request->AddEventListener(NS_LITERAL_STRING("error"), this, false);
+  nsCOMPtr<nsIDOMEventListener> listener = new StartRecordingHelper(this);
+  request->AddEventListener(NS_LITERAL_STRING("success"), listener, false);
+  request->AddEventListener(NS_LITERAL_STRING("error"), listener, false);
 }
 
-NS_IMETHODIMP
-nsDOMCameraControl::HandleEvent(nsIDOMEvent* aEvent)
+void
+nsDOMCameraControl::OnCreatedFileDescriptor(bool aSucceeded)
 {
-  nsString  eventType;
-  aEvent->GetType(eventType);
-  ErrorResult rv;
-
-  if ((eventType.EqualsLiteral("success")) &&
-      mDSFileDescriptor->mFileDescriptor.IsValid()) {
+  if (aSucceeded && mDSFileDescriptor->mFileDescriptor.IsValid()) {
+    ICameraControl::StartRecordingOptions o;
 
-    rv = mCameraControl->StartRecording(&mOptions,
-                                        mDSFileDescriptor.get(),
-                                        mOnSuccessCb.get(),
-                                        mOnErrorCb.get());
-    if (!rv.Failed()) {
-      return rv.ErrorCode();
+    o.rotation = mOptions.mRotation;
+    o.maxFileSizeBytes = mOptions.mMaxFileSizeBytes;
+    o.maxVideoLengthMs = mOptions.mMaxVideoLengthMs;
+    nsresult rv = mCameraControl->StartRecording(mDSFileDescriptor.get(), &o);
+    if (NS_SUCCEEDED(rv)) {
+      return;
     }
-
-    // An error happened. Fall through and call the error callback.
   }
 
-  // We're already be on the main thread, so go ahead and call the
-  // error callback directly.
-
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (mOnErrorCb &&
-      nsDOMCameraManager::IsWindowStillActive(mWindow->WindowID())) {
-    mOnErrorCb->HandleEvent(NS_LITERAL_STRING("FAILURE"));
-  }
-
-  return NS_OK;
+  OnError(CameraControlListener::kInStartRecording, NS_LITERAL_STRING("FAILURE"));
 }
 
 void
 nsDOMCameraControl::StopRecording(ErrorResult& aRv)
 {
-  aRv = NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
+  MOZ_ASSERT(mCameraControl);
 
-  #ifdef MOZ_B2G
+#ifdef MOZ_B2G
   if (mAudioChannelAgent) {
     mAudioChannelAgent->StopPlaying();
     mAudioChannelAgent = nullptr;
   }
-  #endif
+#endif
 
   aRv = mCameraControl->StopRecording();
 }
 
 void
-nsDOMCameraControl::GetPreviewStream(JSContext* aCx,
-                                     JS::Handle<JS::Value> aOptions,
-                                     nsICameraPreviewStreamCallback* onSuccess,
-                                     const Optional<nsICameraErrorCallback*>& onError,
-                                     ErrorResult& aRv)
-{
-  mozilla::idl::CameraSize size;
-  aRv = size.Init(aCx, aOptions.address());
-  if (aRv.Failed()) {
-    return;
-  }
-
-  aRv = mCameraControl->GetPreviewStream(size, onSuccess,
-                                         onError.WasPassed()
-                                         ? onError.Value() : nullptr);
-}
-
-void
 nsDOMCameraControl::ResumePreview(ErrorResult& aRv)
 {
-  aRv = mCameraControl->StartPreview(nullptr);
-}
-
-already_AddRefed<nsICameraPreviewStateChange>
-nsDOMCameraControl::GetOnPreviewStateChange() const
-{
-  nsCOMPtr<nsICameraPreviewStateChange> cb;
-  mCameraControl->Get(getter_AddRefs(cb));
-  return cb.forget();
-}
-
-void
-nsDOMCameraControl::SetOnPreviewStateChange(nsICameraPreviewStateChange* aCb)
-{
-  mCameraControl->Set(aCb);
-}
-
-void
-nsDOMCameraControl::AutoFocus(nsICameraAutoFocusCallback* onSuccess,
-                              const Optional<nsICameraErrorCallback*>& onError,
-                              ErrorResult& aRv)
-{
-  aRv = mCameraControl->AutoFocus(onSuccess,
-                                  onError.WasPassed() ? onError.Value() : nullptr);
+  MOZ_ASSERT(mCameraControl);
+  aRv = mCameraControl->StartPreview();
 }
 
 void
-nsDOMCameraControl::TakePicture(JSContext* aCx,
-                                const CameraPictureOptions& aOptions,
-                                nsICameraTakePictureCallback* onSuccess,
-                                const Optional<nsICameraErrorCallback*>& aOnError,
-                                ErrorResult& aRv)
+nsDOMCameraControl::SetConfiguration(const CameraConfiguration& aConfiguration,
+                                     const Optional<OwningNonNull<CameraSetConfigurationCallback> >& aOnSuccess,
+                                     const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
+                                     ErrorResult& aRv)
 {
-  mozilla::idl::CameraSize           size;
-  mozilla::idl::CameraPosition       pos;
-
-  aRv = size.Init(aCx, &aOptions.mPictureSize);
-  if (aRv.Failed()) {
-    return;
-  }
+  MOZ_ASSERT(mCameraControl);
 
-  /**
-   * 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;
-  aRv = pos.Init(aCx, &aOptions.mPosition);
-  if (aRv.Failed()) {
-    return;
-  }
-
-  nsICameraErrorCallback* onError =
-    aOnError.WasPassed() ? aOnError.Value() : nullptr;
-  aRv = mCameraControl->TakePicture(size, aOptions.mRotation,
-                                    aOptions.mFileFormat, pos,
-                                    aOptions.mDateTime, onSuccess, onError);
-}
-
-void
-nsDOMCameraControl::GetPreviewStreamVideoMode(JSContext* aCx,
-                                              JS::Handle<JS::Value> aOptions,
-                                              nsICameraPreviewStreamCallback* onSuccess,
-                                              const Optional<nsICameraErrorCallback*>& onError,
-                                              ErrorResult& aRv)
-{
-  mozilla::idl::CameraRecorderOptions options;
-  aRv = options.Init(aCx, aOptions.address());
-  if (aRv.Failed()) {
+  nsCOMPtr<CameraTakePictureCallback> cb = mTakePictureOnSuccessCb;
+  if (cb) {
+    // We're busy taking a picture, can't change modes right now.
+    if (aOnError.WasPassed()) {
+      ErrorResult ignored;
+      aOnError.Value().Call(NS_LITERAL_STRING("Busy"), ignored);
+    }
+    aRv = NS_ERROR_FAILURE;
     return;
   }
 
-  aRv = mCameraControl->GetPreviewStreamVideoMode(&options, onSuccess,
-                                                  onError.WasPassed()
-                                                  ? onError.Value() : nullptr);
+  ICameraControl::Configuration config;
+  config.mRecorderProfile = aConfiguration.mRecorderProfile;
+  config.mPreviewSize.width = aConfiguration.mPreviewSize.mWidth;
+  config.mPreviewSize.height = aConfiguration.mPreviewSize.mHeight;
+  config.mMode = ICameraControl::kPictureMode;
+  if (aConfiguration.mMode == CameraMode::Video) {
+    config.mMode = ICameraControl::kVideoMode;
+  }
+
+  mSetConfigurationOnSuccessCb = nullptr;
+  if (aOnSuccess.WasPassed()) {
+    mSetConfigurationOnSuccessCb = &aOnSuccess.Value();
+  }
+  mSetConfigurationOnErrorCb = nullptr;
+  if (aOnError.WasPassed()) {
+    mSetConfigurationOnErrorCb = &aOnError.Value();
+  }
+
+  aRv = mCameraControl->SetConfiguration(config);
+}
+
+void
+nsDOMCameraControl::AutoFocus(CameraAutoFocusCallback& aOnSuccess,
+                              const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
+                              ErrorResult& aRv)
+{
+  MOZ_ASSERT(mCameraControl);
+
+  nsCOMPtr<CameraAutoFocusCallback> cb = mAutoFocusOnSuccessCb.forget();
+  bool cancel = false;
+  if (cb) {
+    // we have a callback, which means we're already in the process of
+    // auto-focusing--cancel the old callback
+    nsCOMPtr<CameraErrorCallback> ecb = mAutoFocusOnErrorCb.forget();
+    ErrorResult ignored;
+    ecb->Call(NS_LITERAL_STRING("Interrupted"), ignored);
+    cancel = true;
+  }
+
+  mAutoFocusOnSuccessCb = &aOnSuccess;
+  mAutoFocusOnErrorCb = nullptr;
+  if (aOnError.WasPassed()) {
+    mAutoFocusOnErrorCb = &aOnError.Value();
+  }
+
+  aRv = mCameraControl->AutoFocus(cancel);
 }
 
 void
-nsDOMCameraControl::ReleaseHardware(const Optional<nsICameraReleaseCallback*>& onSuccess,
-                                    const Optional<nsICameraErrorCallback*>& onError,
-                                    ErrorResult& aRv)
+nsDOMCameraControl::TakePicture(const CameraPictureOptions& aOptions,
+                                CameraTakePictureCallback& aOnSuccess,
+                                const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
+                                ErrorResult& aRv)
 {
-  aRv =
-    mCameraControl->ReleaseHardware(
-        onSuccess.WasPassed() ? onSuccess.Value() : nullptr,
-        onError.WasPassed() ? onError.Value() : nullptr);
+  MOZ_ASSERT(mCameraControl);
+
+  nsCOMPtr<CameraTakePictureCallback> cb = mTakePictureOnSuccessCb;
+  if (cb) {
+    // There is already a call to TakePicture() in progress, abort this one and
+    //  invoke the error callback (if one was passed in).
+    if (aOnError.WasPassed()) {
+      ErrorResult ignored;
+      aOnError.Value().Call(NS_LITERAL_STRING("TakePictureAlreadyInProgress"), ignored);
+    }
+    aRv = NS_ERROR_FAILURE;
+    return;
+  }
+
+  {
+    ICameraControlParameterSetAutoEnter batch(mCameraControl);
+
+    // XXXmikeh - remove this: see bug 931155
+    ICameraControl::Size s;
+    s.width = aOptions.mPictureSize.mWidth;
+    s.height = aOptions.mPictureSize.mHeight;
+
+    ICameraControl::Position p;
+    p.latitude = aOptions.mPosition.mLatitude;
+    p.longitude = aOptions.mPosition.mLongitude;
+    p.altitude = aOptions.mPosition.mAltitude;
+    p.timestamp = aOptions.mPosition.mTimestamp;
+
+    if (s.width && s.height) {
+      mCameraControl->Set(CAMERA_PARAM_PICTURE_SIZE, s);
+    }
+    mCameraControl->Set(CAMERA_PARAM_PICTURE_ROTATION, aOptions.mRotation);
+    mCameraControl->Set(CAMERA_PARAM_PICTURE_FILEFORMAT, aOptions.mFileFormat);
+    mCameraControl->Set(CAMERA_PARAM_PICTURE_DATETIME, aOptions.mDateTime);
+    mCameraControl->SetLocation(p);
+  }
+
+  mTakePictureOnSuccessCb = &aOnSuccess;
+  mTakePictureOnErrorCb = nullptr;
+  if (aOnError.WasPassed()) {
+    mTakePictureOnErrorCb = &aOnError.Value();
+  }
+
+  aRv = mCameraControl->TakePicture();
 }
 
-class GetCameraResult : public nsRunnable
+void
+nsDOMCameraControl::ReleaseHardware(const Optional<OwningNonNull<CameraReleaseCallback> >& aOnSuccess,
+                                    const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
+                                    ErrorResult& aRv)
 {
-public:
-  GetCameraResult(nsDOMCameraControl* aDOMCameraControl,
-    nsresult aResult,
-    const nsMainThreadPtrHandle<nsICameraGetCameraCallback>& onSuccess,
-    const nsMainThreadPtrHandle<nsICameraErrorCallback>& onError,
-    uint64_t aWindowId)
-    : mDOMCameraControl(aDOMCameraControl)
-    , mResult(aResult)
-    , mOnSuccessCb(onSuccess)
-    , mOnErrorCb(onError)
-    , mWindowId(aWindowId)
-  { }
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mCameraControl);
 
-    if (nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
-      DOM_CAMERA_LOGT("%s : this=%p -- BEFORE CALLBACK\n", __func__, this);
-      if (NS_FAILED(mResult)) {
-        if (mOnErrorCb.get()) {
-          mOnErrorCb->HandleEvent(NS_LITERAL_STRING("FAILURE"));
-        }
-      } else {
-        if (mOnSuccessCb.get()) {
-          mOnSuccessCb->HandleEvent(mDOMCameraControl);
-        }
-      }
-      DOM_CAMERA_LOGT("%s : this=%p -- AFTER CALLBACK\n", __func__, this);
-    }
-
-    /**
-     * Finally, release the extra reference to the DOM-facing camera control.
-     * See the nsDOMCameraControl constructor for the corresponding call to
-     * NS_ADDREF_THIS().
-     */
-    NS_RELEASE(mDOMCameraControl);
-    return NS_OK;
+  mReleaseOnSuccessCb = nullptr;
+  if (aOnSuccess.WasPassed()) {
+    mReleaseOnSuccessCb = &aOnSuccess.Value();
+  }
+  mReleaseOnErrorCb = nullptr;
+  if (aOnError.WasPassed()) {
+    mReleaseOnErrorCb = &aOnError.Value();
   }
 
-protected:
-  /**
-   * 'mDOMCameraControl' is a raw pointer to a previously ADDREF()ed object,
-   * which is released in Run().
-   */
-  nsDOMCameraControl* mDOMCameraControl;
-  nsresult mResult;
-  nsMainThreadPtrHandle<nsICameraGetCameraCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-  uint64_t mWindowId;
-};
-
-nsresult
-nsDOMCameraControl::Result(nsresult aResult,
-                           const nsMainThreadPtrHandle<nsICameraGetCameraCallback>& onSuccess,
-                           const nsMainThreadPtrHandle<nsICameraErrorCallback>& onError,
-                           uint64_t aWindowId)
-{
-  nsCOMPtr<GetCameraResult> getCameraResult = new GetCameraResult(this, aResult, onSuccess, onError, aWindowId);
-  return NS_DispatchToMainThread(getCameraResult);
+  aRv = mCameraControl->ReleaseHardware();
 }
 
 void
 nsDOMCameraControl::Shutdown()
 {
   DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+  MOZ_ASSERT(mCameraControl);
+
+  // Remove any pending solicited event handlers; these
+  // reference our window object, which in turn references
+  // us. If we don't remove them, we can leak DOM objects.
+  mGetCameraOnSuccessCb = nullptr;
+  mGetCameraOnErrorCb = nullptr;
+  mAutoFocusOnSuccessCb = nullptr;
+  mAutoFocusOnErrorCb = nullptr;
+  mTakePictureOnSuccessCb = nullptr;
+  mTakePictureOnErrorCb = nullptr;
+  mStartRecordingOnSuccessCb = nullptr;
+  mStartRecordingOnErrorCb = nullptr;
+  mReleaseOnSuccessCb = nullptr;
+  mReleaseOnErrorCb = nullptr;
+  mSetConfigurationOnSuccessCb = nullptr;
+  mSetConfigurationOnErrorCb = nullptr;
+
+  // Remove all of the unsolicited event handlers too.
+  mOnShutterCb = nullptr;
+  mOnClosedCb = nullptr;
+  mOnRecorderStateChangeCb = nullptr;
+  mOnPreviewStateChangeCb = nullptr;
+
   mCameraControl->Shutdown();
 }
 
 nsRefPtr<ICameraControl>
 nsDOMCameraControl::GetNativeCameraControl()
 {
   return mCameraControl;
 }
@@ -633,8 +957,270 @@ nsDOMCameraControl::NotifyRecordingStatu
   NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE);
 
   return MediaManager::NotifyRecordingStatusChange(mWindow,
                                                    aMsg,
                                                    true /* aIsAudio */,
                                                    true /* aIsVideo */);
 }
 
+// Camera Control event handlers--must only be called from the Main Thread!
+void
+nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState aState)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ErrorResult ignored;
+
+  DOM_CAMERA_LOGI("DOM OnHardwareStateChange(%d)\n", aState);
+
+  switch (aState) {
+    case CameraControlListener::kHardwareOpen:
+      // The hardware is open, so we can return a camera to JS, even if
+      // the preview hasn't started yet.
+      if (mGetCameraOnSuccessCb) {
+        nsCOMPtr<GetCameraCallback> cb = mGetCameraOnSuccessCb.forget();
+        ErrorResult ignored;
+        mGetCameraOnErrorCb = nullptr;
+        cb->Call(*this, *mCurrentConfiguration, ignored);
+      }
+      break;
+
+    case CameraControlListener::kHardwareClosed:
+      if (mReleaseOnSuccessCb) {
+        // If we have this event handler, this was a solicited hardware close.
+        nsCOMPtr<CameraReleaseCallback> cb = mReleaseOnSuccessCb.forget();
+        mReleaseOnErrorCb = nullptr;
+        cb->Call(ignored);
+      } else if(mOnClosedCb) {
+        // If not, something else closed the hardware.
+        nsCOMPtr<CameraClosedCallback> cb = mOnClosedCb;
+        cb->Call(ignored);
+      }
+      break;
+
+    default:
+      MOZ_ASSUME_UNREACHABLE("Unanticipated camera hardware state");
+  }
+}
+
+void
+nsDOMCameraControl::OnShutter()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  DOM_CAMERA_LOGI("DOM ** SNAP **\n");
+
+  nsCOMPtr<CameraShutterCallback> cb = mOnShutterCb;
+  if (cb) {
+    ErrorResult ignored;
+    cb->Call(ignored);
+  }
+}
+
+void
+nsDOMCameraControl::OnPreviewStateChange(CameraControlListener::PreviewState aState)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mOnPreviewStateChangeCb) {
+    return;
+  }
+
+  nsString state;
+  switch (aState) {
+    case CameraControlListener::kPreviewStarted:
+      state = NS_LITERAL_STRING("started");
+      break;
+
+    default:
+      state = NS_LITERAL_STRING("stopped");
+      break;
+  }
+
+  nsCOMPtr<CameraPreviewStateChange> cb = mOnPreviewStateChangeCb;
+  ErrorResult ignored;
+  cb->Call(state, ignored);
+}
+
+void
+nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState aState,
+                                          int32_t aArg, int32_t aTrackNum)
+{
+  // For now, we do nothing with 'aStatus' and 'aTrackNum'.
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ErrorResult ignored;
+  nsString state;
+
+  switch (aState) {
+    case CameraControlListener::kRecorderStarted:
+      if (mStartRecordingOnSuccessCb) {
+        nsCOMPtr<CameraStartRecordingCallback> cb = mStartRecordingOnSuccessCb.forget();
+        mStartRecordingOnErrorCb = nullptr;
+        cb->Call(ignored);
+      }
+      return;
+
+    case CameraControlListener::kRecorderStopped:
+      NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
+      return;
+
+#ifdef MOZ_B2G_CAMERA
+    case CameraControlListener::kFileSizeLimitReached:
+      state = NS_LITERAL_STRING("FileSizeLimitReached");
+      break;
+
+    case CameraControlListener::kVideoLengthLimitReached:
+      state = NS_LITERAL_STRING("VideoLengthLimitReached");
+      break;
+
+    case CameraControlListener::kTrackCompleted:
+      state = NS_LITERAL_STRING("TrackCompleted");
+      break;
+
+    case CameraControlListener::kTrackFailed:
+      state = NS_LITERAL_STRING("TrackFailed");
+      break;
+
+    case CameraControlListener::kMediaRecorderFailed:
+      state = NS_LITERAL_STRING("MediaRecorderFailed");
+      break;
+
+    case CameraControlListener::kMediaServerFailed:
+      state = NS_LITERAL_STRING("MediaServerFailed");
+      break;
+#endif
+
+    default:
+      MOZ_ASSUME_UNREACHABLE("Unanticipated video recorder error");
+      return;
+  }
+
+  nsCOMPtr<CameraRecorderStateChange> cb = mOnRecorderStateChangeCb;
+  if (cb) {
+    cb->Call(state, ignored);
+  }
+}
+
+void
+nsDOMCameraControl::OnConfigurationChange(DOMCameraConfiguration* aConfiguration)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Update our record of the current camera configuration
+  mCurrentConfiguration = aConfiguration;
+
+  DOM_CAMERA_LOGI("DOM OnConfigurationChange: this=%p\n", this);
+  DOM_CAMERA_LOGI("    mode                   : %s\n",
+    mCurrentConfiguration->mMode == CameraMode::Video ? "video" : "picture");
+  DOM_CAMERA_LOGI("    maximum focus areas    : %d\n",
+    mCurrentConfiguration->mMaxFocusAreas);
+  DOM_CAMERA_LOGI("    maximum metering areas : %d\n",
+    mCurrentConfiguration->mMaxMeteringAreas);
+  DOM_CAMERA_LOGI("    preview size (w x h)   : %d x %d\n",
+    mCurrentConfiguration->mPreviewSize.mWidth, mCurrentConfiguration->mPreviewSize.mHeight);
+  DOM_CAMERA_LOGI("    recorder profile       : %s\n",
+    NS_ConvertUTF16toUTF8(mCurrentConfiguration->mRecorderProfile).get());
+
+  nsCOMPtr<CameraSetConfigurationCallback> cb = mSetConfigurationOnSuccessCb.forget();
+  mSetConfigurationOnErrorCb = nullptr;
+  if (cb) {
+    ErrorResult ignored;
+    cb->Call(*mCurrentConfiguration, ignored);
+  }
+}
+
+void
+nsDOMCameraControl::OnAutoFocusComplete(bool aAutoFocusSucceeded)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ErrorResult ignored;
+
+  nsCOMPtr<CameraAutoFocusCallback> cb = mAutoFocusOnSuccessCb.forget();
+  mAutoFocusOnErrorCb = nullptr;
+  cb->Call(aAutoFocusSucceeded, ignored);
+}
+
+void
+nsDOMCameraControl::OnTakePictureComplete(nsIDOMBlob* aPicture)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ErrorResult ignored;
+
+  nsCOMPtr<CameraTakePictureCallback> cb = mTakePictureOnSuccessCb.forget();
+  mTakePictureOnErrorCb = nullptr;
+  cb->Call(aPicture, ignored);
+}
+
+void
+nsDOMCameraControl::OnError(CameraControlListener::CameraErrorContext aContext, const nsAString& aError)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<CameraErrorCallback>* errorCb;
+  switch (aContext) {
+    case CameraControlListener::kInGetCamera:
+      mGetCameraOnSuccessCb = nullptr;
+      errorCb = &mGetCameraOnErrorCb;
+      break;
+
+    case CameraControlListener::kInSetConfiguration:
+      mSetConfigurationOnSuccessCb = nullptr;
+      errorCb = &mSetConfigurationOnErrorCb;
+      break;
+
+    case CameraControlListener::kInAutoFocus:
+      mAutoFocusOnSuccessCb = nullptr;
+      errorCb = &mAutoFocusOnErrorCb;
+      break;
+
+    case CameraControlListener::kInTakePicture:
+      mTakePictureOnSuccessCb = nullptr;
+      errorCb = &mTakePictureOnErrorCb;
+      break;
+
+    case CameraControlListener::kInStartRecording:
+      mStartRecordingOnSuccessCb = nullptr;
+      errorCb = &mStartRecordingOnErrorCb;
+      break;
+
+    case CameraControlListener::kInReleaseHardware:
+      mReleaseOnSuccessCb = nullptr;
+      errorCb = &mReleaseOnErrorCb;
+      break;
+
+    case CameraControlListener::kInStopRecording:
+      NS_WARNING("Failed to stop recording (which shouldn't happen)!");
+      MOZ_CRASH();
+      break;
+
+    case CameraControlListener::kInStartPreview:
+      NS_WARNING("Failed to (re)start preview!");
+      MOZ_CRASH();
+      break;
+
+    case CameraControlListener::kInUnspecified:
+      if (aError.EqualsASCII("ErrorServiceFailed")) {
+        // If the camera service fails, we will get preview-stopped and
+        //  hardware-closed events, so nothing to do here.
+        return;
+      }
+      // fallthrough
+
+    default:
+      MOZ_ASSUME_UNREACHABLE("Error occurred in unanticipated camera state");
+      return;
+  }
+
+  MOZ_ASSERT(errorCb);
+
+  if (!*errorCb) {
+    DOM_CAMERA_LOGW("DOM No error handler for error '%s' at %d\n",
+      NS_LossyConvertUTF16toASCII(aError).get(), aContext);
+    return;
+  }
+
+  // kung-fu death grip
+  nsCOMPtr<CameraErrorCallback> cb = (*errorCb).forget();
+  ErrorResult ignored;
+  cb->Call(aError, ignored);
+}
+
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -3,61 +3,60 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DOM_CAMERA_DOMCAMERACONTROL_H
 #define DOM_CAMERA_DOMCAMERACONTROL_H
 
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsCycleCollectionParticipant.h"
-#include "nsIDOMEventListener.h"
-#include "DictionaryHelpers.h"
+#include "mozilla/dom/CameraControlBinding.h"
 #include "ICameraControl.h"
-#include "DOMCameraPreview.h"
-#include "nsIDOMCameraManager.h"
 #include "CameraCommon.h"
+#include "DOMMediaStream.h"
 #include "AudioChannelAgent.h"
 #include "nsProxyRelease.h"
 #include "nsHashPropertyBag.h"
 #include "DeviceStorage.h"
+#include "DOMCameraControlListener.h"
 
 class nsDOMDeviceStorage;
 class nsPIDOMWindow;
+class nsIDOMBlob;
 
 namespace mozilla {
+
 namespace dom {
-class CameraPictureOptions;
-template<typename T> class Optional;
+  class CameraCapabilities;
+  class CameraPictureOptions;
+  class CameraStartRecordingOptions;
+  template<typename T> class Optional;
 }
 class ErrorResult;
+class StartRecordingHelper;
 
 // Main camera control.
-class nsDOMCameraControl MOZ_FINAL : public nsIDOMEventListener,
-                                     public nsWrapperCache
+class nsDOMCameraControl MOZ_FINAL : public DOMMediaStream
 {
 public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_NSIDOMEVENTLISTENER
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMCameraControl)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsDOMCameraControl, DOMMediaStream)
+  NS_DECL_ISUPPORTS_INHERITED
 
-  nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread,
-                     nsICameraGetCameraCallback* onSuccess,
-                     nsICameraErrorCallback* onError, nsPIDOMWindow* aWindow);
-  nsresult Result(nsresult aResult,
-                  const nsMainThreadPtrHandle<nsICameraGetCameraCallback>& onSuccess,
-                  const nsMainThreadPtrHandle<nsICameraErrorCallback>& onError,
-                  uint64_t aWindowId);
+  nsDOMCameraControl(uint32_t aCameraId,
+                     const dom::CameraConfiguration& aInitialConfig,
+                     dom::GetCameraCallback* aOnSuccess,
+                     dom::CameraErrorCallback* aOnError,
+                     nsPIDOMWindow* aWindow);
   nsRefPtr<ICameraControl> GetNativeCameraControl();
 
   void Shutdown();
 
   nsPIDOMWindow* GetParentObject() const { return mWindow; }
 
-  // WebIDL
-  nsICameraCapabilities* Capabilities();
+  // Attributes.
   void GetEffect(nsString& aEffect, ErrorResult& aRv);
   void SetEffect(const nsAString& aEffect, ErrorResult& aRv);
   void GetWhiteBalanceMode(nsString& aMode, ErrorResult& aRv);
   void SetWhiteBalanceMode(const nsAString& aMode, ErrorResult& aRv);
   void GetSceneMode(nsString& aMode, ErrorResult& aRv);
   void SetSceneMode(const nsAString& aMode, ErrorResult& aRv);
   void GetFlashMode(nsString& aMode, ErrorResult& aRv);
   void SetFlashMode(const nsAString& aMode, ErrorResult& aRv);
@@ -75,55 +74,137 @@ public:
   void SetThumbnailSize(JSContext* aCx, JS::Handle<JS::Value> aSize, ErrorResult& aRv);
   double GetFocalLength(ErrorResult& aRv);
   double GetFocusDistanceNear(ErrorResult& aRv);
   double GetFocusDistanceOptimum(ErrorResult& aRv);
   double GetFocusDistanceFar(ErrorResult& aRv);
   void SetExposureCompensation(const dom::Optional<double>& aCompensation, ErrorResult& aRv);
   double GetExposureCompensation(ErrorResult& aRv);
   int32_t SensorAngle();
-  already_AddRefed<nsICameraShutterCallback> GetOnShutter(ErrorResult& aRv);
-  void SetOnShutter(nsICameraShutterCallback* aCb, ErrorResult& aRv);
-  already_AddRefed<nsICameraClosedCallback> GetOnClosed(ErrorResult& aRv);
-  void SetOnClosed(nsICameraClosedCallback* aCb, ErrorResult& aRv);
-  already_AddRefed<nsICameraRecorderStateChange> GetOnRecorderStateChange(ErrorResult& aRv);
-  void SetOnRecorderStateChange(nsICameraRecorderStateChange* aCb, ErrorResult& aRv);
-  void AutoFocus(nsICameraAutoFocusCallback* aOnSuccess, const dom::Optional<nsICameraErrorCallback*>& aOnErro, ErrorResult& aRvr);
-  void TakePicture(JSContext* aCx, const dom::CameraPictureOptions& aOptions,
-                   nsICameraTakePictureCallback* onSuccess,
-                   const dom::Optional<nsICameraErrorCallback* >& onError,
+  already_AddRefed<dom::CameraCapabilities> Capabilities();
+
+  // Unsolicited event handlers.
+  already_AddRefed<dom::CameraShutterCallback> GetOnShutter();
+  void SetOnShutter(dom::CameraShutterCallback* aCb);
+  already_AddRefed<dom::CameraClosedCallback> GetOnClosed();
+  void SetOnClosed(dom::CameraClosedCallback* aCb);
+  already_AddRefed<dom::CameraRecorderStateChange> GetOnRecorderStateChange();
+  void SetOnRecorderStateChange(dom::CameraRecorderStateChange* aCb);
+  already_AddRefed<dom::CameraPreviewStateChange> GetOnPreviewStateChange();
+  void SetOnPreviewStateChange(dom::CameraPreviewStateChange* aCb);
+
+  // Methods.
+  void SetConfiguration(const dom::CameraConfiguration& aConfiguration,
+                        const dom::Optional<dom::OwningNonNull<dom::CameraSetConfigurationCallback> >& aOnSuccess,
+                        const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
+                        ErrorResult& aRv);
+  void AutoFocus(dom::CameraAutoFocusCallback& aOnSuccess,
+                 const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
+                 ErrorResult& aRv);
+  void TakePicture(const dom::CameraPictureOptions& aOptions,
+                   dom::CameraTakePictureCallback& aOnSuccess,
+                   const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
                    ErrorResult& aRv);
-  already_AddRefed<nsICameraPreviewStateChange> GetOnPreviewStateChange() const;
-  void SetOnPreviewStateChange(nsICameraPreviewStateChange* aOnStateChange);
-  void GetPreviewStreamVideoMode(JSContext* cx, JS::Handle<JS::Value> aOptions, nsICameraPreviewStreamCallback* onSuccess, const dom::Optional<nsICameraErrorCallback* >& onError, ErrorResult& aRv);
-  void StartRecording(JSContext* cx, JS::Handle<JS::Value> aOptions, nsDOMDeviceStorage& storageArea, const nsAString& filename, nsICameraStartRecordingCallback* onSuccess, const dom::Optional<nsICameraErrorCallback* >& onError, ErrorResult& aRv);
+  void StartRecording(const dom::CameraStartRecordingOptions& aOptions,
+                      nsDOMDeviceStorage& storageArea,
+                      const nsAString& filename,
+                      dom::CameraStartRecordingCallback& aOnSuccess,
+                      const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
+                      ErrorResult& aRv);
   void StopRecording(ErrorResult& aRv);
-  void GetPreviewStream(JSContext* cx, JS::Handle<JS::Value> aOptions, nsICameraPreviewStreamCallback* onSuccess, const dom::Optional<nsICameraErrorCallback* >& onError, ErrorResult& aRv);
   void ResumePreview(ErrorResult& aRv);
-  void ReleaseHardware(const dom::Optional<nsICameraReleaseCallback* >& onSuccess, const dom::Optional<nsICameraErrorCallback* >& onError, ErrorResult& aRv);
+  void ReleaseHardware(const dom::Optional<dom::OwningNonNull<dom::CameraReleaseCallback> >& aOnSuccess,
+                       const dom::Optional<dom::OwningNonNull<dom::CameraErrorCallback> >& aOnError,
+                       ErrorResult& aRv);
+
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
 protected:
   virtual ~nsDOMCameraControl();
 
+  class DOMCameraConfiguration : public dom::CameraConfiguration
+  {
+  public:
+    NS_INLINE_DECL_REFCOUNTING(DOMCameraConfiguration)
+
+    DOMCameraConfiguration();
+    DOMCameraConfiguration(const dom::CameraConfiguration& aConfiguration);
+
+    // Additional configuration options that aren't exposed to the DOM
+    uint32_t mMaxFocusAreas;
+    uint32_t mMaxMeteringAreas;
+
+  protected:
+    ~DOMCameraConfiguration();
+  };
+
+  friend class DOMCameraControlListener;
+  friend class mozilla::StartRecordingHelper;
+
+  void OnCreatedFileDescriptor(bool aSucceeded);
+
+  void OnAutoFocusComplete(bool aAutoFocusSucceeded);
+  void OnTakePictureComplete(nsIDOMBlob* aPicture);
+
+  void OnHardwareStateChange(DOMCameraControlListener::HardwareState aState);
+  void OnPreviewStateChange(DOMCameraControlListener::PreviewState aState);
+  void OnRecorderStateChange(CameraControlListener::RecorderState aState, int32_t aStatus, int32_t aTrackNum);
+  void OnConfigurationChange(DOMCameraConfiguration* aConfiguration);
+  void OnShutter();
+  void OnError(CameraControlListener::CameraErrorContext aContext, const nsAString& mError);
+
+  bool IsWindowStillActive();
+
+  nsresult NotifyRecordingStatusChange(const nsString& aMsg);
+
+  nsRefPtr<ICameraControl> mCameraControl; // non-DOM camera control
+
+  // An agent used to join audio channel service.
+  nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
+
+  nsresult Set(JSContext* aCx, uint32_t aKey, const JS::Value& aValue, uint32_t aLimit);
+  nsresult Get(JSContext* aCx, uint32_t aKey, JS::Value* aValue);
+
+  nsRefPtr<DOMCameraConfiguration>              mCurrentConfiguration;
+  nsRefPtr<dom::CameraCapabilities>             mCapabilities;
+
+  // solicited camera control event handlers
+  nsCOMPtr<dom::GetCameraCallback>              mGetCameraOnSuccessCb;
+  nsCOMPtr<dom::CameraErrorCallback>            mGetCameraOnErrorCb;
+  nsCOMPtr<dom::CameraAutoFocusCallback>        mAutoFocusOnSuccessCb;
+  nsCOMPtr<dom::CameraErrorCallback>            mAutoFocusOnErrorCb;
+  nsCOMPtr<dom::CameraTakePictureCallback>      mTakePictureOnSuccessCb;
+  nsCOMPtr<dom::CameraErrorCallback>            mTakePictureOnErrorCb;
+  nsCOMPtr<dom::CameraStartRecordingCallback>   mStartRecordingOnSuccessCb;
+  nsCOMPtr<dom::CameraErrorCallback>            mStartRecordingOnErrorCb;
+  nsCOMPtr<dom::CameraReleaseCallback>          mReleaseOnSuccessCb;
+  nsCOMPtr<dom::CameraErrorCallback>            mReleaseOnErrorCb;
+  nsCOMPtr<dom::CameraSetConfigurationCallback> mSetConfigurationOnSuccessCb;
+  nsCOMPtr<dom::CameraErrorCallback>            mSetConfigurationOnErrorCb;
+
+  // unsolicited event handlers
+  nsCOMPtr<dom::CameraShutterCallback>          mOnShutterCb;
+  nsCOMPtr<dom::CameraClosedCallback>           mOnClosedCb;
+  nsCOMPtr<dom::CameraRecorderStateChange>      mOnRecorderStateChangeCb;
+  nsCOMPtr<dom::CameraPreviewStateChange>       mOnPreviewStateChangeCb;
+
+  // Camera event listener; we only need this weak reference so that
+  //  we can remove the listener from the camera when we're done
+  //  with it.
+  DOMCameraControlListener* mListener;
+
+  // our viewfinder stream
+  CameraPreviewMediaStream* mInput;
+
+  // set once when this object is created
+  nsCOMPtr<nsPIDOMWindow>   mWindow;
+
+  dom::CameraStartRecordingOptions mOptions;
+  nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
+
 private:
   nsDOMCameraControl(const nsDOMCameraControl&) MOZ_DELETE;
   nsDOMCameraControl& operator=(const nsDOMCameraControl&) MOZ_DELETE;
-
-  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
-  nsresult NotifyRecordingStatusChange(const nsString& aMsg);
-
-  mozilla::idl::CameraStartRecordingOptions mOptions;
-  nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
-  nsCOMPtr<nsICameraStartRecordingCallback> mOnSuccessCb;
-  nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
-
-protected:
-  /* additional members */
-  nsRefPtr<ICameraControl>        mCameraControl; // non-DOM camera control
-  nsCOMPtr<nsICameraCapabilities> mDOMCapabilities;
-  // An agent used to join audio channel service.
-  nsCOMPtr<nsIAudioChannelAgent>  mAudioChannelAgent;
-  nsCOMPtr<nsPIDOMWindow> mWindow;
 };
 
 } // namespace mozilla
 
 #endif // DOM_CAMERA_DOMCAMERACONTROL_H
new file mode 100644
--- /dev/null
+++ b/dom/camera/DOMCameraControlListener.cpp
@@ -0,0 +1,329 @@
+/* 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 "DOMCameraControlListener.h"
+#include "nsThreadUtils.h"
+#include "nsDOMFile.h"
+#include "CameraCommon.h"
+#include "DOMCameraControl.h"
+#include "CameraPreviewMediaStream.h"
+#include "mozilla/dom/CameraManagerBinding.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// Boilerplate callback runnable
+class DOMCameraControlListener::DOMCallback : public nsRunnable
+{
+public:
+  DOMCallback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl)
+    : mDOMCameraControl(aDOMCameraControl)
+  { }
+  virtual ~DOMCallback() { }
+
+  virtual void RunCallback(nsDOMCameraControl* aDOMCameraControl) = 0;
+
+  NS_IMETHOD
+  Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsRefPtr<nsDOMCameraControl> camera = mDOMCameraControl.get();
+    if (camera) {
+      RunCallback(camera);
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsMainThreadPtrHandle<nsDOMCameraControl> mDOMCameraControl;
+};
+
+// Specific callback handlers
+void
+DOMCameraControlListener::OnHardwareStateChange(HardwareState aState)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
+             HardwareState aState)
+      : DOMCallback(aDOMCameraControl)
+      , mState(aState)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      aDOMCameraControl->OnHardwareStateChange(mState);
+    }
+
+  protected:
+    HardwareState mState;
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aState));
+}
+
+void
+DOMCameraControlListener::OnPreviewStateChange(PreviewState aState)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
+             PreviewState aState)
+      : DOMCallback(aDOMCameraControl)
+      , mState(aState)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      aDOMCameraControl->OnPreviewStateChange(mState);
+    }
+
+  protected:
+    PreviewState mState;
+  };
+
+  switch (aState) {
+    case kPreviewStopped:
+      // Clear the current frame right away, without dispatching a
+      //  runnable. This is an ugly coupling between the camera's
+      //  SurfaceTextureClient and the MediaStream/ImageContainer,
+      //  but without it, the preview can fail to start.
+      DOM_CAMERA_LOGI("Preview stopped, clearing current frame\n");
+      mStream->ClearCurrentFrame();
+      break;
+
+    case kPreviewPaused:
+      // In the paused state, we still want to reflect the change
+      //  in preview state, but we don't want to clear the current
+      //  frame as above, since doing so seems to cause genlock
+      //  problems when we restart the preview. See bug 957749.
+      DOM_CAMERA_LOGI("Preview paused\n");
+      break;
+
+    case kPreviewStarted:
+      DOM_CAMERA_LOGI("Preview started\n");
+      break;
+
+    default:
+      DOM_CAMERA_LOGE("Unknown preview state %d\n", aState);
+      MOZ_ASSUME_UNREACHABLE("Invalid preview state");
+      return;
+  }
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aState));
+}
+
+void
+DOMCameraControlListener::OnRecorderStateChange(RecorderState aState,
+                                                int32_t aStatus, int32_t aTrackNum)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
+             RecorderState aState,
+             int32_t aStatus,
+             int32_t aTrackNum)
+      : DOMCallback(aDOMCameraControl)
+      , mState(aState)
+      , mStatus(aStatus)
+      , mTrackNum(aTrackNum)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      aDOMCameraControl->OnRecorderStateChange(mState, mStatus, mTrackNum);
+    }
+
+  protected:
+    RecorderState mState;
+    int32_t mStatus;
+    int32_t mTrackNum;
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aState, aStatus, aTrackNum));
+}
+
+void
+DOMCameraControlListener::OnConfigurationChange(const CameraListenerConfiguration& aConfiguration)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
+             const CameraListenerConfiguration& aConfiguration)
+      : DOMCallback(aDOMCameraControl)
+      , mConfiguration(aConfiguration)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      nsRefPtr<nsDOMCameraControl::DOMCameraConfiguration> config =
+        new nsDOMCameraControl::DOMCameraConfiguration();
+
+      switch (mConfiguration.mMode) {
+        case ICameraControl::kVideoMode:
+          config->mMode = CameraMode::Video;
+          break;
+
+        case ICameraControl::kPictureMode:
+          config->mMode = CameraMode::Picture;
+          break;
+
+        default:
+          MOZ_ASSUME_UNREACHABLE("Unanticipated camera mode!");
+      }
+
+      // Map CameraControl parameters to their DOM-facing equivalents
+      config->mRecorderProfile = mConfiguration.mRecorderProfile;
+      config->mPreviewSize.mWidth = mConfiguration.mPreviewSize.width;
+      config->mPreviewSize.mHeight = mConfiguration.mPreviewSize.height;
+      config->mMaxMeteringAreas = mConfiguration.mMaxMeteringAreas;
+      config->mMaxFocusAreas = mConfiguration.mMaxFocusAreas;
+
+      aDOMCameraControl->OnConfigurationChange(config);
+    }
+
+  protected:
+    const CameraListenerConfiguration mConfiguration;
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aConfiguration));
+}
+
+void
+DOMCameraControlListener::OnShutter()
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl)
+      : DOMCallback(aDOMCameraControl)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      aDOMCameraControl->OnShutter();
+    }
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl));
+}
+
+bool
+DOMCameraControlListener::OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight)
+{
+  DOM_CAMERA_LOGI("OnNewPreviewFrame: got %d x %d frame\n", aWidth, aHeight);
+
+  mStream->SetCurrentFrame(gfxIntSize(aWidth, aHeight), aImage);
+  return true;
+}
+
+void
+DOMCameraControlListener::OnAutoFocusComplete(bool aAutoFocusSucceeded)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
+             bool aAutoFocusSucceeded)
+      : DOMCallback(aDOMCameraControl)
+      , mAutoFocusSucceeded(aAutoFocusSucceeded)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      aDOMCameraControl->OnAutoFocusComplete(mAutoFocusSucceeded);
+    }
+
+  protected:
+    bool mAutoFocusSucceeded;
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aAutoFocusSucceeded));
+}
+
+void
+DOMCameraControlListener::OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
+             uint8_t* aData, uint32_t aLength, const nsAString& aMimeType)
+      : DOMCallback(aDOMCameraControl)
+      , mData(aData)
+      , mLength(aLength)
+      , mMimeType(aMimeType)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      nsCOMPtr<nsIDOMBlob> picture = new nsDOMMemoryFile(static_cast<void*>(mData),
+                                                         static_cast<uint64_t>(mLength),
+                                                         mMimeType);
+      aDOMCameraControl->OnTakePictureComplete(picture);
+    }
+
+  protected:
+    uint8_t* mData;
+    uint32_t mLength;
+    nsString mMimeType;
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aData, aLength, aMimeType));
+}
+
+void
+DOMCameraControlListener::OnError(CameraErrorContext aContext, CameraError aError)
+{
+  class Callback : public DOMCallback
+  {
+  public:
+    Callback(nsMainThreadPtrHandle<nsDOMCameraControl> aDOMCameraControl,
+             CameraErrorContext aContext,
+             CameraError aError)
+      : DOMCallback(aDOMCameraControl)
+      , mContext(aContext)
+      , mError(aError)
+    { }
+
+    void
+    RunCallback(nsDOMCameraControl* aDOMCameraControl) MOZ_OVERRIDE
+    {
+      nsString error;
+
+      switch (mError) {
+        case kErrorServiceFailed:
+          error = NS_LITERAL_STRING("ErrorServiceFailed");
+          break;
+
+        case kErrorApiFailed:
+          // XXXmikeh legacy error placeholder
+          error = NS_LITERAL_STRING("FAILURE");
+          break;
+
+        default:
+          error = NS_LITERAL_STRING("ErrorUnknown");
+          break;
+      }
+      aDOMCameraControl->OnError(mContext, error);
+    }
+
+  protected:
+    CameraErrorContext mContext;
+    CameraError mError;
+  };
+
+  NS_DispatchToMainThread(new Callback(mDOMCameraControl, aContext, aError));
+}
new file mode 100644
--- /dev/null
+++ b/dom/camera/DOMCameraControlListener.h
@@ -0,0 +1,48 @@
+/* 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_DOMCAMERACONTROLLISTENER_H
+#define DOM_CAMERA_DOMCAMERACONTROLLISTENER_H
+
+#include "nsProxyRelease.h"
+#include "CameraControlListener.h"
+
+namespace mozilla {
+
+class nsDOMCameraControl;
+class CameraPreviewMediaStream;
+
+class DOMCameraControlListener : public CameraControlListener
+{
+protected:
+  nsMainThreadPtrHandle<nsDOMCameraControl> mDOMCameraControl;
+  CameraPreviewMediaStream* mStream;
+
+  class DOMCallback;
+
+public:
+  DOMCameraControlListener(nsDOMCameraControl* aDOMCameraControl, CameraPreviewMediaStream* aStream)
+    : mDOMCameraControl(new nsMainThreadPtrHolder<nsDOMCameraControl>(aDOMCameraControl))
+    , mStream(aStream)
+  { }
+
+  void OnAutoFocusComplete(bool aAutoFocusSucceeded);
+  void OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType);
+
+  void OnHardwareStateChange(HardwareState aState);
+  void OnPreviewStateChange(PreviewState aState);
+  void OnRecorderStateChange(RecorderState aState, int32_t aStatus, int32_t aTrackNum);
+  void OnConfigurationChange(const CameraListenerConfiguration& aConfiguration);
+  void OnShutter();
+  bool OnNewPreviewFrame(layers::Image* aImage, uint32_t aWidth, uint32_t aHeight);
+  void OnError(CameraErrorContext aContext, CameraError aError);
+
+private:
+  DOMCameraControlListener(const DOMCameraControlListener&) MOZ_DELETE;
+  DOMCameraControlListener& operator=(const DOMCameraControlListener&) MOZ_DELETE;
+};
+
+} // namespace mozilla
+
+#endif // DOM_CAMERA_DOMCAMERACONTROLLISTENER_H
--- a/dom/camera/DOMCameraManager.cpp
+++ b/dom/camera/DOMCameraManager.cpp
@@ -1,79 +1,80 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "DOMCameraManager.h"
 #include "nsDebug.h"
+#include "jsapi.h"
 #include "nsPIDOMWindow.h"
 #include "mozilla/Services.h"
 #include "nsObserverService.h"
 #include "nsIPermissionManager.h"
 #include "DOMCameraControl.h"
-#include "DOMCameraManager.h"
 #include "nsDOMClassInfo.h"
 #include "DictionaryHelpers.h"
 #include "CameraCommon.h"
+#include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/CameraManagerBinding.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(nsDOMCameraManager, mWindow)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCameraManager)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCameraManager)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCameraManager)
 
 /**
  * Global camera logging object
  *
  * Set the NSPR_LOG_MODULES environment variable to enable logging
  * in a debug build, e.g. NSPR_LOG_MODULES=Camera:5
  */
 PRLogModuleInfo*
 GetCameraLog()
 {
   static PRLogModuleInfo *sLog;
-  if (!sLog)
+  if (!sLog) {
     sLog = PR_NewLogModule("Camera");
+  }
   return sLog;
 }
 
-/**
- * nsDOMCameraManager::GetListOfCameras
- * is implementation-specific, and can be found in (e.g.)
- * GonkCameraManager.cpp and FallbackCameraManager.cpp.
- */
-
 WindowTable* nsDOMCameraManager::sActiveWindows = nullptr;
 
 nsDOMCameraManager::nsDOMCameraManager(nsPIDOMWindow* aWindow)
   : mWindowId(aWindow->WindowID())
-  , mCameraThread(nullptr)
   , mWindow(aWindow)
 {
   /* member initializers and constructor code */
   DOM_CAMERA_LOGT("%s:%d : this=%p, windowId=%llx\n", __func__, __LINE__, this, mWindowId);
+  MOZ_COUNT_CTOR(nsDOMCameraManager);
   SetIsDOMBinding();
 }
 
 nsDOMCameraManager::~nsDOMCameraManager()
 {
   /* destructor code */
+  MOZ_COUNT_DTOR(nsDOMCameraManager);
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  if (obs) {
-    obs->RemoveObserver(this, "xpcom-shutdown");
-  }
+}
+
+void
+nsDOMCameraManager::GetListOfCameras(nsTArray<nsString>& aList, ErrorResult& aRv)
+{
+  aRv = ICameraControl::GetListOfCameras(aList);
 }
 
 bool
 nsDOMCameraManager::CheckPermission(nsPIDOMWindow* aWindow)
 {
   nsCOMPtr<nsIPermissionManager> permMgr =
     do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
   NS_ENSURE_TRUE(permMgr, false);
@@ -101,40 +102,38 @@ nsDOMCameraManager::CreateInstance(nsPID
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   obs->AddObserver(cameraManager, "xpcom-shutdown", true);
 
   return cameraManager.forget();
 }
 
 void
-nsDOMCameraManager::GetCamera(const CameraSelector& aOptions,
-                              nsICameraGetCameraCallback* onSuccess,
-                              const Optional<nsICameraErrorCallback*>& onError,
+nsDOMCameraManager::GetCamera(const nsAString& aCamera,
+                              const CameraConfiguration& aInitialConfig,
+                              GetCameraCallback& aOnSuccess,
+                              const Optional<OwningNonNull<CameraErrorCallback> >& aOnError,
                               ErrorResult& aRv)
 {
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+
   uint32_t cameraId = 0;  // back (or forward-facing) camera by default
-  if (aOptions.mCamera.EqualsLiteral("front")) {
+  if (aCamera.EqualsLiteral("front")) {
     cameraId = 1;
   }
 
-  // reuse the same camera thread to conserve resources
-  if (!mCameraThread) {
-    aRv = NS_NewThread(getter_AddRefs(mCameraThread));
-    if (aRv.Failed()) {
-      return;
-    }
+  nsCOMPtr<CameraErrorCallback> errorCallback = nullptr;
+  if (aOnError.WasPassed()) {
+    errorCallback = &aOnError.Value();
   }
 
-  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-
-  // Creating this object will trigger the onSuccess handler
+  // Creating this object will trigger the aOnSuccess callback
+  //  (or the aOnError one, if it fails).
   nsRefPtr<nsDOMCameraControl> cameraControl =
-    new nsDOMCameraControl(cameraId, mCameraThread,
-                           onSuccess, onError.WasPassed() ? onError.Value() : nullptr, mWindow);
+    new nsDOMCameraControl(cameraId, aInitialConfig, &aOnSuccess, errorCallback, mWindow);
 
   Register(cameraControl);
 }
 
 void
 nsDOMCameraManager::Register(nsDOMCameraControl* aDOMCameraControl)
 {
   DOM_CAMERA_LOGI(">>> Register( aDOMCameraControl = %p ) mWindowId = 0x%llx\n", aDOMCameraControl, mWindowId);
--- a/dom/camera/DOMCameraManager.h
+++ b/dom/camera/DOMCameraManager.h
@@ -5,34 +5,34 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DOM_CAMERA_DOMCAMERAMANAGER_H
 #define DOM_CAMERA_DOMCAMERAMANAGER_H
 
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
-#include "nsIThread.h"
 #include "nsIObserver.h"
-#include "nsThreadUtils.h"
 #include "nsHashKeys.h"
 #include "nsWrapperCache.h"
 #include "nsWeakReference.h"
 #include "nsClassHashtable.h"
-#include "nsIDOMCameraManager.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/Attributes.h"
-#include "nsPIDOMWindow.h"
+
+class nsPIDOMWindow;
 
 namespace mozilla {
   class ErrorResult;
-class nsDOMCameraControl;
-namespace dom {
-class CameraSelector;
-}
+  class nsDOMCameraControl;
+  namespace dom {
+    class CameraConfiguration;
+    class GetCameraCallback;
+    class CameraErrorCallback;
+  }
 }
 
 typedef nsTArray<nsRefPtr<mozilla::nsDOMCameraControl> > CameraControls;
 typedef nsClassHashtable<nsUint64HashKey, CameraControls> WindowTable;
 
 class nsDOMCameraManager MOZ_FINAL
   : public nsIObserver
   , public nsSupportsWeakReference
@@ -47,23 +47,21 @@ public:
   static bool CheckPermission(nsPIDOMWindow* aWindow);
   static already_AddRefed<nsDOMCameraManager>
     CreateInstance(nsPIDOMWindow* aWindow);
   static bool IsWindowStillActive(uint64_t aWindowId);
 
   void Register(mozilla::nsDOMCameraControl* aDOMCameraControl);
   void OnNavigation(uint64_t aWindowId);
 
-  nsresult GetNumberOfCameras(int32_t& aDeviceCount);
-  nsresult GetCameraName(uint32_t aDeviceNum, nsCString& aDeviceName);
-
   // WebIDL
-  void GetCamera(const mozilla::dom::CameraSelector& aOptions,
-                 nsICameraGetCameraCallback* aCallback,
-                 const mozilla::dom::Optional<nsICameraErrorCallback*>& ErrorCallback,
+  void GetCamera(const nsAString& aCamera,
+                 const mozilla::dom::CameraConfiguration& aOptions,
+                 mozilla::dom::GetCameraCallback& aOnSuccess,
+                 const mozilla::dom::Optional<mozilla::dom::OwningNonNull<mozilla::dom::CameraErrorCallback> >& aOnError,
                  mozilla::ErrorResult& aRv);
   void GetListOfCameras(nsTArray<nsString>& aList, mozilla::ErrorResult& aRv);
 
   nsPIDOMWindow* GetParentObject() const { return mWindow; }
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
     MOZ_OVERRIDE;
 
 protected:
@@ -74,37 +72,17 @@ protected:
 private:
   nsDOMCameraManager() MOZ_DELETE;
   nsDOMCameraManager(nsPIDOMWindow* aWindow);
   nsDOMCameraManager(const nsDOMCameraManager&) MOZ_DELETE;
   nsDOMCameraManager& operator=(const nsDOMCameraManager&) MOZ_DELETE;
 
 protected:
   uint64_t mWindowId;
-  nsCOMPtr<nsIThread> mCameraThread;
   nsCOMPtr<nsPIDOMWindow> mWindow;
   /**
-   * 'mActiveWindows' is only ever accessed while in the main thread,
+   * 'sActiveWindows' is only ever accessed while in the Main Thread,
    * so it is not otherwise protected.
    */
   static ::WindowTable* sActiveWindows;
 };
 
-class GetCameraTask : public nsRunnable
-{
-public:
-  GetCameraTask(uint32_t aCameraId, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, nsIThread* aCameraThread)
-    : mCameraId(aCameraId)
-    , mOnSuccessCb(onSuccess)
-    , mOnErrorCb(onError)
-    , mCameraThread(aCameraThread)
-  { }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE;
-
-protected:
-  uint32_t mCameraId;
-  nsCOMPtr<nsICameraGetCameraCallback> mOnSuccessCb;
-  nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
-  nsCOMPtr<nsIThread> mCameraThread;
-};
-
 #endif // DOM_CAMERA_DOMCAMERAMANAGER_H
deleted file mode 100644
--- a/dom/camera/DOMCameraPreview.cpp
+++ /dev/null
@@ -1,304 +0,0 @@
-/* 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 "base/basictypes.h"
-#include "Layers.h"
-#include "VideoUtils.h"
-#include "DOMCameraPreview.h"
-#include "CameraCommon.h"
-#include "nsGlobalWindow.h"
-#include "nsIDocument.h"
-#include "nsPIDOMWindow.h"
-
-using namespace mozilla;
-using namespace mozilla::layers;
-
-/**
- * 'PreviewControl' is a helper class that dispatches preview control
- * events from the main thread.
- *
- * NS_NewRunnableMethod() can't be used because it AddRef()s the method's
- * object, which can't be done off the main thread for cycle collection
- * participants.
- *
- * Before using this class, 'aDOMPreview' must be appropriately AddRef()ed.
- */
-class PreviewControl : public nsRunnable
-{
-public:
-  enum {
-    START,
-    STOP,
-    STARTED,
-    STOPPED
-  };
-  PreviewControl(DOMCameraPreview* aDOMPreview, uint32_t aControl)
-    : mDOMPreview(aDOMPreview)
-    , mControl(aControl)
-  { }
-
-  NS_IMETHOD Run()
-  {
-    NS_ASSERTION(NS_IsMainThread(), "PreviewControl not run on main thread");
-
-    switch (mControl) {
-      case START:
-        mDOMPreview->Start();
-        break;
-
-      case STOP:
-        mDOMPreview->StopPreview();
-        break;
-
-      case STARTED:
-        mDOMPreview->SetStateStarted();
-        break;
-
-      case STOPPED:
-        mDOMPreview->SetStateStopped();
-        break;
-
-      default:
-        DOM_CAMERA_LOGE("PreviewControl: invalid control %d\n", mControl);
-        break;
-    }
-
-    return NS_OK;
-  }
-
-protected:
-  /**
-   * This must be a raw pointer because this class is not created on the
-   * main thread, and DOMCameraPreview is not threadsafe.  Prior to
-   * issuing a preview control event, the caller must ensure that
-   * mDOMPreview will not disappear.
-   */
-  DOMCameraPreview* mDOMPreview;
-  uint32_t mControl;
-};
-
-class DOMCameraPreviewListener : public MediaStreamListener
-{
-public:
-  DOMCameraPreviewListener(DOMCameraPreview* aDOMPreview) :
-    mDOMPreview(aDOMPreview)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  ~DOMCameraPreviewListener()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  void NotifyConsumptionChanged(MediaStreamGraph* aGraph, Consumption aConsuming)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-
-#ifdef PR_LOGGING
-    const char* state;
-
-    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);
-#endif
-    nsCOMPtr<nsIRunnable> previewControl;
-
-    switch (aConsuming) {
-      case NOT_CONSUMED:
-        previewControl = new PreviewControl(mDOMPreview, PreviewControl::STOP);
-        break;
-
-      case CONSUMED:
-        previewControl = new PreviewControl(mDOMPreview, PreviewControl::START);
-        break;
-
-      default:
-        return;
-    }
-
-    nsresult rv = NS_DispatchToMainThread(previewControl);
-    if (NS_FAILED(rv)) {
-      DOM_CAMERA_LOGE("Failed to dispatch preview control (%d)!\n", rv);
-    }
-  }
-
-protected:
-  // Raw pointer; if we exist, 'mDOMPreview' exists as well
-  DOMCameraPreview* mDOMPreview;
-};
-
-DOMCameraPreview::DOMCameraPreview(nsGlobalWindow* aWindow,
-                                   ICameraControl* aCameraControl,
-                                   uint32_t aWidth, uint32_t aHeight,
-                                   uint32_t aFrameRate)
-  : DOMMediaStream()
-  , mState(STOPPED)
-  , mWidth(aWidth)
-  , mHeight(aHeight)
-  , mFramesPerSecond(aFrameRate)
-  , mFrameCount(0)
-  , mCameraControl(aCameraControl)
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p : mWidth=%d, mHeight=%d, mFramesPerSecond=%d\n", __func__, __LINE__, this, mWidth, mHeight, mFramesPerSecond);
-
-  mImageContainer = LayerManager::CreateImageContainer();
-  mWindow = aWindow;
-  mInput = new CameraPreviewMediaStream(this);
-  mStream = mInput;
-
-  mListener = new DOMCameraPreviewListener(this);
-  mInput->AddListener(mListener);
-
-  if (aWindow->GetExtantDoc()) {
-    CombineWithPrincipal(aWindow->GetExtantDoc()->NodePrincipal());
-  }
-}
-
-DOMCameraPreview::~DOMCameraPreview()
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p, mListener=%p\n", __func__, __LINE__, this, mListener);
-  mInput->RemoveListener(mListener);
-}
-
-bool
-DOMCameraPreview::HaveEnoughBuffered()
-{
-  return true;
-}
-
-bool
-DOMCameraPreview::ReceiveFrame(void* aBuffer, ImageFormat aFormat, FrameBuilder aBuilder)
-{
-  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  if (!aBuffer || !aBuilder) {
-    return false;
-  }
-  if (mState != STARTED) {
-    return false;
-  }
-
-  nsRefPtr<Image> image = mImageContainer->CreateImage(aFormat);
-  aBuilder(image, aBuffer, mWidth, mHeight);
-
-  mInput->SetCurrentFrame(gfxIntSize(mWidth, mHeight), image);
-  return true;
-}
-
-void
-DOMCameraPreview::Start()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Start() not called from main thread");
-  if (mState != STOPPED) {
-    return;
-  }
-
-  DOM_CAMERA_LOGI("Starting preview stream\n");
-
-  /**
-   * Add a reference to ourselves to make sure we stay alive while
-   * the preview is running, as the CameraControlImpl object holds a
-   * weak reference to us.
-   *
-   * This reference is removed in SetStateStopped().
-   */
-  NS_ADDREF_THIS();
-  DOM_CAMERA_SETSTATE(STARTING);
-  mCameraControl->StartPreview(this);
-}
-
-void
-DOMCameraPreview::SetStateStarted()
-{
-  NS_ASSERTION(NS_IsMainThread(), "SetStateStarted() not called from main thread");
-
-  DOM_CAMERA_SETSTATE(STARTED);
-  DOM_CAMERA_LOGI("Preview stream started\n");
-}
-
-void
-DOMCameraPreview::Started()
-{
-  if (mState != STARTING) {
-    return;
-  }
-
-  DOM_CAMERA_LOGI("Dispatching preview stream started\n");
-  nsCOMPtr<nsIRunnable> started = new PreviewControl(this, PreviewControl::STARTED);
-  nsresult rv = NS_DispatchToMainThread(started);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGE("failed to set statrted state (%d), POTENTIAL MEMORY LEAK!\n", rv);
-  }
-}
-
-void
-DOMCameraPreview::StopPreview()
-{
-  NS_ASSERTION(NS_IsMainThread(), "StopPreview() not called from main thread");
-  if (mState != STARTED) {
-    return;
-  }
-
-  DOM_CAMERA_LOGI("Stopping preview stream\n");
-  DOM_CAMERA_SETSTATE(STOPPING);
-  mCameraControl->StopPreview();
-  //mInput->EndTrack(TRACK_VIDEO);
-  //mInput->Finish();
-}
-
-void
-DOMCameraPreview::SetStateStopped()
-{
-  NS_ASSERTION(NS_IsMainThread(), "SetStateStopped() not called from main thread");
-
-  // see bug 809259 and bug 817367.
-  if (mState != STOPPING) {
-    //mInput->EndTrack(TRACK_VIDEO);
-    //mInput->Finish();
-  }
-  DOM_CAMERA_SETSTATE(STOPPED);
-  DOM_CAMERA_LOGI("Preview stream stopped\n");
-
-  /**
-   * Only remove the reference added in Start() once the preview
-   * has stopped completely.
-   */
-  NS_RELEASE_THIS();
-}
-
-void
-DOMCameraPreview::Stopped(bool aForced)
-{
-  if (mState != STOPPING && !aForced) {
-    return;
-  }
-
-  mInput->ClearCurrentFrame();
-
-  DOM_CAMERA_LOGI("Dispatching preview stream stopped\n");
-  nsCOMPtr<nsIRunnable> stopped = new PreviewControl(this, PreviewControl::STOPPED);
-  nsresult rv = NS_DispatchToMainThread(stopped);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGE("failed to decrement reference count (%d), MEMORY LEAK!\n", rv);
-  }
-}
-
-void
-DOMCameraPreview::Error()
-{
-  DOM_CAMERA_LOGE("Error occurred changing preview state!\n");
-  Stopped(true);
-}
deleted file mode 100644
--- a/dom/camera/DOMCameraPreview.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/* 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_DOMCAMERAPREVIEW_H
-#define DOM_CAMERA_DOMCAMERAPREVIEW_H
-
-#include "nsCycleCollectionParticipant.h"
-#include "MediaStreamGraph.h"
-#include "StreamBuffer.h"
-#include "ICameraControl.h"
-#include "DOMMediaStream.h"
-#include "CameraPreviewMediaStream.h"
-#include "CameraCommon.h"
-
-class nsGlobalWindow;
-
-namespace mozilla {
-
-typedef void (*FrameBuilder)(mozilla::layers::Image* aImage, void* aBuffer, uint32_t aWidth, uint32_t aHeight);
-
-/**
- * DOMCameraPreview is only exposed to the DOM as an nsDOMMediaStream,
- * which is a cycle-collection participant already, and we don't
- * add any traceable fields here, so we don't need to declare any
- * more cycle-collection goop.
- */
-class DOMCameraPreview : public DOMMediaStream
-{
-protected:
-  enum { TRACK_VIDEO = 1 };
-
-public:
-  DOMCameraPreview(nsGlobalWindow* aWindow, ICameraControl* aCameraControl,
-                   uint32_t aWidth, uint32_t aHeight, uint32_t aFramesPerSecond = 30);
-
-  bool ReceiveFrame(void* aBuffer, ImageFormat aFormat, mozilla::FrameBuilder aBuilder);
-  bool HaveEnoughBuffered();
-
-  void Start();   // called by the MediaStreamListener to start preview
-  void Started(); // called by the CameraControl when preview is started
-  void StopPreview(); // called by the MediaStreamListener to stop preview
-  void Stopped(bool aForced = false);
-                  // called by the CameraControl when preview is stopped
-  void Error();   // something went wrong, NS_RELEASE needed
-
-  void SetStateStarted();
-  void SetStateStopped();
-
-protected:
-  virtual ~DOMCameraPreview();
-
-  enum {
-    STOPPED,
-    STARTING,
-    STARTED,
-    STOPPING
-  };
-  uint32_t mState;
-
-  // Helper function, used in conjunction with the macro below, to make
-  //  it easy to track state changes, which must happen only on the main
-  //  thread.
-  void
-  SetState(uint32_t aNewState, const char* aFileOrFunc, int aLine)
-  {
-#ifdef PR_LOGGING
-    const char* states[] = { "stopped", "starting", "started", "stopping" };
-    MOZ_ASSERT(mState < sizeof(states) / sizeof(states[0]));
-    MOZ_ASSERT(aNewState < sizeof(states) / sizeof(states[0]));
-    DOM_CAMERA_LOGI("SetState: (this=%p) '%s' --> '%s' : %s:%d\n", this, states[mState], states[aNewState], aFileOrFunc, aLine);
-#endif
-
-    NS_ASSERTION(NS_IsMainThread(), "Preview state set OFF OF main thread!");
-    mState = aNewState;
-  }
-
-  uint32_t mWidth;
-  uint32_t mHeight;
-  uint32_t mFramesPerSecond;
-  CameraPreviewMediaStream* mInput;
-  nsRefPtr<mozilla::layers::ImageContainer> mImageContainer;
-  VideoSegment mVideoSegment;
-  uint32_t mFrameCount;
-  nsRefPtr<ICameraControl> mCameraControl;
-
-  // Raw pointer; AddListener() keeps the reference for us
-  MediaStreamListener* mListener;
-
-private:
-  DOMCameraPreview(const DOMCameraPreview&) MOZ_DELETE;
-  DOMCameraPreview& operator=(const DOMCameraPreview&) MOZ_DELETE;
-};
-
-} // namespace mozilla
-
-#define DOM_CAMERA_SETSTATE(newState)   SetState((newState), __func__, __LINE__)
-
-#endif // DOM_CAMERA_DOMCAMERAPREVIEW_H
--- a/dom/camera/FallbackCameraCapabilities.cpp
+++ b/dom/camera/FallbackCameraCapabilities.cpp
@@ -1,15 +1,14 @@
 /* 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 "DOMCameraCapabilities.h"
 #include "DOMCameraControl.h"
-#include "DOMCameraCapabilities.h"
 #include "CameraCommon.h"
 
 using namespace mozilla;
 
 DOMCI_DATA(CameraCapabilities, nsICameraCapabilities)
 
 NS_INTERFACE_MAP_BEGIN(DOMCameraCapabilities)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
--- a/dom/camera/FallbackCameraControl.cpp
+++ b/dom/camera/FallbackCameraControl.cpp
@@ -1,216 +1,75 @@
 /* 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 "DOMCameraControl.h"
 #include "CameraControlImpl.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 namespace mozilla {
-class RecorderProfileManager;
-}
-
-/**
- * Fallback camera control subclass.  Can be used as a template for the
- * definition of new camera support classes.
- */
-class nsFallbackCameraControl : public CameraControlImpl
-{
-public:
-  nsFallbackCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId);
-
-  const char* GetParameter(const char* aKey);
-  const char* GetParameterConstChar(uint32_t aKey);
-  double GetParameterDouble(uint32_t aKey);
-  int32_t GetParameterInt32(uint32_t aKey);
-  void GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions);
-  void GetParameter(uint32_t aKey, idl::CameraSize& aSize);
-  void SetParameter(const char* aKey, const char* aValue);
-  void SetParameter(uint32_t aKey, const char* aValue);
-  void SetParameter(uint32_t aKey, double aValue);
-  void SetParameter(uint32_t aKey, const nsTArray<idl::CameraRegion>& aRegions);
-  void SetParameter(uint32_t aKey, const idl::CameraSize& aSize);
-  nsresult GetVideoSizes(nsTArray<idl::CameraSize>& aVideoSizes);
-  nsresult PushParameters();
-
-protected:
-  ~nsFallbackCameraControl();
+  class RecorderProfileManager;
 
-  nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream);
-  nsresult StartPreviewImpl(StartPreviewTask* aStartPreview);
-  nsresult StopPreviewImpl(StopPreviewTask* aStopPreview);
-  nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus);
-  nsresult TakePictureImpl(TakePictureTask* aTakePicture);
-  nsresult StartRecordingImpl(StartRecordingTask* aStartRecording);
-  nsresult StopRecordingImpl(StopRecordingTask* aStopRecording);
-  nsresult PushParametersImpl();
-  nsresult PullParametersImpl();
-  already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl();
-
-private:
-  nsFallbackCameraControl(const nsFallbackCameraControl&) MOZ_DELETE;
-  nsFallbackCameraControl& operator=(const nsFallbackCameraControl&) MOZ_DELETE;
-};
-
-/**
- * Stub implementation of the DOM-facing camera control constructor.
- *
- * This should never get called--it exists to keep the linker happy; if
- * implemented, it should construct (e.g.) nsFallbackCameraControl and
- * store a reference in the 'mCameraControl' member (which is why it is
- * defined here).
- */
-nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, nsPIDOMWindow* aWindow) :
-  mWindow(aWindow)
-{
-  MOZ_ASSERT(aWindow, "shouldn't be created with null window!");
-  SetIsDOMBinding();
+  namespace layers {
+    class GraphicBufferLocked;
+  }
 }
 
 /**
- * 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.
+ * Fallback camera control subclass. Can be used as a template for the
+ * definition of new camera support classes.
  */
-nsFallbackCameraControl::nsFallbackCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
-  : CameraControlImpl(aCameraId, aCameraThread, aWindowId)
-{
-}
-
-nsFallbackCameraControl::~nsFallbackCameraControl()
-{
-}
-
-const char*
-nsFallbackCameraControl::GetParameter(const char* aKey)
-{
-  return nullptr;
-}
-
-const char*
-nsFallbackCameraControl::GetParameterConstChar(uint32_t aKey)
-{
-  return nullptr;
-}
-
-double
-nsFallbackCameraControl::GetParameterDouble(uint32_t aKey)
-{
-  return NAN;
-}
-
-int32_t
-nsFallbackCameraControl::GetParameterInt32(uint32_t aKey)
-{
-  return 0;
-}
-
-void
-nsFallbackCameraControl::GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions)
-{
-}
-
-void
-nsFallbackCameraControl::GetParameter(uint32_t aKey, idl::CameraSize& aSize)
-{
-}
-
-void
-nsFallbackCameraControl::SetParameter(const char* aKey, const char* aValue)
-{
-}
-
-void
-nsFallbackCameraControl::SetParameter(uint32_t aKey, const char* aValue)
-{
-}
-
-void
-nsFallbackCameraControl::SetParameter(uint32_t aKey, double aValue)
-{
-}
-
-void
-nsFallbackCameraControl::SetParameter(uint32_t aKey, const nsTArray<idl::CameraRegion>& aRegions)
-{
-}
-
-void
-nsFallbackCameraControl::SetParameter(uint32_t aKey, const idl::CameraSize& aSize)
+class FallbackCameraControl : public CameraControlImpl
 {
-}
+public:
+  FallbackCameraControl(uint32_t aCameraId) : CameraControlImpl(aCameraId) { }
 
-nsresult
-nsFallbackCameraControl::PushParameters()
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-nsresult
-nsFallbackCameraControl::GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
+  void OnAutoFocusComplete(bool aSuccess);
+  void OnTakePictureComplete(uint8_t* aData, uint32_t aLength) { }
+  void OnTakePictureError() { }
+  void OnNewPreviewFrame(layers::GraphicBufferLocked* aBuffer) { }
+  void OnRecorderEvent(int msg, int ext1, int ext2) { }
+  void OnError(CameraControlListener::CameraErrorContext aWhere,
+               CameraControlListener::CameraError aError) { }
 
-nsresult
-nsFallbackCameraControl::StartPreviewImpl(StartPreviewTask* aGetPreviewStream)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
+  virtual nsresult Set(uint32_t aKey, const nsAString& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, nsAString& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Set(uint32_t aKey, double aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, double& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Set(uint32_t aKey, int32_t aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, int32_t& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Set(uint32_t aKey, int64_t aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, int64_t& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Set(uint32_t aKey, const Size& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, Size& aValue) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Set(uint32_t aKey, const nsTArray<Region>& aRegions) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, nsTArray<Region>& aRegions) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
 
-nsresult
-nsFallbackCameraControl::StopPreviewImpl(StopPreviewTask* aGetPreviewStream)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-nsresult
-nsFallbackCameraControl::AutoFocusImpl(AutoFocusTask* aAutoFocus)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
+  virtual nsresult SetLocation(const Position& aLocation) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
 
-nsresult
-nsFallbackCameraControl::TakePictureImpl(TakePictureTask* aTakePicture)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
+  virtual nsresult Get(uint32_t aKey, nsTArray<Size>& aSizes) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, nsTArray<nsString>& aValues) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
+  virtual nsresult Get(uint32_t aKey, nsTArray<double>& aValues) MOZ_OVERRIDE { return NS_ERROR_FAILURE; }
 
-nsresult
-nsFallbackCameraControl::StartRecordingImpl(StartRecordingTask* aStartRecording)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
+  nsresult PushParameters() { return NS_ERROR_FAILURE; }
+  nsresult PullParameters() { return NS_ERROR_FAILURE; }
 
-nsresult
-nsFallbackCameraControl::StopRecordingImpl(StopRecordingTask* aStopRecording)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
+protected:
+  ~FallbackCameraControl();
 
-nsresult
-nsFallbackCameraControl::PushParametersImpl()
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-nsresult
-nsFallbackCameraControl::PullParametersImpl()
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
+  virtual nsresult StartPreviewImpl() { return NS_ERROR_FAILURE; }
+  virtual nsresult StopPreviewImpl() { return NS_ERROR_FAILURE; }
+  virtual nsresult AutoFocusImpl(bool aCancelExistingCall) { return NS_ERROR_FAILURE; }
+  virtual nsresult TakePictureImpl() { return NS_ERROR_FAILURE; }
+  virtual nsresult StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescriptor,
+                                      const StartRecordingOptions* aOptions = nullptr)
+                                        { return NS_ERROR_FAILURE; }
+  virtual nsresult StopRecordingImpl() { return NS_ERROR_FAILURE; }
+  virtual nsresult PushParametersImpl() { return NS_ERROR_FAILURE; }
+  virtual nsresult PullParametersImpl() { return NS_ERROR_FAILURE; }
+  virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManagerImpl() { return nullptr; }
 
-nsresult
-nsFallbackCameraControl::GetVideoSizes(nsTArray<idl::CameraSize>& aVideoSizes)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-already_AddRefed<RecorderProfileManager> 
-nsFallbackCameraControl::GetRecorderProfileManagerImpl()
-{
-  return nullptr;
-}
+private:
+  FallbackCameraControl(const FallbackCameraControl&) MOZ_DELETE;
+  FallbackCameraControl& operator=(const FallbackCameraControl&) MOZ_DELETE;
+};
--- a/dom/camera/FallbackCameraManager.cpp
+++ b/dom/camera/FallbackCameraManager.cpp
@@ -1,28 +1,32 @@
 /* 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"
-
-#include "mozilla/ErrorResult.h"
+#include "ICameraControl.h"
 
 using namespace mozilla;
 
-// From nsDOMCameraManager.
+// From ICameraControl.
 nsresult
-nsDOMCameraManager::GetNumberOfCameras(int32_t& aDeviceCount)
+ICameraControl::GetNumberOfCameras(int32_t& aDeviceCount)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 };
 
 nsresult
-nsDOMCameraManager::GetCameraName(uint32_t aDeviceNum, nsCString& aDeviceName)
+ICameraControl::GetCameraName(uint32_t aDeviceNum, nsCString& aDeviceName)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
-void
-nsDOMCameraManager::GetListOfCameras(nsTArray<nsString>& aList, ErrorResult& aRv)
+nsresult
+ICameraControl::GetListOfCameras(nsTArray<nsString>& aList)
 {
-  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+  return NS_ERROR_NOT_IMPLEMENTED;
 }
+
+already_AddRefed<ICameraControl>
+ICameraControl::Create(uint32_t aCameraId, const Configuration* aInitialConfig)
+{
+  return nullptr;
+}
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -1,976 +1,755 @@
 /*
- * Copyright (C) 2012 Mozilla Foundation
+ * Copyright (C) 2012-2014 Mozilla Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * 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 "GonkCameraControl.h"
 #include <time.h>
 #include <string.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <libgen.h>
 #include "base/basictypes.h"
 #include "camera/CameraParameters.h"
 #include "nsCOMPtr.h"
-#include "nsDOMClassInfo.h"
 #include "nsMemory.h"
 #include "nsThread.h"
 #include <media/MediaProfiles.h>
 #include "mozilla/FileUtils.h"
 #include "mozilla/Services.h"
+#include "mozilla/unused.h"
 #include "nsAlgorithm.h"
 #include <media/mediaplayer.h>
 #include "nsPrintfCString.h"
 #include "nsIObserverService.h"
 #include "nsIVolume.h"
 #include "nsIVolumeService.h"
-#include "DOMCameraManager.h"
+#include "AutoRwLock.h"
 #include "GonkCameraHwMgr.h"
-#include "DOMCameraCapabilities.h"
-#include "DOMCameraControl.h"
 #include "GonkRecorderProfiles.h"
-#include "GonkCameraControl.h"
 #include "CameraCommon.h"
+#include "GonkCameraParameters.h"
 #include "DeviceStorageFileDescriptor.h"
 
 using namespace mozilla;
-using namespace mozilla::dom;
 using namespace mozilla::layers;
+using namespace mozilla::gfx;
 using namespace android;
-using mozilla::gfx::IntSize;
-
-/**
- * See bug 783682.  Most camera implementations, despite claiming they
- * support 'yuv420p' as a preview format, actually ignore this setting
- * and return 'yuv420sp' data anyway.  We have come across a new implementation
- * that, while reporting that 'yuv420p' is supported *and* has been accepted,
- * still returns the frame data in 'yuv420sp' anyway.  So for now, since
- * everyone seems to return this format, we just force it.
- */
-#define FORCE_PREVIEW_FORMAT_YUV420SP   1
 
 #define RETURN_IF_NO_CAMERA_HW()                                          \
   do {                                                                    \
     if (!mCameraHw.get()) {                                               \
       DOM_CAMERA_LOGE("%s:%d : mCameraHw is null\n", __func__, __LINE__); \
       return NS_ERROR_NOT_AVAILABLE;                                      \
     }                                                                     \
   } while(0)
 
-static const char* getKeyText(uint32_t aKey)
-{
-  switch (aKey) {
-    case CAMERA_PARAM_EFFECT:
-      return CameraParameters::KEY_EFFECT;
-    case CAMERA_PARAM_WHITEBALANCE:
-      return CameraParameters::KEY_WHITE_BALANCE;
-    case CAMERA_PARAM_SCENEMODE:
-      return CameraParameters::KEY_SCENE_MODE;
-    case CAMERA_PARAM_FLASHMODE:
-      return CameraParameters::KEY_FLASH_MODE;
-    case CAMERA_PARAM_FOCUSMODE:
-      return CameraParameters::KEY_FOCUS_MODE;
-    case CAMERA_PARAM_ZOOM:
-      return CameraParameters::KEY_ZOOM;
-    case CAMERA_PARAM_METERINGAREAS:
-      return CameraParameters::KEY_METERING_AREAS;
-    case CAMERA_PARAM_FOCUSAREAS:
-      return CameraParameters::KEY_FOCUS_AREAS;
-    case CAMERA_PARAM_FOCALLENGTH:
-      return CameraParameters::KEY_FOCAL_LENGTH;
-    case CAMERA_PARAM_FOCUSDISTANCENEAR:
-      return CameraParameters::KEY_FOCUS_DISTANCES;
-    case CAMERA_PARAM_FOCUSDISTANCEOPTIMUM:
-      return CameraParameters::KEY_FOCUS_DISTANCES;
-    case CAMERA_PARAM_FOCUSDISTANCEFAR:
-      return CameraParameters::KEY_FOCUS_DISTANCES;
-    case CAMERA_PARAM_EXPOSURECOMPENSATION:
-      return CameraParameters::KEY_EXPOSURE_COMPENSATION;
-    case CAMERA_PARAM_PICTURESIZE:
-      return CameraParameters::KEY_PICTURE_SIZE;
-    case CAMERA_PARAM_THUMBNAILQUALITY:
-      return CameraParameters::KEY_JPEG_THUMBNAIL_QUALITY;
-
-    case CAMERA_PARAM_SUPPORTED_PREVIEWSIZES:
-      return CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES;
-    case CAMERA_PARAM_SUPPORTED_VIDEOSIZES:
-      return CameraParameters::KEY_SUPPORTED_VIDEO_SIZES;
-    case CAMERA_PARAM_SUPPORTED_PICTURESIZES:
-      return CameraParameters::KEY_SUPPORTED_PICTURE_SIZES;
-    case CAMERA_PARAM_SUPPORTED_PICTUREFORMATS:
-      return CameraParameters::KEY_SUPPORTED_PICTURE_FORMATS;
-    case CAMERA_PARAM_SUPPORTED_WHITEBALANCES:
-      return CameraParameters::KEY_SUPPORTED_WHITE_BALANCE;
-    case CAMERA_PARAM_SUPPORTED_SCENEMODES:
-      return CameraParameters::KEY_SUPPORTED_SCENE_MODES;
-    case CAMERA_PARAM_SUPPORTED_EFFECTS:
-      return CameraParameters::KEY_SUPPORTED_EFFECTS;
-    case CAMERA_PARAM_SUPPORTED_FLASHMODES:
-      return CameraParameters::KEY_SUPPORTED_FLASH_MODES;
-    case CAMERA_PARAM_SUPPORTED_FOCUSMODES:
-      return CameraParameters::KEY_SUPPORTED_FOCUS_MODES;
-    case CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS:
-      return CameraParameters::KEY_MAX_NUM_FOCUS_AREAS;
-    case CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS:
-      return CameraParameters::KEY_MAX_NUM_METERING_AREAS;
-    case CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION:
-      return CameraParameters::KEY_MIN_EXPOSURE_COMPENSATION;
-    case CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION:
-      return CameraParameters::KEY_MAX_EXPOSURE_COMPENSATION;
-    case CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP:
-      return CameraParameters::KEY_EXPOSURE_COMPENSATION_STEP;
-    case CAMERA_PARAM_SUPPORTED_ZOOM:
-      return CameraParameters::KEY_ZOOM_SUPPORTED;
-    case CAMERA_PARAM_SUPPORTED_ZOOMRATIOS:
-      return CameraParameters::KEY_ZOOM_RATIOS;
-    case CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES:
-      return CameraParameters::KEY_SUPPORTED_JPEG_THUMBNAIL_SIZES;
-    default:
-      return nullptr;
-  }
-}
-
-// nsDOMCameraControl implementation-specific constructor
-nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, nsPIDOMWindow* aWindow)
-  : mDOMCapabilities(nullptr), mWindow(aWindow)
-{
-  MOZ_ASSERT(aWindow, "shouldn't be created with null window!");
-  DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  SetIsDOMBinding();
-
-  /**
-   * nsDOMCameraControl is a cycle-collection participant, which means it is
-   * not threadsafe--so we need to bump up its reference count here to make
-   * sure that it exists long enough to be initialized.
-   *
-   * Once it is initialized, the GetCameraResult main-thread runnable will
-   * decrement it again to make sure it can be cleaned up.
-   *
-   * nsGonkCameraControl MUST NOT hold a strong reference to this
-   * nsDOMCameraControl or memory will leak!
-   */
-  NS_ADDREF_THIS();
-  nsRefPtr<nsGonkCameraControl> control = new nsGonkCameraControl(aCameraId, aCameraThread, this, onSuccess, onError, aWindow->WindowID());
-  control->DispatchInit(this, onSuccess, onError, aWindow->WindowID());
-  mCameraControl = control;
-}
-
-// Gonk-specific CameraControl implementation.
-
-// Initialize nsGonkCameraControl instance--runs on camera thread.
-class InitGonkCameraControl : public nsRunnable
-{
-public:
-  InitGonkCameraControl(nsGonkCameraControl* aCameraControl, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
-    : mCameraControl(aCameraControl)
-    , mDOMCameraControl(aDOMCameraControl)
-    , mOnSuccessCb(new nsMainThreadPtrHolder<nsICameraGetCameraCallback>(onSuccess))
-    , mOnErrorCb(new nsMainThreadPtrHolder<nsICameraErrorCallback>(onError))
-    , mWindowId(aWindowId)
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  ~InitGonkCameraControl()
-  {
-    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  }
-
-  NS_IMETHOD Run()
-  {
-    nsresult rv = mCameraControl->Init();
-    return mDOMCameraControl->Result(rv, mOnSuccessCb, mOnErrorCb, mWindowId);
-  }
-
-  nsRefPtr<nsGonkCameraControl> mCameraControl;
-  // Raw pointer to DOM-facing camera control--it must NS_ADDREF itself for us
-  nsDOMCameraControl* mDOMCameraControl;
-  nsMainThreadPtrHandle<nsICameraGetCameraCallback> mOnSuccessCb;
-  nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb;
-  uint64_t mWindowId;
-};
-
 // Construct nsGonkCameraControl on the main thread.
-nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
-  : CameraControlImpl(aCameraId, aCameraThread, aWindowId)
-  , mExposureCompensationMin(0.0)
-  , mExposureCompensationStep(0.0)
-  , mDeferConfigUpdate(false)
-  , mWidth(0)
-  , mHeight(0)
-  , mLastPictureWidth(0)
-  , mLastPictureHeight(0)
-  , mLastThumbnailWidth(0)
-  , mLastThumbnailHeight(0)
-#if !FORCE_PREVIEW_FORMAT_YUV420SP
-  , mFormat(PREVIEW_FORMAT_UNKNOWN)
-#else
-  , mFormat(PREVIEW_FORMAT_YUV420SP)
-#endif
-  , mFps(30)
-  , mDiscardedFrameCount(0)
+nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId)
+  : CameraControlImpl(aCameraId)
+  , mLastPictureSize({0, 0})
+  , mLastThumbnailSize({0, 0})
+  , mPreviewFps(30)
+  , mResumePreviewAfterTakingPicture(false) // XXXmikeh - see bug 950102
+  , mDeferConfigUpdate(0)
   , mMediaProfiles(nullptr)
   , mRecorder(nullptr)
   , mProfileManager(nullptr)
   , mRecorderProfile(nullptr)
   , mVideoFile(nullptr)
+  , mReentrantMonitor("GonkCameraControl::OnTakePictureMonitor")
 {
   // Constructor runs on the main thread...
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  mRwLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "GonkCameraControl.Parameters.Lock");
-}
-
-void nsGonkCameraControl::DispatchInit(nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
-{
-  // ...but initialization is carried out on the camera thread.
-  nsCOMPtr<nsIRunnable> init = new InitGonkCameraControl(this, aDOMCameraControl, onSuccess, onError, aWindowId);
-  mCameraThread->Dispatch(init, NS_DISPATCH_NORMAL);
+  mImageContainer = LayerManager::CreateImageContainer();
 }
 
 nsresult
-nsGonkCameraControl::Init()
+nsGonkCameraControl::Init(const Configuration* aInitialConfig)
 {
+  class InitGonkCameraControl : public nsRunnable
+  {
+  public:
+    InitGonkCameraControl(nsGonkCameraControl* aCameraControl,
+                          const Configuration* aConfig)
+      : mCameraControl(aCameraControl)
+      , mHaveInitialConfig(false)
+    {
+      DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+      if (aConfig) {
+        mConfig = *aConfig;
+        mHaveInitialConfig = true;
+      }
+    }
+
+    ~InitGonkCameraControl()
+    {
+      DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+    }
+
+    /**
+     * For initialization, we try to return the camera control to the upper
+     * upper layer (i.e. the DOM) as quickly as possible. To do this, the
+     * camera is initialized in the following stages:
+     *
+     *  0. InitImpl() initializes the hardware;
+     *  1. SetConfigurationInternal() does the minimal configuration
+     *     required so that we can start the preview -and- report a valid
+     *     configuration to the upper layer;
+     *  2. OnHardwareStateChange() reports that the hardware is ready,
+     *     which the upper layer can (and does) use to return the camera
+     *     control object;
+     *  3. StartPreviewImpl() starts the flow of preview frames from the
+     *     camera hardware.
+     *
+     * The intent of the above flow is to let the Main Thread do as much work
+     * up-front as possible without waiting for blocking Camera Thread calls
+     * to complete.
+     */
+    NS_IMETHODIMP
+    Run() MOZ_OVERRIDE
+    {
+      nsresult rv = mCameraControl->InitImpl();
+      if (NS_FAILED(rv)) {
+        mCameraControl->OnError(CameraControlListener::kInGetCamera,
+                                CameraControlListener::kErrorInitFailed);
+        // The hardware failed to initialize, so close it up
+        mCameraControl->ReleaseHardware();
+        return rv;
+      }
+
+      if (mHaveInitialConfig) {
+        rv = mCameraControl->SetConfigurationInternal(mConfig);
+        if (NS_FAILED(rv)) {
+          mCameraControl->OnError(CameraControlListener::kInGetCamera,
+                                  CameraControlListener::kErrorInvalidConfiguration);
+          // The initial configuration failed, close up the hardware
+          mCameraControl->ReleaseHardware();
+          return rv;
+        }
+      }
+
+      mCameraControl->OnHardwareStateChange(CameraControlListener::kHardwareOpen);
+      return mCameraControl->StartPreviewImpl();
+    }
+
+  protected:
+    nsRefPtr<nsGonkCameraControl> mCameraControl;
+    Configuration mConfig;
+    bool mHaveInitialConfig;
+  };
+
+  // Initialization is carried out on the camera thread.
+  return mCameraThread->Dispatch(
+    new InitGonkCameraControl(this, aInitialConfig), NS_DISPATCH_NORMAL);
+}
+
+nsresult
+nsGonkCameraControl::InitImpl()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
+
   mCameraHw = GonkCameraHardware::Connect(this, mCameraId);
   if (!mCameraHw.get()) {
     DOM_CAMERA_LOGE("Failed to connect to camera %d (this=%p)\n", mCameraId, this);
     return NS_ERROR_FAILURE;
   }
 
   DOM_CAMERA_LOGI("Initializing camera %d (this=%p, mCameraHw=%p)\n", mCameraId, this, mCameraHw.get());
 
   // Initialize our camera configuration database.
   PullParametersImpl();
 
-  // Try to set preferred image format and frame rate
-#if !FORCE_PREVIEW_FORMAT_YUV420SP
-  DOM_CAMERA_LOGI("Camera preview formats: %s\n", mParams.get(mParams.KEY_SUPPORTED_PREVIEW_FORMATS));
-  const char* const PREVIEW_FORMAT = "yuv420p";
-  const char* const BAD_PREVIEW_FORMAT = "yuv420sp";
-  mParams.setPreviewFormat(PREVIEW_FORMAT);
-  mParams.setPreviewFrameRate(mFps);
-#else
-  mParams.setPreviewFormat("yuv420sp");
-  mParams.setPreviewFrameRate(mFps);
-#endif
+  // Set preferred preview frame format.
+  mParams.Set(CAMERA_PARAM_PREVIEWFORMAT, NS_LITERAL_STRING("yuv420sp"));
   PushParametersImpl();
 
-  // Check that our settings stuck
-  PullParametersImpl();
-#if !FORCE_PREVIEW_FORMAT_YUV420SP
-  const char* format = mParams.getPreviewFormat();
-  if (strcmp(format, PREVIEW_FORMAT) == 0) {
-    mFormat = PREVIEW_FORMAT_YUV420P;  /* \o/ */
-  } else if (strcmp(format, BAD_PREVIEW_FORMAT) == 0) {
-    mFormat = PREVIEW_FORMAT_YUV420SP;
-    DOM_CAMERA_LOGA("Camera ignored our request for '%s' preview, will have to convert (from %d)\n", PREVIEW_FORMAT, mFormat);
-  } else {
-    mFormat = PREVIEW_FORMAT_UNKNOWN;
-    DOM_CAMERA_LOGE("Camera ignored our request for '%s' preview, returned UNSUPPORTED format '%s'\n", PREVIEW_FORMAT, format);
-  }
-#endif
+  // Grab any other settings we'll need later.
+  mParams.Get(CAMERA_PARAM_PICTURE_FILEFORMAT, mFileFormat);
+  mParams.Get(CAMERA_PARAM_THUMBNAILSIZE, mLastThumbnailSize);
 
-  // Check the frame rate and log if the camera ignored our setting
-  uint32_t fps = mParams.getPreviewFrameRate();
-  if (fps != mFps) {
-    DOM_CAMERA_LOGA("We asked for %d fps but camera returned %d fps, using that", mFps, fps);
-    mFps = fps;
-  }
+  // The emulator's camera returns -1 for these values; bump them up to 0
+  int areas;
+  mParams.Get(CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS, areas);
+  mCurrentConfiguration.mMaxMeteringAreas = areas != -1 ? areas : 0;
+  mParams.Get(CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS, areas);
+  mCurrentConfiguration.mMaxFocusAreas = areas != -1 ? areas : 0;
 
-  // Grab any other settings we'll need later.
-  mExposureCompensationMin = mParams.getFloat(mParams.KEY_MIN_EXPOSURE_COMPENSATION);
-  mExposureCompensationStep = mParams.getFloat(mParams.KEY_EXPOSURE_COMPENSATION_STEP);
-  mMaxMeteringAreas = mParams.getInt(mParams.KEY_MAX_NUM_METERING_AREAS);
-  mMaxFocusAreas = mParams.getInt(mParams.KEY_MAX_NUM_FOCUS_AREAS);
-  mLastThumbnailWidth = mParams.getInt(mParams.KEY_JPEG_THUMBNAIL_WIDTH);
-  mLastThumbnailHeight = mParams.getInt(mParams.KEY_JPEG_THUMBNAIL_HEIGHT);
+  mParams.Get(CAMERA_PARAM_PICTURE_SIZE, mLastPictureSize);
+  mParams.Get(CAMERA_PARAM_PREVIEWSIZE, mCurrentConfiguration.mPreviewSize);
+  mParams.Get(CAMERA_PARAM_VIDEOSIZE, mLastRecorderSize);
 
-  int w;
-  int h;
-  mParams.getPictureSize(&w, &h);
-  MOZ_ASSERT(w > 0 && h > 0); // make sure the driver returns sane values
-  mLastPictureWidth = static_cast<uint32_t>(w);
-  mLastPictureHeight = static_cast<uint32_t>(h);
-
-  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);
-  DOM_CAMERA_LOGI(" - default picture size:          %u x %u\n", mLastPictureWidth, mLastPictureHeight);
-  DOM_CAMERA_LOGI(" - default thumbnail size:        %u x %u\n", mLastThumbnailWidth, mLastThumbnailHeight);
+  DOM_CAMERA_LOGI(" - maximum metering areas:        %u\n", mCurrentConfiguration.mMaxMeteringAreas);
+  DOM_CAMERA_LOGI(" - maximum focus areas:           %u\n", mCurrentConfiguration.mMaxFocusAreas);
+  DOM_CAMERA_LOGI(" - default picture size:          %u x %u\n",
+    mLastPictureSize.width, mLastPictureSize.height);
+  DOM_CAMERA_LOGI(" - default thumbnail size:        %u x %u\n",
+    mLastThumbnailSize.width, mLastThumbnailSize.height);
+  DOM_CAMERA_LOGI(" - default preview size:          %u x %u\n",
+    mCurrentConfiguration.mPreviewSize.width, mCurrentConfiguration.mPreviewSize.height);
+  DOM_CAMERA_LOGI(" - default video recorder size:   %u x %u\n",
+    mLastRecorderSize.width, mLastRecorderSize.height);
+  DOM_CAMERA_LOGI(" - default picture file format:   %s\n",
+    NS_ConvertUTF16toUTF8(mFileFormat).get());
 
   return NS_OK;
 }
 
 nsGonkCameraControl::~nsGonkCameraControl()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p, mCameraHw = %p\n", __func__, __LINE__, this, mCameraHw.get());
 
-  ReleaseHardwareImpl(nullptr);
-  if (mRwLock) {
-    PRRWLock* lock = mRwLock;
-    mRwLock = nullptr;
-    PR_DestroyRWLock(lock);
-  }
-
+  ReleaseHardwareImpl();
   DOM_CAMERA_LOGT("%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
+nsresult
+nsGonkCameraControl::SetConfigurationInternal(const Configuration& aConfig)
 {
-public:
-  RwAutoLockWrite(PRRWLock* aRwLock)
-    : mRwLock(aRwLock)
-  {
-    PR_RWLock_Wlock(mRwLock);
-  }
-
-  ~RwAutoLockWrite()
-  {
-    PR_RWLock_Unlock(mRwLock);
-  }
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 
-protected:
-  PRRWLock* mRwLock;
-};
-
-const char*
-nsGonkCameraControl::GetParameter(const char* aKey)
-{
-  RwAutoLockRead lock(mRwLock);
-  return mParams.get(aKey);
-}
-
-const char*
-nsGonkCameraControl::GetParameterConstChar(uint32_t aKey)
-{
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    return nullptr;
-  }
+  nsresult rv;
 
-  RwAutoLockRead lock(mRwLock);
-  return mParams.get(key);
-}
-
-double
-nsGonkCameraControl::GetParameterDouble(uint32_t 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;
+  switch (aConfig.mMode) {
+    case kPictureMode:
+      rv = SetPictureConfiguration(aConfig);
+      break;
 
-    /**
-     * 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;
+    case kVideoMode:
+      rv = SetVideoConfiguration(aConfig);
+      break;
 
     default:
-      return mParams.getFloat(key);
-  }
-}
-
-int32_t
-nsGonkCameraControl::GetParameterInt32(uint32_t aKey)
-{
-  if (aKey == CAMERA_PARAM_SENSORANGLE) {
-    if (!mCameraHw.get()) {
-      return 0;
-    }
-    return mCameraHw->GetSensorOrientation();
+      MOZ_ASSUME_UNREACHABLE("Unanticipated camera mode in SetConfigurationInternal()");
   }
 
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    return 0;
-  }
-
-  RwAutoLockRead lock(mRwLock);
-  return mParams.getInt(key);
-}
-
-void
-nsGonkCameraControl::GetParameter(uint32_t aKey,
-                                  nsTArray<idl::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;
-  uint32_t count = 1;
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  // count the number of regions in the string
-  while ((p = strstr(p, "),("))) {
-    ++count;
-    p += 3;
-  }
-
-  aRegions.SetCapacity(count);
-  idl::CameraRegion* r;
-
-  // parse all of the region sets
-  uint32_t i;
-  for (i = 0, p = value; p && i < count; ++i, p = strchr(p + 1, '(')) {
-    r = aRegions.AppendElement();
-    if (sscanf(p, "(%d,%d,%d,%d,%u)", &r->top, &r->left, &r->bottom, &r->right, &r->weight) != 5) {
-      DOM_CAMERA_LOGE("%s:%d : region tuple has bad format: '%s'\n", __func__, __LINE__, p);
-      aRegions.Clear();
-      return;
-    }
-  }
-
-  return;
-}
-
-void
-nsGonkCameraControl::GetParameter(uint32_t aKey,
-                                  nsTArray<idl::CameraSize>& aSizes)
-{
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    return;
+  mCurrentConfiguration.mMode = aConfig.mMode;
+  mCurrentConfiguration.mRecorderProfile = aConfig.mRecorderProfile;
+  if (aConfig.mMode == kVideoMode) {
+    mCurrentConfiguration.mPreviewSize = mLastRecorderSize;
   }
 
-  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;
-  idl::CameraSize* s;
-
-  // The 'value' string is in the format "w1xh1,w2xh2,w3xh3,..."
-  while (p) {
-    s = aSizes.AppendElement();
-    if (sscanf(p, "%dx%d", &s->width, &s->height) != 2) {
-      DOM_CAMERA_LOGE("%s:%d : size tuple has bad format: '%s'\n", __func__, __LINE__, p);
-      aSizes.Clear();
-      return;
-    }
-    // Look for the next record...
-    p = strchr(p, ',');
-    if (p) {
-      // ...skip the comma too
-      ++p;
-    }
-  }
-
-  return;
-}
-
-void
-nsGonkCameraControl::GetParameter(uint32_t aKey, idl::CameraSize& aSize)
-{
-  if (aKey == CAMERA_PARAM_THUMBNAILSIZE) {
-    // This is a special case--for some reason the thumbnail size
-    // is accessed as two separate values instead of a tuple.
-    RwAutoLockRead lock(mRwLock);
-
-    aSize.width = mParams.getInt(CameraParameters::KEY_JPEG_THUMBNAIL_WIDTH);
-    aSize.height = mParams.getInt(CameraParameters::KEY_JPEG_THUMBNAIL_HEIGHT);
-    DOM_CAMERA_LOGI("thumbnail size --> value='%ux%u'\n", aSize.width, aSize.height);
-    return;
-  }
-
-  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;
-  }
-
-  if (sscanf(value, "%ux%u", &aSize.width, &aSize.height) != 2) {
-    DOM_CAMERA_LOGE("%s:%d : size tuple has bad format: '%s'\n", __func__, __LINE__, value);
-    aSize.width = 0;
-    aSize.height = 0;
-  }
+  OnConfigurationChange();
+  return NS_OK;
 }
 
 nsresult
+nsGonkCameraControl::SetConfigurationImpl(const Configuration& aConfig)
+{
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
+
+  // Stop any currently running preview
+  StopPreviewImpl();
+
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  nsresult rv = SetConfigurationInternal(aConfig);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Restart the preview
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  return StartPreviewImpl();
+}
+
+nsresult
+nsGonkCameraControl::SetPictureConfiguration(const Configuration& aConfig)
+{
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+
+  // remove any existing recorder profile
+  mRecorderProfile = nullptr;
+
+  nsresult rv = SetPreviewSize(aConfig.mPreviewSize);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mParams.Get(CAMERA_PARAM_PREVIEWFRAMERATE, mPreviewFps);
+
+  DOM_CAMERA_LOGI("picture mode preview: wanted %ux%u, got %ux%u (%u fps)\n",
+    aConfig.mPreviewSize.width, aConfig.mPreviewSize.height,
+    mCurrentConfiguration.mPreviewSize.width, mCurrentConfiguration.mPreviewSize.height,
+    mPreviewFps);
+
+  return NS_OK;
+}
+
+nsresult
+nsGonkCameraControl::SetVideoConfiguration(const Configuration& aConfig)
+{
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+
+  nsresult rv = SetupVideoMode(aConfig.mRecorderProfile);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  DOM_CAMERA_LOGI("video mode preview: profile '%s', got %ux%u (%u fps)\n",
+    NS_ConvertUTF16toUTF8(aConfig.mRecorderProfile).get(),
+    mLastRecorderSize.width, mLastRecorderSize.height,
+    mPreviewFps);
+
+  return rv;
+}
+
+// Parameter management.
+nsresult
 nsGonkCameraControl::PushParameters()
 {
-  if (mDeferConfigUpdate) {
-    DOM_CAMERA_LOGT("%s:%d - defering config update\n", __func__, __LINE__);
+  uint32_t dcu = mDeferConfigUpdate;
+  if (dcu > 0) {
+    DOM_CAMERA_LOGI("Defering config update (nest level %u)\n", dcu);
     return NS_OK;
   }
 
   /**
    * 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()) {
-    DOM_CAMERA_LOGT("%s:%d - dispatching to camera thread\n", __func__, __LINE__);
-    nsCOMPtr<nsIRunnable> pushParametersTask = NS_NewRunnableMethod(this, &nsGonkCameraControl::PushParametersImpl);
+  if (NS_GetCurrentThread() != mCameraThread) {
+    DOM_CAMERA_LOGT("%s:%d - dispatching to Camera Thread\n", __func__, __LINE__);
+    nsCOMPtr<nsIRunnable> pushParametersTask =
+      NS_NewRunnableMethod(this, &nsGonkCameraControl::PushParametersImpl);
     return mCameraThread->Dispatch(pushParametersTask, NS_DISPATCH_NORMAL);
   }
 
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
   return PushParametersImpl();
 }
 
 void
-nsGonkCameraControl::SetParameter(const char* aKey, const char* aValue)
+nsGonkCameraControl::BeginBatchParameterSet()
 {
-  {
-    RwAutoLockWrite lock(mRwLock);
-    mParams.set(aKey, aValue);
-  }
-  PushParameters();
-}
-
-void
-nsGonkCameraControl::SetParameter(uint32_t aKey, const char* aValue)
-{
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    return;
-  }
-
-  {
-    RwAutoLockWrite lock(mRwLock);
-    mParams.set(key, aValue);
+  uint32_t dcu = ++mDeferConfigUpdate;
+  if (dcu == 0) {
+    NS_WARNING("Overflow weirdness incrementing mDeferConfigUpdate!");
+    MOZ_CRASH();
   }
-  PushParameters();
-}
-
-void
-nsGonkCameraControl::SetParameter(uint32_t aKey, double aValue)
-{
-  uint32_t 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();
+  DOM_CAMERA_LOGI("Begin deferring camera configuration updates (nest level %u)\n", dcu);
 }
 
 void
-nsGonkCameraControl::SetParameter(uint32_t aKey,
-                                  const nsTArray<idl::CameraRegion>& aRegions)
+nsGonkCameraControl::EndBatchParameterSet()
 {
-  const char* key = getKeyText(aKey);
-  if (!key) {
-    return;
+  uint32_t dcu = mDeferConfigUpdate--;
+  if (dcu == 0) {
+    NS_WARNING("Underflow badness decrementing mDeferConfigUpdate!");
+    MOZ_CRASH();
   }
+  DOM_CAMERA_LOGI("End deferring camera configuration updates (nest level %u)\n", dcu);
 
-  uint32_t length = aRegions.Length();
+  if (dcu == 1) {
+    PushParameters();
+  }
+}
 
-  if (!length) {
-    // This tells the camera driver to revert to automatic regioning.
-    {
-      RwAutoLockWrite lock(mRwLock);
-      mParams.set(key, "(0,0,0,0,0)");
+template<class T> nsresult
+nsGonkCameraControl::SetAndPush(uint32_t aKey, const T& aValue)
+{
+  nsresult rv = mParams.Set(aKey, aValue);
+  if (NS_FAILED(rv)) {
+    DOM_CAMERA_LOGE("Camera parameter aKey=%d failed to set (0x%x)\n", aKey, rv);
+    return rv;
+  }
+  return PushParameters();
+}
+
+// Array-of-Size parameter accessor.
+nsresult
+nsGonkCameraControl::Get(uint32_t aKey, nsTArray<Size>& aSizes)
+{
+  if (aKey == CAMERA_PARAM_SUPPORTED_VIDEOSIZES) {
+    nsresult rv = mParams.Get(aKey, aSizes);
+    if (aSizes.Length() != 0) {
+      return rv;
     }
-    PushParameters();
-    return;
-  }
-
-  nsCString s;
-
-  for (uint32_t i = 0; i < length; ++i) {
-    const idl::CameraRegion* r = &aRegions[i];
-    s.AppendPrintf("(%d,%d,%d,%d,%d),", r->top, r->left, r->bottom, r->righ