Merge m-c to inbound a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Tue, 10 Mar 2015 16:11:23 -0700
changeset 232881 fd8e079d63359725bae730fec4fbe433d3995048
parent 232880 2fa9d9762eecd0a2629a686cb9b61f23bf94cd54 (current diff)
parent 232864 a9aff724afc7861ebbbc85db7b9fce61b40f2a08 (diff)
child 232901 052ffb4ebc6dc0df887eff536209a979b2ff4f4a
child 232902 9e174f63f46fa82926faedb033dba98fe55028a3
push id28394
push userkwierso@gmail.com
push dateWed, 11 Mar 2015 01:24:00 +0000
treeherdermozilla-central@fd8e079d6335 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone39.0a1
first release with
nightly linux32
fd8e079d6335 / 39.0a1 / 20150311030203 / files
nightly linux64
fd8e079d6335 / 39.0a1 / 20150311030203 / files
nightly mac
fd8e079d6335 / 39.0a1 / 20150311030203 / files
nightly win32
fd8e079d6335 / 39.0a1 / 20150311030203 / files
nightly win64
fd8e079d6335 / 39.0a1 / 20150311030203 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound a=merge CLOSED TREE
toolkit/mozapps/update/tests/unit_aus_update/updateRootDirMigration_win.js
toolkit/themes/shared/in-content/sorter.png
toolkit/themes/shared/in-content/sorter@2x.png
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f6a1fcd30ee6a286f3bca9d0c3cb600e21bfbf69"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="943c8b4039f59b08ba100390e164a076a20c892e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c82a532ee1f14b9733214022b1e2d55a0b030be8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="ddf33f81e9a60f8110fcfd6b51b5dff2db676183"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <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="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <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="f6a1fcd30ee6a286f3bca9d0c3cb600e21bfbf69"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="943c8b4039f59b08ba100390e164a076a20c892e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c82a532ee1f14b9733214022b1e2d55a0b030be8"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="97c3d9b8b87774ca7a08c89145e95b55652459ef"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="ddf33f81e9a60f8110fcfd6b51b5dff2db676183"/>
   <!-- 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
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f6a1fcd30ee6a286f3bca9d0c3cb600e21bfbf69"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="943c8b4039f59b08ba100390e164a076a20c892e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c82a532ee1f14b9733214022b1e2d55a0b030be8"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="ddf33f81e9a60f8110fcfd6b51b5dff2db676183"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- 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"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f6a1fcd30ee6a286f3bca9d0c3cb600e21bfbf69"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="943c8b4039f59b08ba100390e164a076a20c892e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c82a532ee1f14b9733214022b1e2d55a0b030be8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="ddf33f81e9a60f8110fcfd6b51b5dff2db676183"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="52775e03a2d8532429dff579cb2cd56718e488c3">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f6a1fcd30ee6a286f3bca9d0c3cb600e21bfbf69"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="943c8b4039f59b08ba100390e164a076a20c892e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c82a532ee1f14b9733214022b1e2d55a0b030be8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="ddf33f81e9a60f8110fcfd6b51b5dff2db676183"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <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="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <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="f6a1fcd30ee6a286f3bca9d0c3cb600e21bfbf69"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="943c8b4039f59b08ba100390e164a076a20c892e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c82a532ee1f14b9733214022b1e2d55a0b030be8"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="97c3d9b8b87774ca7a08c89145e95b55652459ef"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="ddf33f81e9a60f8110fcfd6b51b5dff2db676183"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f6a1fcd30ee6a286f3bca9d0c3cb600e21bfbf69"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="943c8b4039f59b08ba100390e164a076a20c892e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c82a532ee1f14b9733214022b1e2d55a0b030be8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="ddf33f81e9a60f8110fcfd6b51b5dff2db676183"/>
@@ -120,17 +120,17 @@
   <project name="platform/hardware/libhardware_legacy" path="hardware/libhardware_legacy" revision="76c4bf4bc430a1b8317f2f21ef735867733e50cc"/>
   <project name="platform/system/media" path="system/media" revision="c1332c21c608f4932a6d7e83450411cde53315ef"/>
   <!--original fetch url was git://github.com/t2m-foxfone/-->
   <remote fetch="https://git.mozilla.org/external/t2m-foxfone" name="t2m"/>
   <default remote="caf" revision="LNX.LA.3.5.2.1.1" sync-j="4"/>
   <!-- Flame specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="1bb28abbc215f45220620af5cd60a8ac1be93722"/>
   <project name="device/qcom/common" path="device/qcom/common" revision="2501e5940ba69ece7654ff85611c76ae5bda299c"/>
-  <project name="device-flame" path="device/t2m/flame" remote="b2g" revision="e7c90613521145db090dd24147afd5ceb5703190"/>
+  <project name="device-flame" path="device/t2m/flame" remote="b2g" revision="ebad7da532429a6f5efadc00bf6ad8a41288a429"/>
   <project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="0865bc4134b67220df4058625fba29305d6b10c3"/>
   <project name="kernel_lk" path="bootable/bootloader/lk" remote="b2g" revision="fda40423ffa573dc6cafd3780515010cb2a086be"/>
   <project name="platform_bootable_recovery" path="bootable/recovery" remote="b2g" revision="26e78a979f3090dc196219e268467620b6c40ec5"/>
   <project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="d61fc97258c8b0c362430dd2eb195dcc4d266f14"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="5b71e40213f650459e95d35b6f14af7e88d8ab62"/>
   <project name="platform_external_libnfc-nci" path="external/libnfc-nci" remote="t2m" revision="4186bdecb4dae911b39a8202252cc2310d91b0be"/>
   <project name="platform_external_libnfc-pn547" path="external/libnfc-pn547" remote="b2g" revision="5bb999b84b8adc14f6bea004d523ba258dea8188"/>
   <project name="platform/frameworks/av" path="frameworks/av" revision="65f5144987afff35a932262c0c5fad6ecce0c04a"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f6a1fcd30ee6a286f3bca9d0c3cb600e21bfbf69"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="943c8b4039f59b08ba100390e164a076a20c892e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c82a532ee1f14b9733214022b1e2d55a0b030be8"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="ddf33f81e9a60f8110fcfd6b51b5dff2db676183"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
@@ -116,17 +116,17 @@
   <project name="platform/system/security" path="system/security" revision="360f51f7af191316cd739f229db1c5f7233be063"/>
   <project name="platform/system/vold" path="system/vold" revision="153df4d067a4149c7d78f1c92fed2ce2bd6a272e"/>
   <!--original fetch url was git://github.com/t2m-foxfone/-->
   <remote fetch="https://git.mozilla.org/external/t2m-foxfone" name="t2m"/>
   <default remote="caf" revision="jb_3.2" sync-j="4"/>
   <!-- Flame specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="e8a318f7690092e639ba88891606f4183e846d3f"/>
   <project name="device/qcom/common" path="device/qcom/common" revision="878804e0becfe5635bb8ccbf2671333d546c6fb6"/>
-  <project name="device-flame" path="device/t2m/flame" remote="b2g" revision="c76af2b3c4acb7062bc928169cd303451b88092e"/>
+  <project name="device-flame" path="device/t2m/flame" remote="b2g" revision="f54de260816110dca21bc9e76e4c4a09a950f232"/>
   <project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="49417cfc622074daa3c76b345a199f6731375800"/>
   <project name="kernel_lk" path="bootable/bootloader/lk" remote="b2g" revision="6f00133ac0f47e90027bd7e263a16b405bfac503"/>
   <project name="platform_bootable_recovery" path="bootable/recovery" remote="b2g" revision="e81502511cda303c803e63f049574634bc96f9f2"/>
   <project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="81c4a859d75d413ad688067829d21b7ba9205f81"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="f0689ac1914cdbc59e53bdc9edd9013dc157c299"/>
   <project name="platform/external/bluetooth/glib" path="external/bluetooth/glib" revision="dd925f76e4f149c3d5571b80e12f7e24bbe89c59"/>
   <project name="platform/external/dbus" path="external/dbus" revision="ea87119c843116340f5df1d94eaf8275e1055ae8"/>
   <project name="platform_external_libnfc-nci" path="external/libnfc-nci" remote="t2m" revision="4186bdecb4dae911b39a8202252cc2310d91b0be"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "f6a1fcd30ee6a286f3bca9d0c3cb600e21bfbf69", 
+        "git_revision": "943c8b4039f59b08ba100390e164a076a20c892e", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "96b7e916f53ebcd185b087338913edb5ffdadade", 
+    "revision": "c8f9d4bbb0ab4ddfb272c0ee955e640b9d122b54", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f6a1fcd30ee6a286f3bca9d0c3cb600e21bfbf69"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="943c8b4039f59b08ba100390e164a076a20c892e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c82a532ee1f14b9733214022b1e2d55a0b030be8"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="ddf33f81e9a60f8110fcfd6b51b5dff2db676183"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- 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"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="52775e03a2d8532429dff579cb2cd56718e488c3">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f6a1fcd30ee6a286f3bca9d0c3cb600e21bfbf69"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="943c8b4039f59b08ba100390e164a076a20c892e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c82a532ee1f14b9733214022b1e2d55a0b030be8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="ddf33f81e9a60f8110fcfd6b51b5dff2db676183"/>
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -180,16 +180,19 @@
 #ifdef MOZ_B2G_RIL
 @RESPATH@/components/dom_icc.xpt
 @RESPATH@/components/dom_wappush.xpt
 @RESPATH@/components/dom_mobileconnection.xpt
 #endif
 #ifdef MOZ_B2G_BT
 @RESPATH@/components/dom_bluetooth.xpt
 #endif
+#ifdef MOZ_B2G_CAMERA
+@BINPATH@/components/dom_camera.xpt
+#endif
 @RESPATH@/components/dom_canvas.xpt
 @RESPATH@/components/dom_contacts.xpt
 @RESPATH@/components/dom_alarm.xpt
 @RESPATH@/components/dom_core.xpt
 @RESPATH@/components/dom_css.xpt
 @RESPATH@/components/dom_devicestorage.xpt
 @RESPATH@/components/dom_events.xpt
 @RESPATH@/components/dom_geolocation.xpt
@@ -451,16 +454,22 @@
 @RESPATH@/components/NetworkStatsServiceProxy.js
 @RESPATH@/components/NetworkStatsServiceProxy.manifest
 @RESPATH@/components/TetheringService.js
 @RESPATH@/components/TetheringService.manifest
 @RESPATH@/components/WifiWorker.js
 @RESPATH@/components/WifiWorker.manifest
 #endif // MOZ_WIDGET_GONK
 
+; Camera
+#ifdef MOZ_B2G_CAMERA
+@BINPATH@/components/CameraTestHardware.js
+@BINPATH@/components/CameraTestHardware.manifest
+#endif // MOZ_B2G_CAMERA
+
 ; Tethering
 #ifdef MOZ_WIDGET_GONK
 @RESPATH@/components/TetheringManager.js
 @RESPATH@/components/TetheringManager.manifest
 #endif
 
 ; ResourceStats
 #ifdef MOZ_WIDGET_GONK
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -207,16 +207,17 @@
                  label="&devAppMgrMenu.label;"
                  command="Tools:DevAppMgr"/>
     <broadcaster id="devtoolsMenuBroadcaster_webide"
                  label="&webide.label;"
                  command="Tools:WebIDE"
                  key="key_webide"/>
     <broadcaster id="devtoolsMenuBroadcaster_BrowserToolbox"
                  label="&browserToolboxMenu.label;"
+                 key="key_browserToolbox"
                  command="Tools:BrowserToolbox"/>
     <broadcaster id="devtoolsMenuBroadcaster_BrowserContentToolbox"
                  label="&browserContentToolboxMenu.label;"
                  command="Tools:BrowserContentToolbox"/>
     <broadcaster id="devtoolsMenuBroadcaster_BrowserConsole"
                  label="&browserConsoleCmd.label;"
                  key="key_browserConsole"
                  command="Tools:BrowserConsole"/>
@@ -289,16 +290,17 @@
     <key id="key_search2" key="&searchFocusUnix.commandkey;" command="Tools:Search" modifiers="accel"/>
     <key id="key_openDownloads" key="&downloadsUnix.commandkey;" command="Tools:Downloads" modifiers="accel,shift"/>
 #else
     <key id="key_openDownloads" key="&downloads.commandkey;" command="Tools:Downloads" modifiers="accel"/>
 #endif
     <key id="key_openAddons" key="&addons.commandkey;" command="Tools:Addons" modifiers="accel,shift"/>
     <key id="key_devToolboxMenuItemF12" keycode="&devToolsCmd.keycode;" keytext="&devToolsCmd.keytext;" command="Tools:DevToolbox"/>
     <key id="key_browserConsole" key="&browserConsoleCmd.commandkey;" command="Tools:BrowserConsole" modifiers="accel,shift"/>
+    <key id="key_browserToolbox" key="&browserToolboxCmd.commandkey;" command="Tools:BrowserToolbox" modifiers="accel,alt,shift"/>
     <key id="key_devToolbar" keycode="&devToolbar.keycode;" modifiers="shift"
          keytext="&devToolbar.keytext;" command="Tools:DevToolbarFocus"/>
     <key id="key_responsiveUI" key="&responsiveDesignTool.commandkey;" command="Tools:ResponsiveUI"
 #ifdef XP_MACOSX
         modifiers="accel,alt"
 #else
         modifiers="accel,shift"
 #endif
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3485,16 +3485,22 @@ const BrowserSearch = {
    */
   loadSearchFromContext: function (terms) {
     let engine = BrowserSearch._loadSearch(terms, true, "contextmenu");
     if (engine) {
       BrowserSearch.recordSearchInHealthReport(engine, "contextmenu");
     }
   },
 
+  pasteAndSearch: function (event) {
+    BrowserSearch.searchBar.select();
+    goDoCommand("cmd_paste");
+    BrowserSearch.searchBar.handleSearchCommand(event);
+  },
+
   /**
    * Returns the search bar element if it is present in the toolbar, null otherwise.
    */
   get searchBar() {
     return document.getElementById("searchbar");
   },
 
   loadAddEngines: function BrowserSearch_loadAddEngines() {
--- a/browser/base/content/socialmarks.xml
+++ b/browser/base/content/socialmarks.xml
@@ -124,17 +124,16 @@
 
         this.content.setAttribute("origin", provider.origin);
 
         let panel = this.panel;
         // if customization is currently happening, we may not have a panel
         // that we can hide
         if (panel.hidePopup) {
           panel.hidePopup();
-          panel.hidden = true;
         }
         this.pageData = null;
         ]]></body>
       </method>
 
       <method name="loadPanel">
         <parameter name="pageData"/>
         <parameter name="target"/>
--- a/browser/components/loop/MozLoopAPI.jsm
+++ b/browser/components/loop/MozLoopAPI.jsm
@@ -287,16 +287,25 @@ function injectLoopAPI(targetWindow) {
         if (!win || !browser) {
           // This may happen when an undocked conversation window is the only
           // window left.
           let err = new Error("No tabs available to share.");
           MozLoopService.log.error(err);
           listener(cloneValueInto(err, targetWindow));
           return;
         }
+        if (browser.getAttribute("remote") == "true") {
+          // Tab sharing is not supported yet for e10s-enabled browsers. This will
+          // be fixed in bug 1137634.
+          let err = new Error("Tab sharing is not supported for e10s-enabled browsers");
+          MozLoopService.log.error(err);
+          listener(cloneValueInto(err, targetWindow));
+          return;
+        }
+
         win.LoopUI.addBrowserSharingListener(listener);
 
         savedWindowListeners.set(listener, Cu.getWeakReference(win));
       }
     },
 
     /**
      * Removes a listener that was previously added.
--- a/browser/components/loop/content/shared/js/activeRoomStore.js
+++ b/browser/components/loop/content/shared/js/activeRoomStore.js
@@ -416,16 +416,19 @@ loop.store.ActiveRoomStore = (function()
      * Handles switching browser (aka tab) sharing to a new window. Should
      * only be used for browser sharing.
      *
      * @param {Number} windowId  The new windowId to start sharing.
      */
     _handleSwitchBrowserShare: function(err, windowId) {
       if (err) {
         console.error("Error getting the windowId: " + err);
+        this.dispatchAction(new sharedActions.ScreenSharingState({
+          state: SCREEN_SHARE_STATES.INACTIVE
+        }));
         return;
       }
 
       var screenSharingState = this.getStoreState().screenSharingState;
 
       if (screenSharingState === SCREEN_SHARE_STATES.INACTIVE) {
         // Screen sharing is still pending, so assume that we need to kick it off.
         var options = {
--- a/browser/components/loop/test/shared/activeRoomStore_test.js
+++ b/browser/components/loop/test/shared/activeRoomStore_test.js
@@ -826,16 +826,28 @@ describe("loop.store.ActiveRoomStore", f
     });
 
     it("should update the SDK driver when a new window id is received", function() {
       listener(null, 72);
 
       sinon.assert.calledOnce(fakeSdkDriver.switchAcquiredWindow);
       sinon.assert.calledWithExactly(fakeSdkDriver.switchAcquiredWindow, 72);
     });
+
+    it("should end the screen sharing session when the listener receives an error", function() {
+      listener(new Error("foo"));
+
+      // The dispatcher was already called once in beforeEach().
+      sinon.assert.calledTwice(dispatcher.dispatch);
+      sinon.assert.calledWith(dispatcher.dispatch,
+        new sharedActions.ScreenSharingState({
+          state: SCREEN_SHARE_STATES.INACTIVE
+        }));
+      sinon.assert.notCalled(fakeSdkDriver.switchAcquiredWindow);
+    });
   });
 
   describe("#endScreenShare", function() {
     it("should set the state to 'inactive'", function() {
       store.endScreenShare();
 
       sinon.assert.calledOnce(dispatcher.dispatch);
       sinon.assert.calledWith(dispatcher.dispatch,
--- a/browser/components/migration/FirefoxProfileMigrator.js
+++ b/browser/components/migration/FirefoxProfileMigrator.js
@@ -22,18 +22,18 @@ Cu.import("resource://gre/modules/Servic
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
                                   "resource://gre/modules/PlacesBackups.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionMigration",
                                   "resource:///modules/sessionstore/SessionMigration.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ProfileTimesAccessor",
-                                  "resource://gre/modules/services/healthreport/profile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ProfileAge",
+                                  "resource://gre/modules/ProfileAge.jsm");
 
 
 function FirefoxProfileMigrator() {
   this.wrappedJSObject = this; // for testing...
 }
 
 FirefoxProfileMigrator.prototype = Object.create(MigratorPrototype);
 
@@ -166,17 +166,17 @@ FirefoxProfileMigrator.prototype._getRes
     name: "times", // name is used only by tests.
     type: types.OTHERDATA,
     migrate: aCallback => {
       let file = this._getFileObject(sourceProfileDir, "times.json");
       if (file) {
         file.copyTo(currentProfileDir, "");
       }
       // And record the fact a migration (ie, a reset) happened.
-      let timesAccessor = new ProfileTimesAccessor(currentProfileDir.path);
+      let timesAccessor = new ProfileAge(currentProfileDir.path);
       timesAccessor.recordProfileReset().then(
         () => aCallback(true),
         () => aCallback(false)
       );
     }
   };
   let healthReporter = {
     name: "healthreporter", // name is used only by tests...
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -80,20 +80,16 @@ function init_all() {
   let helpCmd = document.getElementById("help-button");
   helpCmd.addEventListener("command", helpButtonCommand);
 
   // Wait until initialization of all preferences are complete before
   // notifying observers that the UI is now ready.
   Services.obs.notifyObservers(window, "advanced-pane-loaded", null);
 }
 
-window.addEventListener("unload", function onUnload() {
-  gSubDialog.uninit();
-});
-
 // Make the space above the categories list shrink on low window heights
 function init_dynamic_padding() {
   let categories = document.getElementById("categories");
   let catPadding = Number.parseInt(getComputedStyle(categories)
                                      .getPropertyValue('padding-top'));
   let fullHeight = categories.lastElementChild.getBoundingClientRect().bottom;
   let mediaRule = `
   @media (max-height: ${fullHeight}px) {
--- a/browser/components/preferences/in-content/subdialogs.js
+++ b/browser/components/preferences/in-content/subdialogs.js
@@ -1,55 +1,31 @@
 /* - 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/. */
 
 "use strict";
 
 let gSubDialog = {
   _closingCallback: null,
+  _closingEvent: null,
+  _isClosing: false,
   _frame: null,
   _overlay: null,
   _box: null,
   _injectedStyleSheets: ["chrome://mozapps/content/preferences/preferences.css",
                          "chrome://browser/skin/preferences/preferences.css",
                          "chrome://global/skin/in-content/common.css",
                          "chrome://browser/skin/preferences/in-content/preferences.css",
                          "chrome://browser/skin/preferences/in-content/dialog.css"],
 
   init: function() {
     this._frame = document.getElementById("dialogFrame");
     this._overlay = document.getElementById("dialogOverlay");
     this._box = document.getElementById("dialogBox");
-
-    // Make the close button work.
-    let dialogClose = document.getElementById("dialogClose");
-    dialogClose.addEventListener("command", this.close.bind(this));
-
-    // DOMTitleChanged isn't fired on the frame, only on the chromeEventHandler
-    let chromeBrowser = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                              .getInterface(Ci.nsIWebNavigation)
-                              .QueryInterface(Ci.nsIDocShell)
-                              .chromeEventHandler;
-    chromeBrowser.addEventListener("DOMTitleChanged", this.updateTitle, true);
-
-    // Similarly DOMFrameContentLoaded only fires on the top window
-    window.addEventListener("DOMFrameContentLoaded", this._onContentLoaded.bind(this), true);
-
-    // Wait for the stylesheets injected during DOMContentLoaded to load before showing the dialog
-    // otherwise there is a flicker of the stylesheet applying.
-    this._frame.addEventListener("load", this._onLoad.bind(this));
-  },
-
-  uninit: function() {
-    let chromeBrowser = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                              .getInterface(Ci.nsIWebNavigation)
-                              .QueryInterface(Ci.nsIDocShell)
-                              .chromeEventHandler;
-    chromeBrowser.removeEventListener("DOMTitleChanged", gSubDialog.updateTitle, true);
   },
 
   updateTitle: function(aEvent) {
     if (aEvent.target != gSubDialog._frame.contentDocument)
       return;
     document.getElementById("dialogTitle").textContent = gSubDialog._frame.contentDocument.title;
   },
 
@@ -58,90 +34,134 @@ let gSubDialog = {
       'xml-stylesheet',
       'href="' + aStylesheetURL + '" type="text/css"'
     );
     this._frame.contentDocument.insertBefore(contentStylesheet,
                                              this._frame.contentDocument.documentElement);
   },
 
   open: function(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
+    this._addDialogEventListeners();
+
     let features = (!!aFeatures ? aFeatures + "," : "") + "resizable,dialog=no,centerscreen";
     let dialog = window.openDialog(aURL, "dialogFrame", features, aParams);
     if (aClosingCallback) {
       this._closingCallback = aClosingCallback.bind(dialog);
     }
+
+    this._closingEvent = null;
+    this._isClosing = false;
+
     features = features.replace(/,/g, "&");
     let featureParams = new URLSearchParams(features.toLowerCase());
     this._box.setAttribute("resizable", featureParams.has("resizable") &&
                                         featureParams.get("resizable") != "no" &&
                                         featureParams.get("resizable") != "0");
     return dialog;
   },
 
   close: function(aEvent = null) {
+    if (this._isClosing) {
+      return;
+    }
+    this._isClosing = true;
+
     if (this._closingCallback) {
       try {
         this._closingCallback.call(null, aEvent);
       } catch (ex) {
         Cu.reportError(ex);
       }
       this._closingCallback = null;
     }
 
+    this._removeDialogEventListeners();
+
     this._overlay.style.visibility = "";
     // Clear the sizing inline styles.
     this._frame.removeAttribute("style");
     // Clear the sizing attributes
     this._box.removeAttribute("width");
     this._box.removeAttribute("height");
     this._box.style.removeProperty("min-height");
     this._box.style.removeProperty("min-width");
 
     setTimeout(() => {
       // Unload the dialog after the event listeners run so that the load of about:blank isn't
       // cancelled by the ESC <key>.
       this._frame.loadURI("about:blank");
     }, 0);
   },
 
+  handleEvent: function(aEvent) {
+    switch (aEvent.type) {
+      case "command":
+        this.close(aEvent);
+        break;
+      case "dialogclosing":
+        this._onDialogClosing(aEvent);
+        break;
+      case "DOMTitleChanged":
+        this.updateTitle(aEvent);
+        break;
+      case "DOMFrameContentLoaded":
+        this._onContentLoaded(aEvent);
+        break;
+      case "load":
+        this._onLoad(aEvent);
+        break;
+      case "unload":
+        this._onUnload(aEvent);
+        break;
+    }
+  },
+
   /* Private methods */
 
+  _onUnload: function(aEvent) {
+    if (aEvent.target.location.href != "about:blank") {
+      this.close(this._closingEvent);
+    }
+  },
+
   _onContentLoaded: function(aEvent) {
     if (aEvent.target != this._frame || aEvent.target.contentWindow.location == "about:blank")
       return;
 
     for (let styleSheetURL of this._injectedStyleSheets) {
       this.injectXMLStylesheet(styleSheetURL);
     }
 
     // Provide the ability for the dialog to know that it is being loaded "in-content".
     this._frame.contentDocument.documentElement.setAttribute("subdialog", "true");
 
+    this._frame.contentWindow.addEventListener("dialogclosing", this);
+
     // Make window.close calls work like dialog closing.
     let oldClose = this._frame.contentWindow.close;
     this._frame.contentWindow.close = function() {
-      var closingEvent = new CustomEvent("dialogclosing", {
-        bubbles: true,
-        detail: { button: null },
-      });
-      gSubDialog._frame.contentWindow.dispatchEvent(closingEvent);
+      var closingEvent = gSubDialog._closingEvent;
+      if (!closingEvent) {
+        closingEvent = new CustomEvent("dialogclosing", {
+          bubbles: true,
+          detail: { button: null },
+        });
 
+        gSubDialog._frame.contentWindow.dispatchEvent(closingEvent);
+      }
+
+      gSubDialog.close(closingEvent);
       oldClose.call(gSubDialog._frame.contentWindow);
     };
 
     // XXX: Hack to make focus during the dialog's load functions work. Make the element visible
     // sooner in DOMContentLoaded but mostly invisible instead of changing visibility just before
     // the dialog's load event.
     this._overlay.style.visibility = "visible";
     this._overlay.style.opacity = "0.01";
-
-    this._frame.contentWindow.addEventListener("dialogclosing", function closingDialog(aEvent) {
-      gSubDialog._frame.contentWindow.removeEventListener("dialogclosing", closingDialog);
-      gSubDialog.close(aEvent);
-    });
   },
 
   _onLoad: function(aEvent) {
     if (aEvent.target.contentWindow.location == "about:blank")
       return;
 
     // Do this on load to wait for the CSS to load and apply before calculating the size.
     let docEl = this._frame.contentDocument.documentElement;
@@ -182,9 +202,52 @@ let gSubDialog = {
     this._box.style.minWidth = "calc(" +
                                (boxHorizontalBorder + boxHorizontalPadding) +
                                "px + " + frameWidth + ")";
 
     this._overlay.style.visibility = "visible";
     this._frame.focus();
     this._overlay.style.opacity = ""; // XXX: focus hack continued from _onContentLoaded
   },
+
+  _onDialogClosing: function(aEvent) {
+    this._frame.contentWindow.removeEventListener("dialogclosing", this);
+    this._closingEvent = aEvent;
+  },
+
+  _addDialogEventListeners: function() {
+    // Make the close button work.
+    let dialogClose = document.getElementById("dialogClose");
+    dialogClose.addEventListener("command", this);
+
+    // DOMTitleChanged isn't fired on the frame, only on the chromeEventHandler
+    let chromeBrowser = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIWebNavigation)
+                              .QueryInterface(Ci.nsIDocShell)
+                              .chromeEventHandler;
+    chromeBrowser.addEventListener("DOMTitleChanged", this, true);
+
+    // Similarly DOMFrameContentLoaded only fires on the top window
+    window.addEventListener("DOMFrameContentLoaded", this, true);
+
+    // Wait for the stylesheets injected during DOMContentLoaded to load before showing the dialog
+    // otherwise there is a flicker of the stylesheet applying.
+    this._frame.addEventListener("load", this);
+
+    chromeBrowser.addEventListener("unload", this, true);
+  },
+
+  _removeDialogEventListeners: function() {
+    let chromeBrowser = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIWebNavigation)
+                              .QueryInterface(Ci.nsIDocShell)
+                              .chromeEventHandler;
+    chromeBrowser.removeEventListener("DOMTitleChanged", this, true);
+    chromeBrowser.removeEventListener("unload", this, true);
+
+    let dialogClose = document.getElementById("dialogClose");
+    dialogClose.removeEventListener("command", this);
+
+    window.removeEventListener("DOMFrameContentLoaded", this, true);
+    this._frame.removeEventListener("load", this);
+    this._frame.contentWindow.removeEventListener("dialogclosing", this);
+  }
 };
--- a/browser/components/preferences/in-content/tests/browser_subdialogs.js
+++ b/browser/components/preferences/in-content/tests/browser_subdialogs.js
@@ -71,17 +71,17 @@ let gTests = [{
     let dialog = yield dialogPromise;
 
     let closingPromise = promiseDialogClosing(dialog);
 
     info("cancelling the dialog");
     dialog.document.documentElement.cancelDialog();
 
     let closingEvent = yield closingPromise;
-    ise(closingEvent.detail.button, "cancel", "closing event should indicate button was 'accept'");
+    ise(closingEvent.detail.button, "cancel", "closing event should indicate button was 'cancel'");
 
     yield deferredClose.promise;
     ise(rv.acceptCount, 0, "return value should NOT have been updated");
   },
 },
 {
   desc: "Check window.close on the dialog",
   run: function* () {
@@ -114,16 +114,32 @@ let gTests = [{
     yield EventUtils.synthesizeMouseAtCenter(content.document.getElementById("dialogClose"), {},
                                              content.window);
 
     yield deferredClose.promise;
     ise(rv.acceptCount, 0, "return value should NOT have been updated");
   },
 },
 {
+  desc: "Check that 'back' navigation will close the dialog",
+  run: function* () {
+    let rv = { acceptCount: 0 };
+    let deferredClose = Promise.defer();
+    let dialogPromise = openAndLoadSubDialog(gDialogURL, null, rv,
+                                             (aEvent) => dialogClosingCallback(deferredClose, aEvent));
+    let dialog = yield dialogPromise;
+
+    info("cancelling the dialog");
+    content.gSubDialog._frame.goBack();
+
+    yield deferredClose.promise;
+    ise(rv.acceptCount, 0, "return value should NOT have been updated");
+  },
+},
+{
   desc: "Hitting escape in the dialog",
   run: function* () {
     let rv = { acceptCount: 0 };
     let deferredClose = Promise.defer();
     let dialogPromise = openAndLoadSubDialog(gDialogURL, null, rv,
                                              (aEvent) => dialogClosingCallback(deferredClose, aEvent));
     let dialog = yield dialogPromise;
 
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -516,16 +516,21 @@
               let target = aEvent.originalTarget;
               type = "mouse";
               if (target.classList.contains("searchbar-engine-one-off-item")) {
                 source = "oneoff";
               } else if (target.classList.contains("search-panel-header") ||
                          target.parentNode.classList.contains("search-panel-header")) {
                 source = "header";
               }
+            } else if (aEvent instanceof XULCommandEvent) {
+              if (target.getAttribute("anonid") == "paste-and-search") {
+                aEngine = this.currentEngine;
+                source = "paste";
+              }
             }
 
             BrowserSearch.recordOneoffSearchInTelemetry(aEngine, source, type, where);
           }
 
           if (where == "tab-background")
             this.focus();
         ]]></body>
@@ -832,18 +837,17 @@
         while (insertLocation.nextSibling &&
                insertLocation.getAttribute("cmd") != "cmd_paste")
           insertLocation = insertLocation.nextSibling;
         if (insertLocation) {
           element = document.createElementNS(kXULNS, "menuitem");
           label = this._stringBundle.getString("cmd_pasteAndSearch");
           element.setAttribute("label", label);
           element.setAttribute("anonid", "paste-and-search");
-          element.setAttribute("oncommand",
-              "BrowserSearch.searchBar.select(); goDoCommand('cmd_paste'); BrowserSearch.searchBar.handleSearchCommand();");
+          element.setAttribute("oncommand", "BrowserSearch.pasteAndSearch(event)");
           cxmenu.insertBefore(element, insertLocation.nextSibling);
           pasteAndSearch = element;
         }
 
         element = document.createElementNS(kXULNS, "menuitem");
         label = this._stringBundle.getString("cmd_clearHistory");
         akey = this._stringBundle.getString("cmd_clearHistory_accesskey");
         element.setAttribute("label", label);
--- a/browser/devtools/framework/ToolboxProcess.jsm
+++ b/browser/devtools/framework/ToolboxProcess.jsm
@@ -213,18 +213,27 @@ BrowserToolboxProcess.prototype = {
     // was present in order to also clear the child profile's startup cache as
     // well.
     //
     // As an approximation of "isLocalBuild", check for an unofficial build.
     if (!Services.appinfo.isOfficial) {
       args.push("-purgecaches");
     }
 
+    // Disable safe mode for the new process in case this was opened via the
+    // keyboard shortcut.
+    let nsIEnvironment = Components.classes["@mozilla.org/process/environment;1"].getService(Components.interfaces.nsIEnvironment);
+    let originalValue = nsIEnvironment.get("MOZ_DISABLE_SAFE_MODE_KEY");
+    nsIEnvironment.set("MOZ_DISABLE_SAFE_MODE_KEY", "1");
+
     process.runwAsync(args, args.length, { observe: () => this.close() });
 
+    // Now that the process has started, it's safe to reset the env variable.
+    nsIEnvironment.set("MOZ_DISABLE_SAFE_MODE_KEY", originalValue);
+
     this._telemetry.toolOpened("jsbrowserdebugger");
 
     dumpn("Chrome toolbox is now running...");
     this.emit("run", this);
   },
 
   /**
    * Closes the remote debugging server and kills the toolbox process.
--- a/browser/devtools/netmonitor/test/browser_net_security-details.js
+++ b/browser/devtools/netmonitor/test/browser_net_security-details.js
@@ -32,18 +32,27 @@ add_task(function* () {
 
   let errorbox = $("#security-error");
   let infobox = $("#security-information");
 
   is(errorbox.hidden, true, "Error box is hidden.");
   is(infobox.hidden, false, "Information box visible.");
 
   // Connection
-  checkLabel("#security-protocol-version-value", "TLSv1.2");
-  checkLabel("#security-ciphersuite-value", "TLS_RSA_WITH_AES_128_CBC_SHA");
+
+  // The protocol will be TLS but the exact version depends on which protocol
+  // the test server example.com supports.
+  let protocol = $("#security-protocol-version-value").value;
+  ok(protocol.startsWith("TLS"), "The protocol " + protocol + " seems valid.");
+
+  // The cipher suite used by the test server example.com might change at any
+  // moment but all of them should start with "TLS_".
+  // http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml
+  let suite = $("#security-ciphersuite-value").value;
+  ok(suite.startsWith("TLS_"), "The suite " + suite + " seems valid.");
 
   // Host
   checkLabel("#security-info-host-header", "Host example.com:");
   checkLabel("#security-http-strict-transport-security-value", "Disabled");
   checkLabel("#security-public-key-pinning-value", "Disabled");
 
   // Cert
   checkLabel("#security-cert-subject-cn", "example.com");
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -20,32 +20,32 @@ support-files =
 [browser_perf-data-samples.js]
 [browser_perf-details-calltree-render.js]
 [browser_perf-details-flamegraph-render.js]
 [browser_perf-details-memory-calltree-render.js]
 [browser_perf-details-memory-flamegraph-render.js]
 [browser_perf-details-waterfall-render.js]
 [browser_perf-details-01.js]
 [browser_perf-details-02.js]
-# [browser_perf-details-03.js] bug 1132206
+[browser_perf-details-03.js]
 [browser_perf-details-04.js]
 [browser_perf-events-calltree.js]
 [browser_perf-front-basic-profiler-01.js]
 [browser_perf-front-basic-timeline-01.js]
 #[browser_perf-front-profiler-01.js] bug 1077464
 [browser_perf-front-profiler-02.js]
 [browser_perf-front-profiler-03.js]
 [browser_perf-front-profiler-04.js]
 #[browser_perf-front-profiler-05.js] bug 1077464
 #[browser_perf-front-profiler-06.js]
 [browser_perf-front.js]
 [browser_perf-jump-to-debugger-01.js]
 [browser_perf-jump-to-debugger-02.js]
 [browser_perf-options-01.js]
-# [browser_perf-options-02.js] bug 1133230
+[browser_perf-options-02.js]
 [browser_perf-options-invert-call-tree-01.js]
 [browser_perf-options-invert-call-tree-02.js]
 [browser_perf-options-invert-flame-graph-01.js]
 [browser_perf-options-invert-flame-graph-02.js]
 [browser_perf-options-flatten-tree-recursion-01.js]
 [browser_perf-options-flatten-tree-recursion-02.js]
 [browser_perf-options-show-platform-data-01.js]
 [browser_perf-options-show-platform-data-02.js]
@@ -67,17 +67,17 @@ support-files =
 [browser_perf-refresh.js]
 [browser_perf-ui-recording.js]
 [browser_perf-recording-notices-01.js]
 [browser_perf-recording-notices-02.js]
 [browser_perf_recordings-io-01.js]
 [browser_perf_recordings-io-02.js]
 [browser_perf_recordings-io-03.js]
 [browser_perf_recordings-io-04.js]
-# [browser_perf-range-changed-render.js] bug 1130669 crash
+[browser_perf-range-changed-render.js]
 [browser_perf-recording-selected-01.js]
 [browser_perf-recording-selected-02.js]
 [browser_perf-recording-selected-03.js]
 [browser_perf-recording-selected-04.js]
 [browser_profiler_categories.js]
 [browser_profiler_content-check.js]
 [browser_profiler_tree-abstract-01.js]
 [browser_profiler_tree-abstract-02.js]
--- a/browser/devtools/shared/widgets/FlameGraph.jsm
+++ b/browser/devtools/shared/widgets/FlameGraph.jsm
@@ -795,20 +795,20 @@ FlameGraph.prototype = {
     let maxIters = FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS;
     let numIters = 0;
 
     if (dataScale > spacingMin) {
       return dataScale;
     }
 
     while (true) {
+      let scaledStep = dataScale * timingStep;
       if (++numIters > maxIters) {
         return scaledStep;
       }
-      let scaledStep = dataScale * timingStep;
       if (scaledStep < spacingMin) {
         timingStep <<= 1;
         continue;
       }
       return scaledStep;
     }
   },
 
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-params-01.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-params-01.js
@@ -7,29 +7,34 @@
 
 add_task(function*() {
   let { target, front } = yield initBackend(SIMPLE_NODES_URL);
   let [_, nodes] = yield Promise.all([
     front.setup({ reload: true }),
     getN(front, "create-node", 15)
   ]);
 
+  yield loadFrameScripts();
+
   let allNodeParams = yield Promise.all(nodes.map(node => node.getParams()));
   let nodeTypes = [
     "AudioDestinationNode",
     "AudioBufferSourceNode", "ScriptProcessorNode", "AnalyserNode", "GainNode",
     "DelayNode", "BiquadFilterNode", "WaveShaperNode", "PannerNode", "ConvolverNode",
     "ChannelSplitterNode", "ChannelMergerNode", "DynamicsCompressorNode", "OscillatorNode",
     "StereoPannerNode"
   ];
 
-  nodeTypes.forEach((type, i) => {
+  let defaults = yield Promise.all(nodeTypes.map(type => nodeDefaultValues(type)));
+
+  nodeTypes.map((type, i) => {
     let params = allNodeParams[i];
+
     params.forEach(({param, value, flags}) => {
-      ok(param in NODE_DEFAULT_VALUES[type], "expected parameter for " + type);
+      ok(param in defaults[i], "expected parameter for " + type);
 
       ok(typeof flags === "object", type + " has a flags object");
 
       if (param === "buffer") {
         is(flags.Buffer, true, "`buffer` params have Buffer flag");
       }
       else if (param === "bufferSize" || param === "frequencyBinCount") {
         is(flags.readonly, true, param + " is readonly");
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-params-02.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-params-02.js
@@ -8,37 +8,45 @@
 
 add_task(function*() {
   let { target, front } = yield initBackend(SIMPLE_NODES_URL);
   let [_, nodes] = yield Promise.all([
     front.setup({ reload: true }),
     getN(front, "create-node", 15)
   ]);
 
+  yield loadFrameScripts();
+
   let allParams = yield Promise.all(nodes.map(node => node.getParams()));
   let types = [
     "AudioDestinationNode", "AudioBufferSourceNode", "ScriptProcessorNode",
     "AnalyserNode", "GainNode", "DelayNode", "BiquadFilterNode", "WaveShaperNode",
     "PannerNode", "ConvolverNode", "ChannelSplitterNode", "ChannelMergerNode",
     "DynamicsCompressorNode", "OscillatorNode", "StereoPannerNode"
   ];
 
+  let defaults = yield Promise.all(types.map(type => nodeDefaultValues(type)));
+
+  info(JSON.stringify(defaults));
+
   allParams.forEach((params, i) => {
-    compare(params, NODE_DEFAULT_VALUES[types[i]], types[i]);
+    compare(params, defaults[i], types[i]);
   });
 
   yield removeTab(target.tab);
 });
 
 function compare (actual, expected, type) {
   actual.forEach(({ value, param }) => {
     value = getGripValue(value);
     if (typeof expected[param] === "function") {
       ok(expected[param](value), type + " has a passing value for " + param);
     }
     else {
       ise(value, expected[param], type + " has correct default value and type for " + param);
     }
   });
 
+  info(Object.keys(expected).join(',') + " - " + JSON.stringify(expected));
+
   is(actual.length, Object.keys(expected).length,
     type + " has correct amount of properties.");
 }
--- a/browser/devtools/webaudioeditor/test/browser_wa_properties-view-media-nodes.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view-media-nodes.js
@@ -41,31 +41,35 @@ add_task(function*() {
   let gVars = PropertiesView._propsView;
 
   // Auto enable getUserMedia
   let mediaPermissionPref = Services.prefs.getBoolPref(MEDIA_PERMISSION);
   Services.prefs.setBoolPref(MEDIA_PERMISSION, true);
 
   reload(target);
 
+  yield loadFrameScripts();
+
   let [actors] = yield Promise.all([
     getN(gFront, "create-node", 4),
     waitForGraphRendered(panelWin, 4, 0)
   ]);
 
   let nodeIds = actors.map(actor => actor.actorID);
   let types = [
     "AudioDestinationNode", "MediaElementAudioSourceNode",
     "MediaStreamAudioSourceNode", "MediaStreamAudioDestinationNode"
   ];
 
+  let defaults = yield Promise.all(types.map(type => nodeDefaultValues(type)));
+
   for (let i = 0; i < types.length; i++) {
     click(panelWin, findGraphNode(panelWin, nodeIds[i]));
     yield waitForInspectorRender(panelWin, EVENTS);
-    checkVariableView(gVars, 0, NODE_DEFAULT_VALUES[types[i]], types[i]);
+    checkVariableView(gVars, 0, defaults[i], types[i]);
   }
 
   // Reset permissions on getUserMedia
   Services.prefs.setBoolPref(MEDIA_PERMISSION, mediaPermissionPref);
 
   yield teardown(target);
 
   yield waitForDeviceClosed();
--- a/browser/devtools/webaudioeditor/test/browser_wa_properties-view-params.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view-params.js
@@ -11,28 +11,32 @@ add_task(function*() {
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
   let gVars = PropertiesView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
+  yield loadFrameScripts();
+
   let [actors] = yield Promise.all([
     getN(gFront, "create-node", 15),
     waitForGraphRendered(panelWin, 15, 0)
   ]);
   let nodeIds = actors.map(actor => actor.actorID);
   let types = [
     "AudioDestinationNode", "AudioBufferSourceNode", "ScriptProcessorNode",
     "AnalyserNode", "GainNode", "DelayNode", "BiquadFilterNode", "WaveShaperNode",
     "PannerNode", "ConvolverNode", "ChannelSplitterNode", "ChannelMergerNode",
     "DynamicsCompressorNode", "OscillatorNode"
   ];
 
+  let defaults = yield Promise.all(types.map(type => nodeDefaultValues(type)));
+
   for (let i = 0; i < types.length; i++) {
     click(panelWin, findGraphNode(panelWin, nodeIds[i]));
     yield waitForInspectorRender(panelWin, EVENTS);
-    checkVariableView(gVars, 0, NODE_DEFAULT_VALUES[types[i]], types[i]);
+    checkVariableView(gVars, 0, defaults[i], types[i]);
   }
 
   yield teardown(target);
 });
--- a/browser/devtools/webaudioeditor/test/head.js
+++ b/browser/devtools/webaudioeditor/test/head.js
@@ -15,16 +15,17 @@ let { Task } = Cu.import("resource://gre
 let { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
 let { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
 
 let { WebAudioFront } = devtools.require("devtools/server/actors/webaudio");
 let TargetFactory = devtools.TargetFactory;
+let audioNodes = devtools.require("devtools/server/actors/utils/audionodes.json");
 let mm = null;
 
 const FRAME_SCRIPT_UTILS_URL = "chrome://browser/content/devtools/frame-script-utils.js";
 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/webaudioeditor/test/";
 const SIMPLE_CONTEXT_URL = EXAMPLE_URL + "doc_simple-context.html";
 const COMPLEX_CONTEXT_URL = EXAMPLE_URL + "doc_complex-context.html";
 const SIMPLE_NODES_URL = EXAMPLE_URL + "doc_simple-node-creation.html";
 const MEDIA_NODES_URL = EXAMPLE_URL + "doc_media-node-creation.html";
@@ -126,16 +127,25 @@ function reload(aTarget, aWaitForTargetE
 }
 
 function navigate(aTarget, aUrl, aWaitForTargetEvent = "navigate") {
   executeSoon(() => aTarget.activeTab.navigateTo(aUrl));
   return once(aTarget, aWaitForTargetEvent);
 }
 
 /**
+ * Call manually in tests that use frame script utils after initializing
+ * the shader editor. Call after init but before navigating to different pages.
+ */
+function loadFrameScripts () {
+  mm = gBrowser.selectedBrowser.messageManager;
+  mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
+}
+
+/**
  * Adds a new tab, and instantiate a WebAudiFront object.
  * This requires calling removeTab before the test ends.
  */
 function initBackend(aUrl) {
   info("Initializing a web audio editor front.");
 
   if (!DebuggerServer.initialized) {
     DebuggerServer.init();
@@ -478,85 +488,56 @@ function evalInDebuggee (script) {
     mm.removeMessageListener("devtools:test:eval:response", handler);
     deferred.resolve(data.value);
   }
 
   return deferred.promise;
 }
 
 /**
- * List of audio node properties to test against expectations of the AudioNode actor
+ * Takes an AudioNode type and returns it's properties (from audionode.json)
+ * as keys and their default values as keys
  */
+function nodeDefaultValues(nodeName) {
+  let fn = NODE_CONSTRUCTORS[nodeName];
+
+  if(typeof fn === 'undefined') return {};
+
+  let init = nodeName === "AudioDestinationNode" ? "destination" : `create${fn}()`;
+
+  let definition = JSON.stringify(audioNodes[nodeName].properties);
+
+  let evalNode = evalInDebuggee(`
+    let ins = (new AudioContext()).${init};
+    let props = ${definition};
+    let answer = {};
 
-const NODE_DEFAULT_VALUES = {
-  "AudioDestinationNode": {},
-  "MediaElementAudioSourceNode": {},
-  "MediaStreamAudioSourceNode": {},
-  "MediaStreamAudioDestinationNode": {
-    "stream": "MediaStream"
-  },
-  "AudioBufferSourceNode": {
-    "playbackRate": 1,
-    "loop": false,
-    "loopStart": 0,
-    "loopEnd": 0,
-    "buffer": null
-  },
-  "ScriptProcessorNode": {
-    "bufferSize": 4096
-  },
-  "AnalyserNode": {
-    "fftSize": 2048,
-    "minDecibels": -100,
-    "maxDecibels": -30,
-    "smoothingTimeConstant": 0.8,
-    "frequencyBinCount": 1024
-  },
-  "GainNode": {
-    "gain": 1
-  },
-  "DelayNode": {
-    "delayTime": 0
-  },
-  "BiquadFilterNode": {
-    "type": "lowpass",
-    "frequency": 350,
-    "Q": 1,
-    "detune": 0,
-    "gain": 0
-  },
-  "WaveShaperNode": {
-    "curve": null,
-    "oversample": "none"
-  },
-  "PannerNode": {
-    "panningModel": "equalpower",
-    "distanceModel": "inverse",
-    "refDistance": 1,
-    "maxDistance": 10000,
-    "rolloffFactor": 1,
-    "coneInnerAngle": 360,
-    "coneOuterAngle": 360,
-    "coneOuterGain": 0
-  },
-  "ConvolverNode": {
-    "buffer": null,
-    "normalize": true
-  },
-  "ChannelSplitterNode": {},
-  "ChannelMergerNode": {},
-  "DynamicsCompressorNode": {
-    "threshold": -24,
-    "knee": 30,
-    "ratio": 12,
-    "reduction": 0,
-    "attack": 0.003000000026077032,
-    "release": 0.25
-  },
-  "OscillatorNode": {
-    "type": "sine",
-    "frequency": 440,
-    "detune": 0
-  },
-  "StereoPannerNode": {
-    "pan": 0
-  }
-};
+    for(let k in props) {
+      if (props[k].param) {
+        answer[k] = ins[k].defaultValue;
+      } else if (typeof ins[k] === "object" && ins[k] !== null) {
+        answer[k] = ins[k].toString().slice(8, -1);
+      } else {
+        answer[k] = ins[k];
+      }
+    }
+    answer;`);
+
+  return evalNode;
+}
+
+const NODE_CONSTRUCTORS = {
+  "MediaStreamAudioDestinationNode": "MediaStreamDestination",
+  "AudioBufferSourceNode": "BufferSource",
+  "ScriptProcessorNode": "ScriptProcessor",
+  "AnalyserNode": "Analyser",
+  "GainNode": "Gain",
+  "DelayNode": "Delay",
+  "BiquadFilterNode": "BiquadFilter",
+  "WaveShaperNode": "WaveShaper",
+  "PannerNode": "Panner",
+  "ConvolverNode": "Convolver",
+  "ChannelSplitterNode": "ChannelSplitter",
+  "ChannelMergerNode": "ChannelMerger",
+  "DynamicsCompressorNode": "DynamicsCompressor",
+  "OscillatorNode": "Oscillator",
+  "StereoPannerNode": "StereoPanner"
+};
\ No newline at end of file
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -264,16 +264,17 @@ These should match what Safari and other
 <!ENTITY scratchpad.accesskey         "s">
 <!ENTITY scratchpad.keycode           "VK_F4">
 <!ENTITY scratchpad.keytext           "F4">
 
 <!-- LOCALIZATION NOTE (browserToolboxMenu.label): This is the label for the
   -  application menu item that opens the browser toolbox UI in the Tools menu. -->
 <!ENTITY browserToolboxMenu.label     "Browser Toolbox">
 <!ENTITY browserToolboxMenu.accesskey "e">
+<!ENTITY browserToolboxCmd.commandkey "i">
 
 <!-- LOCALIZATION NOTE (browserContentToolboxMenu.label): This is the label for the
   -  application menu item that opens the browser content toolbox UI in the Tools menu.
   -  This toolbox allows to debug the chrome of the content process in multiprocess builds.  -->
 <!ENTITY browserContentToolboxMenu.label     "Browser Content Toolbox">
 
 <!ENTITY devToolbarCloseButton.tooltiptext "Close Developer Toolbar">
 <!ENTITY devToolbarMenu.label              "Developer Toolbar">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -671,10 +671,46 @@ readingList.sidebar.showMore.tooltip = S
 # Pre-landed strings for bug 1131457 / bug 1131461
 readingList.urlbar.add = Add page to Reading List
 readingList.urlbar.addDone = Page added to Reading List
 readingList.urlbar.remove = Remove page from Reading List
 readingList.urlbar.removeDone = Page removed from Reading List
 # Pre-landed strings for bug 1133610 & bug 1133611
 # LOCALIZATION NOTE(readingList.promo.noSync.label): %S a link, using the text from readingList.promo.noSync.link
 readingList.promo.noSync.label = Access your Reading List on all your devices. %S
-readingList.promo.noSync.link = Get started with Sync.
-readingList.promo.hasSync.label = You can now access your Reading List on all your devices connected by Sync.
+# LOCALIZATION NOTE(readingList.promo.noSync.link): $S is syncBrandShortName
+readingList.promo.noSync.link = Get started with %S.
+# LOCALIZATION NOTE(readingList.promo.hasSync.label): $S is syncBrandShortName
+readingList.promo.hasSync.label = You can now access your Reading List on all your devices connected by %S.
+
+# Pre-landed strings for bug 1136570
+readerView.promo.firstDetectedArticle.title = Read and save articles easily
+readerView.promo.firstDetectedArticle.body = Click the book to make articles easier to read and use the plus to save them for later.
+readingList.promo.firstUse.exitTourButton = Close
+# LOCALIZATION NOTE(readingList.promo.firstUse.tourDoneButton):
+# » is used as an indication that pressing this button progresses through the tour.
+readingList.promo.firstUse.tourDoneButton = Start Reading »
+# LOCALIZATION NOTE(readingList.promo.firstUse.readingList.multipleStepsTitle):
+# This is used when there are multiple steps in the tour.
+# %1$S is the current step's title (readingList.promo.firstUse.*.title), %2$S is the current step number of the tour, %3$S is the total number of steps.
+readingList.promo.firstUse.multipleStepsTitle = %1$S (%2$S/%3$S)
+readingList.promo.firstUse.readingList.title = Reading List
+readingList.promo.firstUse.readingList.body = Save articles for later and find them easily when you need them.
+# LOCALIZATION NOTE(readingList.promo.firstUse.readingList.moveToButton):
+# » is used as an indication that pressing this button progresses through the tour.
+readingList.promo.firstUse.readingList.moveToButton = Next: Easy finding »
+readingList.promo.firstUse.readerView.title = Reader View
+readingList.promo.firstUse.readerView.body = Remove clutter so you can focus exactly on what you want to read.
+# LOCALIZATION NOTE(readingList.promo.firstUse.readerView.moveToButton):
+# » is used as an indication that pressing this button progresses through the tour.
+readingList.promo.firstUse.readerView.moveToButton = Next: Easy reading »
+readingList.promo.firstUse.syncNotSignedIn.title = Sync
+# LOCALIZATION NOTE(readingList.promo.firstUse.syncNotSignedIn.body): %S is brandShortName
+readingList.promo.firstUse.syncNotSignedIn.body = Sign in to access your Reading List everywhere you use %S.
+# LOCALIZATION NOTE(readingList.promo.firstUse.syncNotSignedIn.moveToButton):
+# » is used as an indication that pressing this button progresses through the tour.
+readingList.promo.firstUse.syncNotSignedIn.moveToButton = Next: Easy access »
+readingList.promo.firstUse.syncSignedIn.title = Sync
+# LOCALIZATION NOTE(readingList.promo.firstUse.syncSignedIn.body): %S is brandShortName
+readingList.promo.firstUse.syncSignedIn.body = Open your Reading List articles everywhere you use %S.
+# LOCALIZATION NOTE(readingList.promo.firstUse.syncSignedIn.moveToButton):
+# » is used as an indication that pressing this button progresses through the tour.
+readingList.promo.firstUse.syncSignedIn.moveToButton = Next: Easy access »
--- a/browser/modules/Social.jsm
+++ b/browser/modules/Social.jsm
@@ -514,16 +514,20 @@ this.OpenGraphBuilder = {
           if (pageData.description)
             body += pageData.description + "\n\n";
           if (pageData.text)
             body += pageData.text + "\n\n";
           body += pageData.url;
           query["body"] = body;
         }
       });
+      // if the url template doesn't have title and no text was provided, add the title as the text.
+      if (!query.text && !query.title && pageData.title) {
+        query.text = pageData.title;
+      }
     }
     var str = [];
     for (let p in query)
        str.push(p + "=" + encodeURIComponent(query[p]));
     if (str.length)
       endpointURL = endpointURL + "?" + str.join("&");
     return endpointURL;
   },
--- a/build/mobile/robocop/FennecNativeElement.java
+++ b/build/mobile/robocop/FennecNativeElement.java
@@ -9,20 +9,22 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.EditText;
 import android.widget.TextSwitcher;
 import android.widget.TextView;
 
 public class FennecNativeElement implements Element {
     private final Activity mActivity;
     private final Integer mId;
+    private final String mName;
 
     public FennecNativeElement(Integer id, Activity activity) {
         mId = id;
         mActivity = activity;
+        mName = activity.getResources().getResourceName(id);
     }
 
     @Override
     public Integer getId() {
         return mId;
     }
 
     private boolean mClickSuccess;
@@ -35,21 +37,21 @@ public class FennecNativeElement impleme
                 @Override
                 public void run() {
                     View view = mActivity.findViewById(mId);
                     if (view != null) {
                         if (view.performClick()) {
                             mClickSuccess = true;
                         } else {
                             FennecNativeDriver.log(FennecNativeDriver.LogLevel.WARN,
-                                "Robocop called click on an element with no listener");
+                                "Robocop called click on an element with no listener " + mId + " " + mName);
                         }
                     } else {
                         FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR,
-                            "click: unable to find view "+mId);
+                            "click: unable to find view " + mId + " " + mName);
                     }
                 }
             });
         return mClickSuccess;
     }
 
     private Object mText;
 
@@ -74,27 +76,27 @@ public class FennecNativeElement impleme
                             if (vg.getChildAt(i) instanceof TextView) {
                                 mText = ((TextView)vg.getChildAt(i)).getText();
                             }
                         }
                     } else if (v instanceof TextView) {
                         mText = ((TextView)v).getText(); 
                     } else if (v == null) {
                         FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR,
-                            "getText: unable to find view "+mId);
+                            "getText: unable to find view " + mId + " " + mName);
                     } else {
                         FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR,
-                            "getText: unhandled type for view "+mId);
+                            "getText: unhandled type for view " + mId + " " + mName);
                     }
                 } // end of run() method definition
             } // end of anonymous Runnable object instantiation
         );
         if (mText == null) {
             FennecNativeDriver.log(FennecNativeDriver.LogLevel.WARN,
-                "getText: Text is null for view "+mId);
+                "getText: Text is null for view " + mId + " " + mName);
             return null;
         }
         return mText.toString();
     }
 
     private boolean mDisplayed;
 
     @Override
--- a/content/test/reftest/reftest.list
+++ b/content/test/reftest/reftest.list
@@ -1,13 +1,13 @@
 == bug453105.html bug453105-ref.html
 == optiontext.html optiontext-ref.html
 == bug456008.xhtml bug456008-ref.html
 == bug439965.html bug439965-ref.html
 == bug427779.xml bug427779-ref.xml
-skip-if(B2G) == bug559996.html bug559996-ref.html # bug 773482
-skip-if(B2G) == bug591981-1.html bug591981-ref.html
+skip-if(B2G||Mulet) == bug559996.html bug559996-ref.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == bug591981-1.html bug591981-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == bug591981-2.html bug591981-ref.html
 == bug592366-1.html bug592366-ref.html
-skip-if(B2G) == bug592366-2.html bug592366-ref.html
-skip-if(B2G&&browserIsRemote) == bug592366-1.xhtml bug592366-ref.xhtml
-skip-if(B2G) == bug592366-2.xhtml bug592366-ref.xhtml
+skip-if(B2G||Mulet) == bug592366-2.html bug592366-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == bug592366-1.xhtml bug592366-ref.xhtml # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == bug592366-2.xhtml bug592366-ref.xhtml # Initial mulet triage: parity with B2G/B2G Desktop
 == bug798068.xhtml bug798068-ref.xhtml
--- a/dom/base/StructuredCloneTags.h
+++ b/dom/base/StructuredCloneTags.h
@@ -34,15 +34,17 @@ enum StructuredCloneTags {
 
   // This tag is for WebCrypto keys
   SCTAG_DOM_WEBCRYPTO_KEY,
 
   SCTAG_DOM_NULL_PRINCIPAL,
   SCTAG_DOM_SYSTEM_PRINCIPAL,
   SCTAG_DOM_CONTENT_PRINCIPAL,
 
+  SCTAG_DOM_NFC_NDEF,
+
   SCTAG_DOM_MAX
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // StructuredCloneTags_h__
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -54,16 +54,19 @@
 #include "nsGlobalWindow.h"
 #include "nsScriptNameSpaceManager.h"
 #include "StructuredCloneTags.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/dom/CryptoKey.h"
 #include "mozilla/dom/ErrorEvent.h"
 #include "mozilla/dom/ImageDataBinding.h"
 #include "mozilla/dom/ImageData.h"
+#ifdef MOZ_NFC
+#include "mozilla/dom/MozNDEFRecord.h"
+#endif // MOZ_NFC
 #include "mozilla/dom/StructuredClone.h"
 #include "mozilla/dom/SubtleCryptoBinding.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "nsAXPCNativeCallContext.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 
 #include "nsJSPrincipals.h"
@@ -2509,16 +2512,34 @@ NS_DOMReadStructuredClone(JSContext* cx,
     JS::RootedValue result(cx);
     rv = nsContentUtils::WrapNative(cx, principal, &NS_GET_IID(nsIPrincipal), &result);
     if (NS_FAILED(rv)) {
       xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR);
       return nullptr;
     }
 
     return result.toObjectOrNull();
+  } else if (tag == SCTAG_DOM_NFC_NDEF) {
+#ifdef MOZ_NFC
+    nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(cx));
+    if (!global) {
+      return nullptr;
+    }
+
+    // Prevent the return value from being trashed by a GC during ~nsRefPtr.
+    JS::Rooted<JSObject*> result(cx);
+    {
+      nsRefPtr<MozNDEFRecord> ndefRecord = new MozNDEFRecord(global);
+      result = ndefRecord->ReadStructuredClone(cx, reader) ?
+               ndefRecord->WrapObject(cx) : nullptr;
+    }
+    return result;
+#else
+    return nullptr;
+#endif
   }
 
   // Don't know what this is. Bail.
   xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR);
   return nullptr;
 }
 
 bool
@@ -2560,16 +2581,24 @@ NS_DOMWriteStructuredClone(JSContext* cx
       MOZ_ASSERT(info.type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
       const mozilla::ipc::ContentPrincipalInfo& cInfo = info;
       return JS_WriteUint32Pair(writer, SCTAG_DOM_CONTENT_PRINCIPAL, cInfo.appId()) &&
              JS_WriteUint32Pair(writer, cInfo.isInBrowserElement(), cInfo.spec().Length()) &&
              JS_WriteBytes(writer, cInfo.spec().get(), cInfo.spec().Length());
     }
   }
 
+#ifdef MOZ_NFC
+  MozNDEFRecord* ndefRecord;
+  if (NS_SUCCEEDED(UNWRAP_OBJECT(MozNDEFRecord, obj, ndefRecord))) {
+    return JS_WriteUint32Pair(writer, SCTAG_DOM_NFC_NDEF, 0) &&
+           ndefRecord->WriteStructuredClone(cx, writer);
+  }
+#endif // MOZ_NFC
+
   // Don't know what this is
   xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR);
   return false;
 }
 
 void
 NS_DOMStructuredCloneError(JSContext* cx,
                            uint32_t errorid)
--- a/dom/bluetooth/BluetoothHidManager.h
+++ b/dom/bluetooth/BluetoothHidManager.h
@@ -18,21 +18,23 @@ class BluetoothHidManager : public Bluet
 public:
   BT_DECL_PROFILE_MGR_BASE
   virtual void GetName(nsACString& aName)
   {
     aName.AssignLiteral("HID");
   }
 
   static BluetoothHidManager* Get();
-  virtual ~BluetoothHidManager();
 
   // HID-specific functions
   void HandleInputPropertyChanged(const BluetoothSignal& aSignal);
 
+protected:
+  virtual ~BluetoothHidManager();
+
 private:
   BluetoothHidManager();
   bool Init();
   void Cleanup();
   void HandleShutdown();
 
   void NotifyStatusChanged();
 
--- a/dom/bluetooth/BluetoothInterface.h
+++ b/dom/bluetooth/BluetoothInterface.h
@@ -16,28 +16,29 @@ BEGIN_BLUETOOTH_NAMESPACE
 // Socket Interface
 //
 
 class BluetoothSocketResultHandler
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothSocketResultHandler)
 
-  virtual ~BluetoothSocketResultHandler() { }
-
   virtual void OnError(BluetoothStatus aStatus)
   {
     BT_WARNING("Received error code %d", (int)aStatus);
   }
 
   virtual void Listen(int aSockFd) { }
   virtual void Connect(int aSockFd, const nsAString& aBdAddress,
                        int aConnectionState) { }
   virtual void Accept(int aSockFd, const nsAString& aBdAddress,
                       int aConnectionState) { }
+
+protected:
+  virtual ~BluetoothSocketResultHandler() { }
 };
 
 class BluetoothSocketInterface
 {
 public:
   // Init and Cleanup is handled by BluetoothInterface
 
   virtual void Listen(BluetoothSocketType aType,
@@ -62,18 +63,16 @@ protected:
 
 //
 // Handsfree Interface
 //
 
 class BluetoothHandsfreeNotificationHandler
 {
 public:
-  virtual ~BluetoothHandsfreeNotificationHandler();
-
   virtual void
   ConnectionStateNotification(BluetoothHandsfreeConnectionState aState,
                               const nsAString& aBdAddr)
   { }
 
   virtual void
   AudioStateNotification(BluetoothHandsfreeAudioState aState,
                          const nsAString& aBdAddr)
@@ -146,25 +145,25 @@ public:
 
   virtual void
   KeyPressedNotification(const nsAString& aBdAddr)
   { }
 
 protected:
   BluetoothHandsfreeNotificationHandler()
   { }
+
+  virtual ~BluetoothHandsfreeNotificationHandler();
 };
 
 class BluetoothHandsfreeResultHandler
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothHandsfreeResultHandler)
 
-  virtual ~BluetoothHandsfreeResultHandler() { }
-
   virtual void OnError(BluetoothStatus aStatus)
   {
     BT_WARNING("Received error code %d", (int)aStatus);
   }
 
   virtual void Init() { }
   virtual void Cleanup() { }
 
@@ -183,16 +182,19 @@ public:
   virtual void CopsResponse() { }
   virtual void CindResponse() { }
   virtual void FormattedAtResponse() { }
   virtual void AtResponse() { }
   virtual void ClccResponse() { }
   virtual void PhoneStateChange() { }
 
   virtual void ConfigureWbs() { }
+
+protected:
+  virtual ~BluetoothHandsfreeResultHandler() { }
 };
 
 class BluetoothHandsfreeInterface
 {
 public:
   virtual void Init(
     BluetoothHandsfreeNotificationHandler* aNotificationHandler,
     int aMaxNumClients, BluetoothHandsfreeResultHandler* aRes) = 0;
@@ -272,18 +274,16 @@ protected:
 
 //
 // Bluetooth Advanced Audio Interface
 //
 
 class BluetoothA2dpNotificationHandler
 {
 public:
-  virtual ~BluetoothA2dpNotificationHandler();
-
   virtual void
   ConnectionStateNotification(BluetoothA2dpConnectionState aState,
                               const nsAString& aBdAddr)
   { }
 
   virtual void
   AudioStateNotification(BluetoothA2dpAudioState aState,
                          const nsAString& aBdAddr)
@@ -293,34 +293,37 @@ public:
   AudioConfigNotification(const nsAString& aBdAddr,
                           uint32_t aSampleRate,
                           uint8_t aChannelCount)
   { }
 
 protected:
   BluetoothA2dpNotificationHandler()
   { }
+
+  virtual ~BluetoothA2dpNotificationHandler();
 };
 
 class BluetoothA2dpResultHandler
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothA2dpResultHandler)
 
-  virtual ~BluetoothA2dpResultHandler() { }
-
   virtual void OnError(BluetoothStatus aStatus)
   {
     BT_WARNING("Received error code %d", (int)aStatus);
   }
 
   virtual void Init() { }
   virtual void Cleanup() { }
   virtual void Connect() { }
   virtual void Disconnect() { }
+
+protected:
+  virtual ~BluetoothA2dpResultHandler() { }
 };
 
 class BluetoothA2dpInterface
 {
 public:
   virtual void Init(BluetoothA2dpNotificationHandler* aNotificationHandler,
                     BluetoothA2dpResultHandler* aRes) = 0;
   virtual void Cleanup(BluetoothA2dpResultHandler* aRes) = 0;
@@ -337,18 +340,16 @@ protected:
 
 //
 // Bluetooth AVRCP Interface
 //
 
 class BluetoothAvrcpNotificationHandler
 {
 public:
-  virtual ~BluetoothAvrcpNotificationHandler();
-
   virtual void
   GetPlayStatusNotification()
   { }
 
   virtual void
   ListPlayerAppAttrNotification()
   { }
 
@@ -395,25 +396,25 @@ public:
 
   virtual void
   PassthroughCmdNotification(int aId, int aKeyState)
   { }
 
 protected:
   BluetoothAvrcpNotificationHandler()
   { }
+
+  virtual ~BluetoothAvrcpNotificationHandler();
 };
 
 class BluetoothAvrcpResultHandler
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothAvrcpResultHandler)
 
-  virtual ~BluetoothAvrcpResultHandler() { }
-
   virtual void OnError(BluetoothStatus aStatus)
   {
     BT_WARNING("Received error code %d", (int)aStatus);
   }
 
   virtual void Init() { }
   virtual void Cleanup() { }
 
@@ -428,16 +429,19 @@ public:
 
   virtual void GetElementAttrRsp() { }
 
   virtual void SetPlayerAppValueRsp() { }
 
   virtual void RegisterNotificationRsp() { }
 
   virtual void SetVolume() { }
+
+protected:
+  virtual ~BluetoothAvrcpResultHandler() { }
 };
 
 class BluetoothAvrcpInterface
 {
 public:
   virtual void Init(BluetoothAvrcpNotificationHandler* aNotificationHandler,
                     BluetoothAvrcpResultHandler* aRes) = 0;
   virtual void Cleanup(BluetoothAvrcpResultHandler* aRes) = 0;
@@ -486,18 +490,16 @@ protected:
 
 //
 // Bluetooth Core Interface
 //
 
 class BluetoothNotificationHandler
 {
 public:
-  virtual ~BluetoothNotificationHandler();
-
   virtual void AdapterStateChangedNotification(bool aState) { }
   virtual void AdapterPropertiesNotification(
     BluetoothStatus aStatus, int aNumProperties,
     const BluetoothProperty* aProperties) { }
 
   virtual void RemoteDevicePropertiesNotification(
     BluetoothStatus aStatus, const nsAString& aBdAddr,
     int aNumProperties, const BluetoothProperty* aProperties) { }
@@ -528,25 +530,25 @@ public:
                                       uint16_t aNumPackets) { }
 
   virtual void EnergyInfoNotification(const BluetoothActivityEnergyInfo& aInfo)
   { }
 
 protected:
   BluetoothNotificationHandler()
   { }
+
+  virtual ~BluetoothNotificationHandler();
 };
 
 class BluetoothResultHandler
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothResultHandler)
 
-  virtual ~BluetoothResultHandler() { }
-
   virtual void OnError(BluetoothStatus aStatus)
   {
     BT_LOGR("Received error code %d", aStatus);
   }
 
   virtual void Init() { }
   virtual void Cleanup() { }
   virtual void Enable() { }
@@ -576,16 +578,19 @@ public:
   virtual void SspReply() { }
 
   virtual void DutModeConfigure() { }
   virtual void DutModeSend() { }
 
   virtual void LeTestMode() { }
 
   virtual void ReadEnergyInfo() { }
+
+protected:
+  virtual ~BluetoothResultHandler() { }
 };
 
 class BluetoothInterface
 {
 public:
   static BluetoothInterface* GetInstance();
 
   virtual void Init(BluetoothNotificationHandler* aNotificationHandler,
--- a/dom/bluetooth/BluetoothProfileController.cpp
+++ b/dom/bluetooth/BluetoothProfileController.cpp
@@ -34,16 +34,17 @@ public:
   NS_DECL_NSITIMERCALLBACK
 
   CheckProfileStatusCallback(BluetoothProfileController* aController)
     : mController(aController)
   {
     MOZ_ASSERT(aController);
   }
 
+protected:
   virtual ~CheckProfileStatusCallback()
   {
     mController = nullptr;
   }
 
 private:
   nsRefPtr<BluetoothProfileController> mController;
 };
--- a/dom/bluetooth/BluetoothProfileManagerBase.h
+++ b/dom/bluetooth/BluetoothProfileManagerBase.h
@@ -28,21 +28,22 @@
 BEGIN_BLUETOOTH_NAMESPACE
 class BluetoothProfileController;
 
 class BluetoothProfileResultHandler
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothProfileResultHandler);
 
-  virtual ~BluetoothProfileResultHandler() { }
-
   virtual void OnError(nsresult aResult) { }
   virtual void Init() { }
   virtual void Deinit() { }
+
+protected:
+  virtual ~BluetoothProfileResultHandler() { }
 };
 
 class BluetoothProfileManagerBase : public nsIObserver
 {
 public:
   virtual void OnGetServiceChannel(const nsAString& aDeviceAddress,
                                    const nsAString& aServiceUuid,
                                    int aChannel) = 0;
--- a/dom/bluetooth/BluetoothRilListener.h
+++ b/dom/bluetooth/BluetoothRilListener.h
@@ -22,52 +22,58 @@ class BluetoothRilListener;
 
 class IccListener : public nsIIccListener
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIICCLISTENER
 
   IccListener() { }
-  virtual ~IccListener() { }
 
   bool Listen(bool aStart);
   void SetOwner(BluetoothRilListener *aOwner);
 
+protected:
+  virtual ~IccListener() { }
+
 private:
   BluetoothRilListener* mOwner;
 };
 
 class MobileConnectionListener : public nsIMobileConnectionListener
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIMOBILECONNECTIONLISTENER
 
   MobileConnectionListener(uint32_t aClientId)
   : mClientId(aClientId) { }
-  virtual ~MobileConnectionListener() { }
 
   bool Listen(bool aStart);
 
+protected:
+  virtual ~MobileConnectionListener() { }
+
 private:
   uint32_t mClientId;
 };
 
 class TelephonyListener : public nsITelephonyListener
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSITELEPHONYLISTENER
 
   TelephonyListener() { }
-  virtual ~TelephonyListener() { }
 
   bool Listen(bool aStart);
 
+protected:
+  virtual ~TelephonyListener() { }
+
 private:
   nsresult HandleCallInfo(nsITelephonyCallInfo* aInfo, bool aSend);
 };
 
 class BluetoothRilListener
 {
 public:
   BluetoothRilListener();
--- a/dom/bluetooth/BluetoothService.cpp
+++ b/dom/bluetooth/BluetoothService.cpp
@@ -174,16 +174,19 @@ public:
     return NS_OK;
   }
 
   NS_IMETHOD HandleError(const nsAString& aName)
   {
     BT_WARNING("Unable to get value for '" BLUETOOTH_ENABLED_SETTING "'");
     return NS_OK;
   }
+
+protected:
+  ~StartupTask() { }
 };
 
 NS_IMPL_ISUPPORTS(BluetoothService::StartupTask, nsISettingsServiceCallback);
 
 NS_IMPL_ISUPPORTS(BluetoothService, nsIObserver)
 
 bool
 BluetoothService::IsToggling() const
--- a/dom/bluetooth/bluedroid/BluetoothA2dpManager.h
+++ b/dom/bluetooth/bluedroid/BluetoothA2dpManager.h
@@ -30,17 +30,16 @@ public:
     SINK_CONNECTING,
     SINK_CONNECTED,
     SINK_PLAYING,
   };
 
   static BluetoothA2dpManager* Get();
   static void InitA2dpInterface(BluetoothProfileResultHandler* aRes);
   static void DeinitA2dpInterface(BluetoothProfileResultHandler* aRes);
-  virtual ~BluetoothA2dpManager();
 
   void OnConnectError();
   void OnDisconnectError();
 
   // A2DP-specific functions
   void HandleSinkPropertyChanged(const BluetoothSignal& aSignal);
 
   // AVRCP-specific functions
@@ -60,16 +59,19 @@ public:
   uint32_t GetDuration();
   ControlPlayStatus GetPlayStatus();
   uint32_t GetPosition();
   uint64_t GetMediaNumber();
   uint64_t GetTotalMediaNumber();
   void GetTitle(nsAString& aTitle);
   void GetArtist(nsAString& aArtist);
 
+protected:
+  virtual ~BluetoothA2dpManager();
+
 private:
   BluetoothA2dpManager();
   void ResetA2dp();
   void ResetAvrcp();
 
   void HandleShutdown();
   void NotifyConnectionStatusChanged();
 
--- a/dom/bluetooth/bluedroid/BluetoothDaemonSetupInterface.h
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonSetupInterface.h
@@ -11,19 +11,20 @@
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothSetupResultHandler
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothSetupResultHandler)
 
-  virtual ~BluetoothSetupResultHandler();
-
   virtual void OnError(BluetoothStatus aStatus);
   virtual void RegisterModule();
   virtual void UnregisterModule();
   virtual void Configuration();
+
+protected:
+  virtual ~BluetoothSetupResultHandler();
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/bluedroid/BluetoothOppManager.h
+++ b/dom/bluetooth/bluedroid/BluetoothOppManager.h
@@ -38,17 +38,16 @@ public:
   BT_DECL_PROFILE_MGR_BASE
   virtual void GetName(nsACString& aName)
   {
     aName.AssignLiteral("OPP");
   }
 
   static const int MAX_PACKET_LENGTH = 0xFFFE;
 
-  virtual ~BluetoothOppManager();
   static BluetoothOppManager* Get();
   void ClientDataHandler(mozilla::ipc::UnixSocketRawData* aMessage);
   void ServerDataHandler(mozilla::ipc::UnixSocketRawData* aMessage);
 
   bool Listen();
 
   bool SendFile(const nsAString& aDeviceAddress, BlobParent* aActor);
   bool SendFile(const nsAString& aDeviceAddress, nsIDOMBlob* aBlob);
@@ -68,16 +67,19 @@ public:
   // The following functions are inherited from BluetoothSocketObserver
   void ReceiveSocketData(
     BluetoothSocket* aSocket,
     nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
   virtual void OnSocketConnectSuccess(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnSocketConnectError(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnSocketDisconnect(BluetoothSocket* aSocket) MOZ_OVERRIDE;
 
+protected:
+  virtual ~BluetoothOppManager();
+
 private:
   BluetoothOppManager();
   bool Init();
   void HandleShutdown();
   void HandleVolumeStateChanged(nsISupports* aSubject);
 
   void StartFileTransfer();
   void StartSendingNextFile();
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
@@ -1588,16 +1588,23 @@ BluetoothServiceBluedroid::BondStateChan
                                BluetoothValue(true),
                                NS_LITERAL_STRING("Authentication failure"));
         sBondingRunnableArray.RemoveElementAt(0);
       }
       break;
     }
     default:
       BT_WARNING("Got an unhandled status of BondStateChangedCallback!");
+      // Dispatch a reply to unblock the waiting status of pairing.
+      if (!sBondingRunnableArray.IsEmpty()) {
+        DispatchBluetoothReply(sBondingRunnableArray[0],
+                               BluetoothValue(true),
+                               NS_LITERAL_STRING("Internal failure"));
+        sBondingRunnableArray.RemoveElementAt(0);
+      }
       break;
   }
 }
 
 void
 BluetoothServiceBluedroid::AclStateChangedNotification(
   BluetoothStatus aStatus, const nsAString& aRemoteBdAddr, bool aState)
 {
--- a/dom/bluetooth/bluedroid/hfp-fallback/BluetoothHfpManager.h
+++ b/dom/bluetooth/bluedroid/hfp-fallback/BluetoothHfpManager.h
@@ -24,23 +24,25 @@ class BluetoothHfpManager : public Bluet
 public:
   BT_DECL_HFP_MGR_BASE
   virtual void GetName(nsACString& aName)
   {
     aName.AssignLiteral("Fallback HFP/HSP");
   }
 
   static BluetoothHfpManager* Get();
-  virtual ~BluetoothHfpManager() { }
   static void InitHfpInterface(BluetoothProfileResultHandler* aRes);
   static void DeinitHfpInterface(BluetoothProfileResultHandler* aRes);
 
   bool ConnectSco();
   bool DisconnectSco();
 
+protected:
+  virtual ~BluetoothHfpManager() { }
+
 private:
   BluetoothHfpManager() { }
   bool Init();
   void HandleShutdown();
 };
 
 END_BLUETOOTH_NAMESPACE
 
--- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp
@@ -68,17 +68,18 @@ IsValidDtmf(const char aChar) {
 }
 
 static bool
 IsSupportedChld(const int aChld) {
   // We currently only support CHLD=0~3.
   return (aChld >= 0 && aChld <= 3);
 }
 
-class BluetoothHfpManager::GetVolumeTask MOZ_FINAL : public nsISettingsServiceCallback
+class BluetoothHfpManager::GetVolumeTask MOZ_FINAL
+  : public nsISettingsServiceCallback
 {
 public:
   NS_DECL_ISUPPORTS
 
   NS_IMETHOD
   Handle(const nsAString& aName, JS::Handle<JS::Value> aResult)
   {
     MOZ_ASSERT(NS_IsMainThread());
@@ -98,16 +99,19 @@ public:
   }
 
   NS_IMETHOD
   HandleError(const nsAString& aName)
   {
     BT_WARNING("Unable to get value for '" AUDIO_VOLUME_BT_SCO_ID "'");
     return NS_OK;
   }
+
+protected:
+  ~GetVolumeTask() { }
 };
 
 class BluetoothHfpManager::CloseScoTask : public Task
 {
 private:
   void Run() MOZ_OVERRIDE
   {
     MOZ_ASSERT(sBluetoothHfpManager);
--- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.h
+++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.h
@@ -83,17 +83,16 @@ public:
   void OnDisconnectError();
 
   virtual void GetName(nsACString& aName)
   {
     aName.AssignLiteral("HFP/HSP");
   }
 
   static BluetoothHfpManager* Get();
-  virtual ~BluetoothHfpManager();
   static void InitHfpInterface(BluetoothProfileResultHandler* aRes);
   static void DeinitHfpInterface(BluetoothProfileResultHandler* aRes);
 
   bool ConnectSco();
   bool DisconnectSco();
 
   /**
    * @param aSend A boolean indicates whether we need to notify headset or not
@@ -133,16 +132,19 @@ public:
   void CnumNotification(const nsAString& aBdAddress) MOZ_OVERRIDE;
   void CindNotification(const nsAString& aBdAddress) MOZ_OVERRIDE;
   void CopsNotification(const nsAString& aBdAddress) MOZ_OVERRIDE;
   void ClccNotification(const nsAString& aBdAddress) MOZ_OVERRIDE;
   void UnknownAtNotification(const nsACString& aAtString,
                              const nsAString& aBdAddress) MOZ_OVERRIDE;
   void KeyPressedNotification(const nsAString& aBdAddress) MOZ_OVERRIDE;
 
+protected:
+  virtual ~BluetoothHfpManager();
+
 private:
   class GetVolumeTask;
   class CloseScoTask;
   class CloseScoRunnable;
   class RespondToBLDNTask;
   class MainThreadTask;
 
   friend class BluetoothHfpManagerObserver;
--- a/dom/bluetooth/bluez/BluetoothA2dpManager.h
+++ b/dom/bluetooth/bluez/BluetoothA2dpManager.h
@@ -26,17 +26,16 @@ public:
     SINK_UNKNOWN,
     SINK_DISCONNECTED,
     SINK_CONNECTING,
     SINK_CONNECTED,
     SINK_PLAYING,
   };
 
   static BluetoothA2dpManager* Get();
-  virtual ~BluetoothA2dpManager();
 
   // A2DP-specific functions
   void HandleSinkPropertyChanged(const BluetoothSignal& aSignal);
 
   // AVRCP-specific functions
   void SetAvrcpConnected(bool aConnected);
   bool IsAvrcpConnected();
   void UpdateMetaData(const nsAString& aTitle,
@@ -50,16 +49,19 @@ public:
                         ControlPlayStatus aPlayStatus);
   void GetAlbum(nsAString& aAlbum);
   uint32_t GetDuration();
   ControlPlayStatus GetPlayStatus();
   uint32_t GetPosition();
   uint64_t GetMediaNumber();
   void GetTitle(nsAString& aTitle);
 
+protected:
+  virtual ~BluetoothA2dpManager();
+
 private:
   BluetoothA2dpManager();
   bool Init();
   void ResetA2dp();
   void ResetAvrcp();
 
   void HandleShutdown();
   void NotifyConnectionStatusChanged();
--- a/dom/bluetooth/bluez/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/bluez/BluetoothHfpManager.cpp
@@ -191,16 +191,19 @@ public:
   }
 
   NS_IMETHOD
   HandleError(const nsAString& aName)
   {
     BT_WARNING("Unable to get value for '" AUDIO_VOLUME_BT_SCO_ID "'");
     return NS_OK;
   }
+
+protected:
+  ~GetVolumeTask() { }
 };
 
 NS_IMPL_ISUPPORTS(BluetoothHfpManager::GetVolumeTask,
                   nsISettingsServiceCallback);
 
 NS_IMETHODIMP
 BluetoothHfpManager::Observe(nsISupports* aSubject,
                              const char* aTopic,
--- a/dom/bluetooth/bluez/BluetoothHfpManager.h
+++ b/dom/bluetooth/bluez/BluetoothHfpManager.h
@@ -81,17 +81,16 @@ class BluetoothHfpManager : public Bluet
 public:
   BT_DECL_HFP_MGR_BASE
   virtual void GetName(nsACString& aName)
   {
     aName.AssignLiteral("HFP/HSP");
   }
 
   static BluetoothHfpManager* Get();
-  ~BluetoothHfpManager();
 
   // The following functions are inherited from BluetoothSocketObserver
   virtual void ReceiveSocketData(
     BluetoothSocket* aSocket,
     nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
   virtual void OnSocketConnectSuccess(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnSocketConnectError(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnSocketDisconnect(BluetoothSocket* aSocket) MOZ_OVERRIDE;
@@ -126,16 +125,19 @@ public:
 
   // CDMA-specific functions
   void UpdateSecondNumber(const nsAString& aNumber);
   void AnswerWaitingCall();
   void IgnoreWaitingCall();
   void ToggleCalls();
 #endif
 
+protected:
+  ~BluetoothHfpManager();
+
 private:
   void ParseAtCommand(const nsACString& aAtCommand, const int aStart,
                       nsTArray<nsCString>& aRetValues);
 
   class CloseScoTask;
   class GetVolumeTask;
 #ifdef MOZ_B2G_RIL
   class RespondToBLDNTask;
--- a/dom/bluetooth/bluez/BluetoothOppManager.h
+++ b/dom/bluetooth/bluez/BluetoothOppManager.h
@@ -38,17 +38,16 @@ public:
   BT_DECL_PROFILE_MGR_BASE
   virtual void GetName(nsACString& aName)
   {
     aName.AssignLiteral("OPP");
   }
 
   static const int MAX_PACKET_LENGTH = 0xFFFE;
 
-  virtual ~BluetoothOppManager();
   static BluetoothOppManager* Get();
   void ClientDataHandler(mozilla::ipc::UnixSocketRawData* aMessage);
   void ServerDataHandler(mozilla::ipc::UnixSocketRawData* aMessage);
 
   bool Listen();
 
   bool SendFile(const nsAString& aDeviceAddress, BlobParent* aActor);
   bool SendFile(const nsAString& aDeviceAddress, nsIDOMBlob* aBlob);
@@ -68,16 +67,19 @@ public:
   // The following functions are inherited from BluetoothSocketObserver
   void ReceiveSocketData(
     BluetoothSocket* aSocket,
     nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
   virtual void OnSocketConnectSuccess(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnSocketConnectError(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnSocketDisconnect(BluetoothSocket* aSocket) MOZ_OVERRIDE;
 
+protected:
+  virtual ~BluetoothOppManager();
+
 private:
   BluetoothOppManager();
   bool Init();
   void HandleShutdown();
 
   void StartFileTransfer();
   void StartSendingNextFile();
   void FileTransferComplete();
--- a/dom/camera/CameraPreferences.cpp
+++ b/dom/camera/CameraPreferences.cpp
@@ -40,52 +40,68 @@ NS_IMPL_ISUPPORTS(CameraPreferences, nsI
 /* static */
 nsresult
 CameraPreferences::UpdatePref(const char* aPref, nsresult& aVal)
 {
   uint32_t val;
   nsresult rv = Preferences::GetUint(aPref, &val);
   if (NS_SUCCEEDED(rv)) {
     aVal = static_cast<nsresult>(val);
+  } else if(rv == NS_ERROR_UNEXPECTED) {
+    // Preference does not exist
+    rv = NS_OK;
+    aVal = NS_OK;
   }
   return rv;
 }
 
 /* static */
 nsresult
 CameraPreferences::UpdatePref(const char* aPref, uint32_t& aVal)
 {
   uint32_t val;
   nsresult rv = Preferences::GetUint(aPref, &val);
   if (NS_SUCCEEDED(rv)) {
     aVal = val;
+  } else if(rv == NS_ERROR_UNEXPECTED) {
+    // Preference does not exist
+    rv = NS_OK;
+    aVal = 0;
   }
   return rv;
 }
 
 /* static */
 nsresult
 CameraPreferences::UpdatePref(const char* aPref, nsACString& aVal)
 {
   nsCString val;
   nsresult rv = Preferences::GetCString(aPref, &val);
   if (NS_SUCCEEDED(rv)) {
     aVal = val;
+  } else if(rv == NS_ERROR_UNEXPECTED) {
+    // Preference does not exist
+    rv = NS_OK;
+    aVal.Truncate();
   }
   return rv;
 }
 
 /* static */
 nsresult
 CameraPreferences::UpdatePref(const char* aPref, bool& aVal)
 {
   bool val;
   nsresult rv = Preferences::GetBool(aPref, &val);
   if (NS_SUCCEEDED(rv)) {
     aVal = val;
+  } else if(rv == NS_ERROR_UNEXPECTED) {
+    // Preference does not exist
+    rv = NS_OK;
+    aVal = false;
   }
   return rv;
 }
 
 /* static */
 CameraPreferences::Pref CameraPreferences::sPrefs[] = {
   {
     "camera.control.test.enabled",
new file mode 100644
--- /dev/null
+++ b/dom/camera/CameraTestHardware.js
@@ -0,0 +1,214 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
+
+const MOZ_CAMERATESTHW_CONTRACTID = "@mozilla.org/cameratesthardware;1";
+const MOZ_CAMERATESTHW_CID        = Components.ID("{fcb7b4cd-689e-453c-8a2c-611a45fa09ac}");
+const DEBUG = false;
+
+function debug(msg) {
+  if (DEBUG) {
+    dump('-*- MozCameraTestHardware: ' + msg + '\n');
+  }
+}
+
+function MozCameraTestHardware() {
+  this._params = {};
+}
+
+MozCameraTestHardware.prototype = {
+  classID:        MOZ_CAMERATESTHW_CID,
+  contractID:     MOZ_CAMERATESTHW_CONTRACTID,
+
+  classInfo:      XPCOMUtils.generateCI({classID: MOZ_CAMERATESTHW_CID,
+                                         contractID: MOZ_CAMERATESTHW_CONTRACTID,
+                                         flags: Ci.nsIClassInfo.SINGLETON,
+                                         interfaces: [Ci.nsICameraTestHardware]}),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsICameraTestHardware]),
+
+  _params: null,
+  _window: null,
+  _mock: null,
+  _handler: null,
+
+  attach: function(mock) {
+    /* Waive xrays permits us to call functions provided to us
+       in the mock */
+    this._mock = Components.utils.waiveXrays(mock);
+  },
+
+  detach: function() {
+    this._mock = null;
+  },
+
+  /* Trigger a delegate handler attached to the test hardware
+     if given via attach. If there is no delegate attached, or
+     it does not provide a handler for this specific operation,
+     or the handler returns true, it will execute the default
+     behaviour. The handler may throw an exception in order to
+     return an error code from the driver call. */
+  _delegate: function(prop) {
+    return (this._mock && this._mock[prop] && !this._mock[prop]());
+  },
+
+  get params() {
+    return this._params;
+  },
+
+  set params(aParams) {
+    this._params = aParams;
+  },
+
+  setHandler: function(handler) {
+    this._handler = handler;
+  },
+
+  dispatchEvent: function(evt) {
+    if (this._handler) {
+      this._handler.handleEvent(evt);
+    }
+  },
+
+  reset: function(aWindow) {
+    this._window = aWindow;
+    this._mock = null;
+    this._params = {};
+  },
+
+  initCamera: function() {
+    this._delegate('init');
+  },
+
+  pushParameters: function(params) {
+    let oldParams = this._params;
+    this._params = {};
+    let s = params.split(';');
+    for(let i = 0; i < s.length; ++i) {
+      let parts = s[i].split('=');
+      if (parts.length == 2) {
+        this._params[parts[0]] = parts[1];
+      }
+    }
+    try {
+      this._delegate('pushParameters');
+    } catch(e) {
+      this._params = oldParams;
+      throw e;
+    }
+  },
+
+  pullParameters: function() {
+    this._delegate('pullParameters');
+    let ret = "";
+    for(let p in this._params) {
+      ret += p + "=" + this._params[p] + ";";
+    }
+    return ret;
+  },
+
+  autoFocus: function() {
+    if (!this._delegate('autoFocus')) {
+      this.fireAutoFocusComplete(true);
+    }
+  },
+
+  fireAutoFocusMoving: function(moving) {
+    let evt = new this._window.CameraStateChangeEvent('focus', { 'newState': moving ? 'focusing' : 'not_focusing' } );
+    this.dispatchEvent(evt);
+  },
+
+  fireAutoFocusComplete: function(state) {
+    let evt = new this._window.CameraStateChangeEvent('focus', { 'newState': state ? 'focused' : 'unfocused' } );
+    this.dispatchEvent(evt);
+  },
+
+  cancelAutoFocus: function() {
+    this._delegate('cancelAutoFocus');
+  },
+
+  fireShutter: function() {
+    let evt = new this._window.Event('shutter');
+    this.dispatchEvent(evt);
+  },
+
+  takePicture: function() {
+    if (!this._delegate('takePicture')) {
+      this.fireTakePictureComplete(new this._window.Blob(['foobar'], {'type': 'jpeg'}));
+    }
+  },
+
+  fireTakePictureComplete: function(blob) {
+    let evt = new this._window.BlobEvent('picture', {'data': blob});
+    this.dispatchEvent(evt);
+  },
+
+  fireTakePictureError: function() {
+    let evt = new this._window.ErrorEvent('error', {'message': 'picture'});
+    this.dispatchEvent(evt);
+  },
+
+  cancelTakePicture: function() {
+    this._delegate('cancelTakePicture');
+  },
+
+  startPreview: function() {
+    this._delegate('startPreview');
+  },
+
+  stopPreview: function() {
+    this._delegate('stopPreview');
+  },
+
+  startFaceDetection: function() {
+    this._delegate('startFaceDetection');
+  },
+
+  stopFaceDetection: function() {
+    this._delegate('stopFaceDetection');
+  },
+
+  fireFacesDetected: function(faces) {
+    /* This works around the fact that we can't have references to
+       dictionaries in a dictionary in WebIDL; we provide a boolean
+       to indicate whether or not the values for those features are
+       actually valid. */
+    let facesIf = [];
+    if (typeof(faces) === 'object' && typeof(faces.faces) === 'object') {
+      let self = this;
+      faces.faces.forEach(function(face) {
+        face.hasLeftEye = face.hasOwnProperty('leftEye') && face.leftEye != null;
+        face.hasRightEye = face.hasOwnProperty('rightEye') && face.rightEye != null;
+        face.hasMouth = face.hasOwnProperty('mouth') && face.mouth != null;
+        facesIf.push(new self._window.CameraDetectedFace(face));
+      });
+    }
+
+    let evt = new this._window.CameraFacesDetectedEvent('facesdetected', {'faces': facesIf});
+    this.dispatchEvent(evt);
+  },
+
+  startRecording: function() {
+    this._delegate('startRecording');
+  },
+
+  stopRecording: function() {
+    this._delegate('stopRecording');
+  },
+
+  fireSystemError: function() {
+    let evt = new this._window.ErrorEvent('error', {'message': 'system'});
+    this.dispatchEvent(evt);
+  },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MozCameraTestHardware]);
new file mode 100644
--- /dev/null
+++ b/dom/camera/CameraTestHardware.manifest
@@ -0,0 +1,2 @@
+component {fcb7b4cd-689e-453c-8a2c-611a45fa09ac} CameraTestHardware.js
+contract @mozilla.org/cameratesthardware;1 {fcb7b4cd-689e-453c-8a2c-611a45fa09ac}
--- a/dom/camera/DOMCameraDetectedFace.cpp
+++ b/dom/camera/DOMCameraDetectedFace.cpp
@@ -26,16 +26,50 @@ DOMCameraDetectedFace::HasSupport(JSCont
 }
 
 JSObject*
 DOMCameraDetectedFace::WrapObject(JSContext* aCx)
 {
   return CameraDetectedFaceBinding::Wrap(aCx, this);
 }
 
+/* static */
+already_AddRefed<DOMCameraDetectedFace>
+DOMCameraDetectedFace::Constructor(const GlobalObject& aGlobal,
+                                   const dom::CameraDetectedFaceInit& aFace,
+                                   ErrorResult& aRv)
+{
+  nsRefPtr<DOMCameraDetectedFace> face =
+    new DOMCameraDetectedFace(aGlobal.GetAsSupports(), aFace);
+  return face.forget();
+}
+
+DOMCameraDetectedFace::DOMCameraDetectedFace(nsISupports* aParent,
+                                             const dom::CameraDetectedFaceInit& aFace)
+  : mParent(aParent)
+  , mId(aFace.mId)
+  , mScore(aFace.mScore)
+  , mBounds(new DOMRect(this))
+{
+  mBounds->SetRect(aFace.mBounds.mLeft,
+                   aFace.mBounds.mTop,
+                   aFace.mBounds.mRight - aFace.mBounds.mLeft,
+                   aFace.mBounds.mBottom - aFace.mBounds.mTop);
+
+  if (aFace.mHasLeftEye) {
+    mLeftEye = new DOMPoint(this, aFace.mLeftEye.mX, aFace.mLeftEye.mY);
+  }
+  if (aFace.mHasRightEye) {
+    mRightEye = new DOMPoint(this, aFace.mRightEye.mX, aFace.mRightEye.mY);
+  }
+  if (aFace.mHasMouth) {
+    mMouth = new DOMPoint(this, aFace.mMouth.mX, aFace.mMouth.mY);
+  }
+}
+
 DOMCameraDetectedFace::DOMCameraDetectedFace(nsISupports* aParent,
                                              const ICameraControl::Face& aFace)
   : mParent(aParent)
   , mId(aFace.id)
   , mScore(aFace.score)
   , mBounds(new DOMRect(this))
 {
   mBounds->SetRect(aFace.bound.left,
--- a/dom/camera/DOMCameraDetectedFace.h
+++ b/dom/camera/DOMCameraDetectedFace.h
@@ -25,16 +25,20 @@ public:
 
   // Because this header's filename doesn't match its C++ or DOM-facing
   // classname, we can't rely on the [Func="..."] WebIDL tag to implicitly
   // include the right header for us; instead we must explicitly include a
   // HasSupport() method in each header. We can get rid of these with the
   // Great Renaming proposed in bug 983177.
   static bool HasSupport(JSContext* aCx, JSObject* aGlobal);
 
+  static already_AddRefed<DOMCameraDetectedFace> Constructor(const GlobalObject& aGlobal,
+                                                             const dom::CameraDetectedFaceInit& aFace,
+                                                             ErrorResult& aRv);
+
   DOMCameraDetectedFace(nsISupports* aParent, const ICameraControl::Face& aFace);
 
   uint32_t Id()       { return mId; }
   uint32_t Score()    { return mScore; }
   bool HasLeftEye()   { return mLeftEye; }
   bool HasRightEye()  { return mRightEye; }
   bool HasMouth()     { return mMouth; }
 
@@ -49,16 +53,17 @@ public:
   {
     MOZ_ASSERT(mParent);
     return mParent;
   }
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
 protected:
+  DOMCameraDetectedFace(nsISupports* aParent, const dom::CameraDetectedFaceInit& aFace);
   virtual ~DOMCameraDetectedFace() { }
 
   nsCOMPtr<nsISupports> mParent;
 
   uint32_t mId;
   uint32_t mScore;
 
   nsRefPtr<dom::DOMRect> mBounds;
--- a/dom/camera/DOMCameraManager.cpp
+++ b/dom/camera/DOMCameraManager.cpp
@@ -12,17 +12,16 @@
 #include "nsIContentPermissionPrompt.h"
 #include "nsIObserverService.h"
 #include "nsIPermissionManager.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "DOMCameraControl.h"
 #include "nsDOMClassInfo.h"
 #include "CameraCommon.h"
 #include "mozilla/dom/BindingUtils.h"
-#include "mozilla/dom/CameraManagerBinding.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMCameraManager, mWindow)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCameraManager)
--- a/dom/camera/GonkCameraHwMgr.cpp
+++ b/dom/camera/GonkCameraHwMgr.cpp
@@ -1,10 +1,10 @@
 /*
- * Copyright (C) 2012-2014 Mozilla Foundation
+ * Copyright (C) 2012-2015 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
@@ -216,28 +216,33 @@ GonkCameraHardware::Init()
 #endif
 
   return NS_OK;
 }
 
 sp<GonkCameraHardware>
 GonkCameraHardware::Connect(mozilla::nsGonkCameraControl* aTarget, uint32_t aCameraId)
 {
-#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 18
-  sp<Camera> camera = Camera::connect(aCameraId, /* clientPackageName */String16("gonk.camera"), Camera::USE_CALLING_UID);
-#else
-  sp<Camera> camera = Camera::connect(aCameraId);
-#endif
-
-  if (camera.get() == nullptr) {
-    return nullptr;
-  }
+  sp<Camera> camera;
 
   nsCString test;
   CameraPreferences::GetPref("camera.control.test.enabled", test);
+
+  if (!test.EqualsASCII("hardware")) {
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 18
+    camera = Camera::connect(aCameraId, /* clientPackageName */String16("gonk.camera"), Camera::USE_CALLING_UID);
+#else
+    camera = Camera::connect(aCameraId);
+#endif
+
+    if (camera.get() == nullptr) {
+      return nullptr;
+    }
+  }
+
   sp<GonkCameraHardware> cameraHardware;
   if (test.EqualsASCII("hardware")) {
     NS_WARNING("Using test Gonk hardware layer");
     cameraHardware = new TestGonkCameraHardware(aTarget, aCameraId, camera);
   } else {
     cameraHardware = new GonkCameraHardware(aTarget, aCameraId, camera);
   }
 
@@ -252,18 +257,20 @@ GonkCameraHardware::Connect(mozilla::nsG
 }
 
 void
 GonkCameraHardware::Close()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, (void*)this);
 
   mClosing = true;
-  mCamera->stopPreview();
-  mCamera->disconnect();
+  if (mCamera.get()) {
+    mCamera->stopPreview();
+    mCamera->disconnect();
+  }
   if (mNativeWindow.get()) {
     mNativeWindow->abandon();
   }
   mCamera.clear();
   mNativeWindow.clear();
 
   // Ensure that ICamera's destructor is actually executed
   IPCThreadState::self()->flushCommands();
--- a/dom/camera/TestGonkCameraHardware.cpp
+++ b/dom/camera/TestGonkCameraHardware.cpp
@@ -1,538 +1,720 @@
 /*
- * Copyright (C) 2013-2014 Mozilla Foundation
+ * Copyright (C) 2013-2015 Mozilla Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "TestGonkCameraHardware.h"
-
 #include "CameraPreferences.h"
 #include "nsThreadUtils.h"
+#include "mozilla/dom/EventListenerBinding.h"
+#include "mozilla/dom/BlobEvent.h"
+#include "mozilla/dom/ErrorEvent.h"
+#include "mozilla/dom/CameraFacesDetectedEvent.h"
+#include "mozilla/dom/CameraStateChangeEvent.h"
+#include "DOMCameraDetectedFace.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsICameraTestHardware.h"
 
 using namespace android;
 using namespace mozilla;
+using namespace mozilla::dom;
+
+static void
+CopyFaceFeature(int32_t (&aDst)[2], bool aExists, const DOMPoint* aSrc)
+{
+  if (aExists && aSrc) {
+    aDst[0] = static_cast<int32_t>(aSrc->X());
+    aDst[1] = static_cast<int32_t>(aSrc->Y());
+  } else {
+    aDst[0] = -2000;
+    aDst[1] = -2000;
+  }
+}
+
+class TestGonkCameraHardwareListener : public nsIDOMEventListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMEVENTLISTENER
+
+  TestGonkCameraHardwareListener(nsGonkCameraControl* aTarget, nsIThread* aCameraThread)
+    : mTarget(aTarget)
+    , mCameraThread(aCameraThread)
+  {
+    MOZ_COUNT_CTOR(TestGonkCameraHardwareListener);
+  }
+
+protected:
+  virtual ~TestGonkCameraHardwareListener()
+  {
+    MOZ_COUNT_DTOR(TestGonkCameraHardwareListener);
+  }
+
+  nsRefPtr<nsGonkCameraControl> mTarget;
+  nsCOMPtr<nsIThread> mCameraThread;
+};
+
+NS_IMETHODIMP
+TestGonkCameraHardwareListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+  nsString eventType;
+  aEvent->GetType(eventType);
+
+  DOM_CAMERA_LOGI("Inject '%s' event",
+    NS_ConvertUTF16toUTF8(eventType).get());
+
+  if (eventType.EqualsLiteral("focus")) {
+    CameraStateChangeEvent* event = aEvent->InternalDOMEvent()->AsCameraStateChangeEvent();
+
+    if (!NS_WARN_IF(!event)) {
+      nsString state;
+
+      event->GetNewState(state);
+      if (state.EqualsLiteral("focused")) {
+        OnAutoFocusComplete(mTarget, true);
+      } else if (state.EqualsLiteral("unfocused")) {
+        OnAutoFocusComplete(mTarget, false);
+      } else if (state.EqualsLiteral("focusing")) {
+        OnAutoFocusMoving(mTarget, true);
+      } else if (state.EqualsLiteral("not_focusing")) {
+        OnAutoFocusMoving(mTarget, false);
+      } else {
+        DOM_CAMERA_LOGE("Unhandled focus state '%s'\n",
+          NS_ConvertUTF16toUTF8(state).get());
+      }
+    }
+  } else if (eventType.EqualsLiteral("shutter")) {
+    DOM_CAMERA_LOGI("Inject shutter event");
+    OnShutter(mTarget);
+  } else if (eventType.EqualsLiteral("picture")) {
+    BlobEvent* event = aEvent->InternalDOMEvent()->AsBlobEvent();
+
+    if (!NS_WARN_IF(!event)) {
+      File* file = event->GetData();
+
+      if (file) {
+        static const uint64_t MAX_FILE_SIZE = 2147483647;
+        uint64_t dataLength = 0;
+        nsresult rv = file->GetSize(&dataLength);
+
+        if (NS_WARN_IF(NS_FAILED(rv) || dataLength > MAX_FILE_SIZE)) {
+          return NS_OK;
+        }
+
+        nsCOMPtr<nsIInputStream> inputStream;
+        rv = file->GetInternalStream(getter_AddRefs(inputStream));
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return NS_OK;
+        }
+
+        uint8_t* data = new uint8_t[dataLength];
+        rv = NS_ReadInputStreamToBuffer(inputStream,
+                                        reinterpret_cast<void**>(&data),
+                                        static_cast<uint32_t>(dataLength));
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          delete [] data;
+          return NS_OK;
+        }
+
+        OnTakePictureComplete(mTarget, data, dataLength);
+        delete [] data;
+      } else {
+        OnTakePictureComplete(mTarget, nullptr, 0);
+      }
+    }
+  } else if(eventType.EqualsLiteral("error")) {
+    ErrorEvent* event = aEvent->InternalDOMEvent()->AsErrorEvent();
+
+    if (!NS_WARN_IF(!event)) {
+      nsString errorType;
+
+      event->GetMessage(errorType);
+      if (errorType.EqualsLiteral("picture")) {
+        OnTakePictureError(mTarget);
+      } else if (errorType.EqualsLiteral("system")) {
+        if (!NS_WARN_IF(!mCameraThread)) {
+          class DeferredSystemFailure : public nsRunnable
+          {
+          public:
+            DeferredSystemFailure(nsGonkCameraControl* aTarget)
+              : mTarget(aTarget)
+            { }
+
+            NS_IMETHODIMP
+            Run()
+            {
+              OnSystemError(mTarget, CameraControlListener::kSystemService, 100, 0);
+              return NS_OK;
+            }
+
+          protected:
+            nsRefPtr<nsGonkCameraControl> mTarget;
+          };
+
+          mCameraThread->Dispatch(new DeferredSystemFailure(mTarget), NS_DISPATCH_NORMAL);
+        }
+      } else {
+        DOM_CAMERA_LOGE("Unhandled error event type '%s'\n",
+          NS_ConvertUTF16toUTF8(errorType).get());
+      }
+    }
+  } else if(eventType.EqualsLiteral("facesdetected")) {
+    CameraFacesDetectedEvent* event = aEvent->InternalDOMEvent()->AsCameraFacesDetectedEvent();
+
+    if (!NS_WARN_IF(!event)) {
+      Nullable<nsTArray<nsRefPtr<DOMCameraDetectedFace>>> faces;
+      event->GetFaces(faces);
+
+      camera_frame_metadata_t metadata;
+      memset(&metadata, 0, sizeof(metadata));
+
+      if (faces.IsNull()) {
+        OnFacesDetected(mTarget, &metadata);
+      } else {
+        const nsTArray<nsRefPtr<DOMCameraDetectedFace>>& facesData = faces.Value();
+        uint32_t i = facesData.Length();
+
+        metadata.number_of_faces = i;
+        metadata.faces = new camera_face_t[i];
+        memset(metadata.faces, 0, sizeof(camera_face_t) * i);
+
+        while (i > 0) {
+          --i;
+          const nsRefPtr<DOMCameraDetectedFace>& face = facesData[i];
+          camera_face_t& f = metadata.faces[i];
+          const DOMRect& bounds = *face->Bounds();
+          f.rect[0] = static_cast<int32_t>(bounds.Left());
+          f.rect[1] = static_cast<int32_t>(bounds.Top());
+          f.rect[2] = static_cast<int32_t>(bounds.Right());
+          f.rect[3] = static_cast<int32_t>(bounds.Bottom());
+          CopyFaceFeature(f.left_eye, face->HasLeftEye(), face->GetLeftEye());
+          CopyFaceFeature(f.right_eye, face->HasRightEye(), face->GetRightEye());
+          CopyFaceFeature(f.mouth, face->HasMouth(), face->GetMouth());
+          f.id = face->Id();
+          f.score = face->Score();
+        }
+
+        OnFacesDetected(mTarget, &metadata);
+        delete [] metadata.faces;
+      }
+    }
+  } else {
+    DOM_CAMERA_LOGE("Unhandled injected event '%s'",
+      NS_ConvertUTF16toUTF8(eventType).get());
+  }
+
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(TestGonkCameraHardwareListener, nsIDOMEventListener)
+
+class TestGonkCameraHardware::ControlMessage : public nsRunnable
+{
+public:
+  ControlMessage(TestGonkCameraHardware* aTestHw)
+    : mTestHw(aTestHw)
+  { }
+
+  NS_IMETHOD
+  Run() MOZ_OVERRIDE
+  {
+    if (NS_WARN_IF(!mTestHw)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    MutexAutoLock lock(mTestHw->mMutex);
+
+    mTestHw->mStatus = RunInline();
+    nsresult rv = mTestHw->mCondVar.Notify();
+    NS_WARN_IF(NS_FAILED(rv));
+    return NS_OK;
+  }
+
+  nsresult
+  RunInline()
+  {
+    if (NS_WARN_IF(!mTestHw)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    nsresult rv;
+    mJSTestWrapper = do_GetService("@mozilla.org/cameratesthardware;1", &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      DOM_CAMERA_LOGE("Cannot get camera test service\n");
+      return rv;
+    }
+
+    rv = RunImpl();
+    mJSTestWrapper = nullptr;
+    return rv;
+  }
+
+protected:
+  NS_IMETHOD RunImpl() = 0;
+  virtual ~ControlMessage() { }
+
+  nsCOMPtr<nsICameraTestHardware> mJSTestWrapper;
+
+  /* Since we block the control thread until we have finished
+     processing the request on the main thread, we know that this
+     pointer will not go out of scope because the control thread
+     and calling class is the owner. */
+  TestGonkCameraHardware* mTestHw;
+};
 
 TestGonkCameraHardware::TestGonkCameraHardware(nsGonkCameraControl* aTarget,
                                                uint32_t aCameraId,
                                                const sp<Camera>& aCamera)
   : GonkCameraHardware(aTarget, aCameraId, aCamera)
+  , mMutex("TestGonkCameraHardware::mMutex")
+  , mCondVar(mMutex, "TestGonkCameraHardware::mCondVar")
 {
   DOM_CAMERA_LOGA("v===== Created TestGonkCameraHardware =====v\n");
   DOM_CAMERA_LOGT("%s:%d : this=%p (aTarget=%p)\n",
     __func__, __LINE__, this, aTarget);
   MOZ_COUNT_CTOR(TestGonkCameraHardware);
+  mCameraThread = NS_GetCurrentThread();
 }
 
 TestGonkCameraHardware::~TestGonkCameraHardware()
 {
   MOZ_COUNT_DTOR(TestGonkCameraHardware);
+
+  class Delegate : public ControlMessage
+  {
+  public:
+    Delegate(TestGonkCameraHardware* aTestHw)
+      : ControlMessage(aTestHw)
+    { }
+
+  protected:
+    NS_IMETHOD
+    RunImpl() MOZ_OVERRIDE
+    {
+      if (mTestHw->mDomListener) {
+        mTestHw->mDomListener = nullptr;
+        nsresult rv = mJSTestWrapper->SetHandler(nullptr);
+        NS_WARN_IF(NS_FAILED(rv));
+      }
+      return NS_OK;
+    }
+  };
+
+  nsresult rv = WaitWhileRunningOnMainThread(new Delegate(this));
+  NS_WARN_IF(NS_FAILED(rv));
   DOM_CAMERA_LOGA("^===== Destroyed TestGonkCameraHardware =====^\n");
 }
 
-void
-TestGonkCameraHardware::InjectFakeSystemFailure()
+nsresult
+TestGonkCameraHardware::WaitWhileRunningOnMainThread(nsRefPtr<ControlMessage> aRunnable)
 {
-  DOM_CAMERA_LOGA("====== Fake Camera Hardware Failure ======\n");
-  // The values '100' and '0' below seem to be what the AOSP layer
-  // throws back when the mediaserver process fails.
-  OnSystemError(mTarget, CameraControlListener::kSystemService, 100, 0);
+  MutexAutoLock lock(mMutex);
+
+  if (NS_WARN_IF(!aRunnable)) {
+    mStatus = NS_ERROR_INVALID_ARG;
+  } else if (!NS_IsMainThread()) {
+    nsresult rv = NS_DispatchToMainThread(aRunnable);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = mCondVar.Wait();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  } else {
+    /* Cannot dispatch to main thread since we would block on
+       the condvar, so we need to run inline. */
+    mStatus = aRunnable->RunInline();
+  }
+  return mStatus;
 }
 
 nsresult
 TestGonkCameraHardware::Init()
 {
-  class DeferredSystemFailure : public nsRunnable
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+
+  class Delegate : public ControlMessage
   {
   public:
-    DeferredSystemFailure(TestGonkCameraHardware* aCameraHw)
-      : mCameraHw(aCameraHw)
+    Delegate(TestGonkCameraHardware* aTestHw)
+      : ControlMessage(aTestHw)
     { }
 
-    NS_IMETHODIMP
-    Run()
+  protected:
+    NS_IMETHOD
+    RunImpl() MOZ_OVERRIDE
     {
-      mCameraHw->InjectFakeSystemFailure();
+      nsresult rv = mJSTestWrapper->InitCamera();
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      mTestHw->mDomListener = new TestGonkCameraHardwareListener(mTestHw->mTarget, mTestHw->mCameraThread);
+      if (NS_WARN_IF(!mTestHw->mDomListener)) {
+        return NS_ERROR_FAILURE;
+      }
+
+      rv = mJSTestWrapper->SetHandler(mTestHw->mDomListener);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
       return NS_OK;
     }
-
-  protected:
-    android::sp<TestGonkCameraHardware> mCameraHw;
   };
 
-  if (IsTestCase("init-failure")) {
-    return NS_ERROR_NOT_INITIALIZED;
-  }
-  if (IsTestCase("post-init-system-failure")) {
-    nsCOMPtr<nsIThread> me = NS_GetCurrentThread();
-    if (me) {
-      me->Dispatch(new DeferredSystemFailure(this), NS_DISPATCH_NORMAL);
-    }
-  }
-
-  return GonkCameraHardware::Init();
-}
-
-const nsCString
-TestGonkCameraHardware::TestCase()
-{
-  nsCString test;
-  CameraPreferences::GetPref("camera.control.test.hardware", test);
-  return test;
-}
-
-const nsCString
-TestGonkCameraHardware::GetExtraParameters()
-{
-  /**
-   * The contents of this pref are appended to the flattened string of
-   * parameters stuffed into GonkCameraParameters by the camera library.
-   * It consists of semicolon-delimited key=value pairs, e.g.
-   *
-   *   focus-mode=auto;flash-mode=auto;preview-size=1024x768
-   *
-   * The unflattening process breaks this string up on semicolon boundaries
-   * and sets an entry in a hashtable of strings with the token before
-   * the equals sign as the key, and the token after as the value. Because
-   * the string is parsed in order, key=value pairs occuring later in the
-   * string will replace value pairs appearing earlier, making it easy to
-   * inject fake, testable values into the parameters table.
-   *
-   * One constraint of this approach is that neither the key nor the value
-   * may contain equals signs or semicolons. We don't enforce that here
-   * so that we can also test correct handling of improperly-formatted values.
-   */
-  nsCString parameters;
-  CameraPreferences::GetPref("camera.control.test.hardware.gonk.parameters", parameters);
-  DOM_CAMERA_LOGA("TestGonkCameraHardware : extra-parameters '%s'\n",
-    parameters.get());
-  return parameters;
-}
-
-bool
-TestGonkCameraHardware::IsTestCaseInternal(const char* aTest, const char* aFile, int aLine)
-{
-  if (TestCase().EqualsASCII(aTest)) {
-    DOM_CAMERA_LOGA("TestGonkCameraHardware : test-case '%s' (%s:%d)\n",
-      aTest, aFile, aLine);
-    return true;
-  }
-
-  return false;
-}
-
-int
-TestGonkCameraHardware::TestCaseError(int aDefaultError)
-{
-  // for now, just return the default error
-  return aDefaultError;
+  nsresult rv = WaitWhileRunningOnMainThread(new Delegate(this));
+  NS_WARN_IF(NS_FAILED(rv));
+  return rv;
 }
 
 int
 TestGonkCameraHardware::AutoFocus()
 {
-  class AutoFocusFailure : public nsRunnable
+  class Delegate : public ControlMessage
   {
   public:
-    AutoFocusFailure(nsGonkCameraControl* aTarget)
-      : mTarget(aTarget)
+    Delegate(TestGonkCameraHardware* aTestHw)
+      : ControlMessage(aTestHw)
     { }
 
-    NS_IMETHODIMP
-    Run()
+  protected:
+    NS_IMETHOD
+    RunImpl() MOZ_OVERRIDE
     {
-      OnAutoFocusComplete(mTarget, false);
-      return NS_OK;
+      return mJSTestWrapper->AutoFocus();
     }
-
-  protected:
-    nsGonkCameraControl* mTarget;
   };
 
-  if (IsTestCase("auto-focus-failure")) {
-    return TestCaseError(UNKNOWN_ERROR);
-  }
-  if (IsTestCase("auto-focus-process-failure")) {
-    nsresult rv = NS_DispatchToCurrentThread(new AutoFocusFailure(mTarget));
-    if (NS_SUCCEEDED(rv)) {
-      return OK;
-    }
-    DOM_CAMERA_LOGE("Failed to dispatch AutoFocusFailure runnable (0x%08x)\n", rv);
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  nsresult rv = WaitWhileRunningOnMainThread(new Delegate(this));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     return UNKNOWN_ERROR;
   }
-
-  return GonkCameraHardware::AutoFocus();
+  return OK;
 }
 
-// These classes have to be external to StartFaceDetection(), at least
-// until we pick up gcc 4.5, which supports local classes as template
-// arguments.
-class FaceDetected : public nsRunnable
-{
-public:
-  FaceDetected(nsGonkCameraControl* aTarget)
-    : mTarget(aTarget)
-  { }
-
-  ~FaceDetected()
-  {
-    ReleaseFacesArray();
-  }
-
-  NS_IMETHODIMP
-  Run()
-  {
-    InitMetaData();
-    OnFacesDetected(mTarget, &mMetaData);
-    return NS_OK;
-  }
-
-protected:
-  virtual nsresult InitMetaData() = 0;
-
-  nsresult
-  AllocateFacesArray(uint32_t num)
-  {
-    mMetaData.faces = new camera_face_t[num];
-    return NS_OK;
-  }
-
-  nsresult
-  ReleaseFacesArray()
-  {
-    delete [] mMetaData.faces;
-    mMetaData.faces = nullptr;
-    return NS_OK;
-  }
-
-  nsRefPtr<nsGonkCameraControl> mTarget;
-  camera_frame_metadata_t mMetaData;
-};
-
-class OneFaceDetected : public FaceDetected
-{
-public:
-  OneFaceDetected(nsGonkCameraControl* aTarget)
-    : FaceDetected(aTarget)
-  { }
-
-  nsresult
-  InitMetaData() MOZ_OVERRIDE
-  {
-    mMetaData.number_of_faces = 1;
-    AllocateFacesArray(1);
-    mMetaData.faces[0].id = 1;
-    mMetaData.faces[0].score = 2;
-    mMetaData.faces[0].rect[0] = 3;
-    mMetaData.faces[0].rect[1] = 4;
-    mMetaData.faces[0].rect[2] = 5;
-    mMetaData.faces[0].rect[3] = 6;
-    mMetaData.faces[0].left_eye[0] = 7;
-    mMetaData.faces[0].left_eye[1] = 8;
-    mMetaData.faces[0].right_eye[0] = 9;
-    mMetaData.faces[0].right_eye[1] = 10;
-    mMetaData.faces[0].mouth[0] = 11;
-    mMetaData.faces[0].mouth[1] = 12;
-
-    return NS_OK;
-  }
-};
-
-class TwoFacesDetected : public FaceDetected
-{
-public:
-  TwoFacesDetected(nsGonkCameraControl* aTarget)
-    : FaceDetected(aTarget)
-  { }
-
-  nsresult
-  InitMetaData() MOZ_OVERRIDE
-  {
-    mMetaData.number_of_faces = 2;
-    AllocateFacesArray(2);
-    mMetaData.faces[0].id = 1;
-    mMetaData.faces[0].score = 2;
-    mMetaData.faces[0].rect[0] = 3;
-    mMetaData.faces[0].rect[1] = 4;
-    mMetaData.faces[0].rect[2] = 5;
-    mMetaData.faces[0].rect[3] = 6;
-    mMetaData.faces[0].left_eye[0] = 7;
-    mMetaData.faces[0].left_eye[1] = 8;
-    mMetaData.faces[0].right_eye[0] = 9;
-    mMetaData.faces[0].right_eye[1] = 10;
-    mMetaData.faces[0].mouth[0] = 11;
-    mMetaData.faces[0].mouth[1] = 12;
-    mMetaData.faces[1].id = 13;
-    mMetaData.faces[1].score = 14;
-    mMetaData.faces[1].rect[0] = 15;
-    mMetaData.faces[1].rect[1] = 16;
-    mMetaData.faces[1].rect[2] = 17;
-    mMetaData.faces[1].rect[3] = 18;
-    mMetaData.faces[1].left_eye[0] = 19;
-    mMetaData.faces[1].left_eye[1] = 20;
-    mMetaData.faces[1].right_eye[0] = 21;
-    mMetaData.faces[1].right_eye[1] = 22;
-    mMetaData.faces[1].mouth[0] = 23;
-    mMetaData.faces[1].mouth[1] = 24;
-
-    return NS_OK;
-  }
-};
-
-class OneFaceNoFeaturesDetected : public FaceDetected
-{
-public:
-  OneFaceNoFeaturesDetected(nsGonkCameraControl* aTarget)
-    : FaceDetected(aTarget)
-  { }
-
-  nsresult
-  InitMetaData() MOZ_OVERRIDE
-  {
-    mMetaData.number_of_faces = 1;
-    AllocateFacesArray(1);
-    mMetaData.faces[0].id = 1;
-    // Test clamping 'score' to 100.
-    mMetaData.faces[0].score = 1000;
-    mMetaData.faces[0].rect[0] = 3;
-    mMetaData.faces[0].rect[1] = 4;
-    mMetaData.faces[0].rect[2] = 5;
-    mMetaData.faces[0].rect[3] = 6;
-    // Nullable values set to 'not-supported' specific values
-    mMetaData.faces[0].left_eye[0] = -2000;
-    mMetaData.faces[0].left_eye[1] = -2000;
-    // Test other 'not-supported' values as well. We treat
-    // anything outside the range [-1000, 1000] as invalid.
-    mMetaData.faces[0].right_eye[0] = 1001;
-    mMetaData.faces[0].right_eye[1] = -1001;
-    mMetaData.faces[0].mouth[0] = -2000;
-    mMetaData.faces[0].mouth[1] = 2000;
-
-    return NS_OK;
-  }
-};
-
-class NoFacesDetected : public FaceDetected
-{
-public:
-  NoFacesDetected(nsGonkCameraControl* aTarget)
-    : FaceDetected(aTarget)
-  { }
-
-  nsresult
-  InitMetaData() MOZ_OVERRIDE
-  {
-    mMetaData.number_of_faces = 0;
-    mMetaData.faces = nullptr;
-
-    return NS_OK;
-  }
-};
-
 int
 TestGonkCameraHardware::StartFaceDetection()
 {
-  nsRefPtr<FaceDetected> faceDetected;
+  class Delegate : public ControlMessage
+  {
+  public:
+    Delegate(TestGonkCameraHardware* aTestHw)
+      : ControlMessage(aTestHw)
+    { }
 
-  if (IsTestCase("face-detection-detected-one-face")) {
-    faceDetected = new OneFaceDetected(mTarget);
-  } else if (IsTestCase("face-detection-detected-two-faces")) {
-    faceDetected = new TwoFacesDetected(mTarget);
-  } else if (IsTestCase("face-detection-detected-one-face-no-features")) {
-    faceDetected = new OneFaceNoFeaturesDetected(mTarget);
-  } else if (IsTestCase("face-detection-no-faces-detected")) {
-    faceDetected = new NoFacesDetected(mTarget);
-  }
+  protected:
+    NS_IMETHOD
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mJSTestWrapper->StartFaceDetection();
+    }
+  };
 
-  if (!faceDetected) {
-    return GonkCameraHardware::StartFaceDetection();
-  }
-
-  nsresult rv = NS_DispatchToCurrentThread(faceDetected);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGE("Failed to dispatch FaceDetected runnable (0x%08x)\n", rv);
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  nsresult rv = WaitWhileRunningOnMainThread(new Delegate(this));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     return UNKNOWN_ERROR;
   }
-
   return OK;
 }
 
 int
 TestGonkCameraHardware::StopFaceDetection()
 {
-  if (IsTestCase("face-detection-detected-one-face") ||
-      IsTestCase("face-detection-detected-two-faces") ||
-      IsTestCase("face-detection-detected-one-face-no-features") ||
-      IsTestCase("face-detection-no-faces-detected"))
+  class Delegate : public ControlMessage
   {
-    return OK;
+  public:
+    Delegate(TestGonkCameraHardware* aTestHw)
+      : ControlMessage(aTestHw)
+    { }
+
+  protected:
+    NS_IMETHOD
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mJSTestWrapper->StopFaceDetection();
+    }
+  };
+
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  nsresult rv = WaitWhileRunningOnMainThread(new Delegate(this));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return UNKNOWN_ERROR;
   }
-
-  return GonkCameraHardware::StopFaceDetection();
+  return OK;
 }
 
 int
 TestGonkCameraHardware::TakePicture()
 {
-  class TakePictureFailure : public nsRunnable
+  class Delegate : public ControlMessage
   {
   public:
-    TakePictureFailure(nsGonkCameraControl* aTarget)
-      : mTarget(aTarget)
+    Delegate(TestGonkCameraHardware* aTestHw)
+      : ControlMessage(aTestHw)
     { }
 
-    NS_IMETHODIMP
-    Run()
-    {
-      OnTakePictureError(mTarget);
-      return NS_OK;
-    }
-
   protected:
-    nsGonkCameraControl* mTarget;
+    NS_IMETHOD
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mJSTestWrapper->TakePicture();
+    }
   };
 
-  if (IsTestCase("take-picture-failure")) {
-    return TestCaseError(UNKNOWN_ERROR);
-  }
-  if (IsTestCase("take-picture-process-failure")) {
-    nsresult rv = NS_DispatchToCurrentThread(new TakePictureFailure(mTarget));
-    if (NS_SUCCEEDED(rv)) {
-      return OK;
-    }
-    DOM_CAMERA_LOGE("Failed to dispatch TakePictureFailure runnable (0x%08x)\n", rv);
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  nsresult rv = WaitWhileRunningOnMainThread(new Delegate(this));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     return UNKNOWN_ERROR;
   }
+  return OK;
+}
 
-  return GonkCameraHardware::TakePicture();
+void
+TestGonkCameraHardware::CancelTakePicture()
+{
+  class Delegate : public ControlMessage
+  {
+  public:
+    Delegate(TestGonkCameraHardware* aTestHw)
+      : ControlMessage(aTestHw)
+    { }
+
+  protected:
+    NS_IMETHOD
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mJSTestWrapper->CancelTakePicture();
+    }
+  };
+
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  nsresult rv = WaitWhileRunningOnMainThread(new Delegate(this));
+  NS_WARN_IF(NS_FAILED(rv));
 }
 
 int
 TestGonkCameraHardware::StartPreview()
 {
-  if (IsTestCase("start-preview-failure")) {
-    return TestCaseError(UNKNOWN_ERROR);
+  class Delegate : public ControlMessage
+  {
+  public:
+    Delegate(TestGonkCameraHardware* aTestHw)
+      : ControlMessage(aTestHw)
+    { }
+
+  protected:
+    NS_IMETHOD
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mJSTestWrapper->StartPreview();
+    }
+  };
+
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  nsresult rv = WaitWhileRunningOnMainThread(new Delegate(this));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return UNKNOWN_ERROR;
   }
-
-  return GonkCameraHardware::StartPreview();
+  return OK;
 }
 
-int
-TestGonkCameraHardware::StartAutoFocusMoving(bool aIsMoving)
+void
+TestGonkCameraHardware::StopPreview()
 {
-  class AutoFocusMoving : public nsRunnable
+  class Delegate : public ControlMessage
   {
   public:
-    AutoFocusMoving(nsGonkCameraControl* aTarget, bool aIsMoving)
-      : mTarget(aTarget)
-      , mIsMoving(aIsMoving)
+    Delegate(TestGonkCameraHardware* aTestHw)
+      : ControlMessage(aTestHw)
     { }
 
-    NS_IMETHODIMP
-    Run()
+  protected:
+    NS_IMETHOD
+    RunImpl() MOZ_OVERRIDE
     {
-      OnAutoFocusMoving(mTarget, mIsMoving);
-      return NS_OK;
+      return mJSTestWrapper->StopPreview();
+    }
+  };
+
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  nsresult rv = WaitWhileRunningOnMainThread(new Delegate(this));
+  NS_WARN_IF(NS_FAILED(rv));
+}
+
+class TestGonkCameraHardware::PushParametersDelegate : public ControlMessage
+{
+public:
+  PushParametersDelegate(TestGonkCameraHardware* aTestHw, String8* aParams)
+    : ControlMessage(aTestHw)
+    , mParams(aParams)
+  { }
+
+protected:
+  NS_IMETHOD
+  RunImpl() MOZ_OVERRIDE
+  {
+    if (NS_WARN_IF(!mParams)) {
+      return NS_ERROR_INVALID_ARG;
     }
 
-  protected:
-    nsGonkCameraControl* mTarget;
-    bool mIsMoving;
-  };
+    DOM_CAMERA_LOGI("Push test parameters: %s\n", mParams->string());
+    return mJSTestWrapper->PushParameters(NS_ConvertASCIItoUTF16(mParams->string()));
+  }
+
+  String8* mParams;
+};
+
+class TestGonkCameraHardware::PullParametersDelegate : public ControlMessage
+{
+public:
+  PullParametersDelegate(TestGonkCameraHardware* aTestHw, nsString* aParams)
+    : ControlMessage(aTestHw)
+    , mParams(aParams)
+  { }
 
-  nsresult rv = NS_DispatchToCurrentThread(new AutoFocusMoving(mTarget, aIsMoving));
-  if (NS_SUCCEEDED(rv)) {
-    return OK;
+protected:
+  NS_IMETHOD
+  RunImpl() MOZ_OVERRIDE
+  {
+    if (NS_WARN_IF(!mParams)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    nsresult rv = mJSTestWrapper->PullParameters(*mParams);
+    DOM_CAMERA_LOGI("Pull test parameters: %s\n",
+      NS_LossyConvertUTF16toASCII(*mParams).get());
+    return rv;
   }
-  DOM_CAMERA_LOGE("Failed to dispatch AutoFocusMoving runnable (0x%08x)\n", rv);
-  return UNKNOWN_ERROR;
-}
+
+  nsString* mParams;
+};
 
 int
 TestGonkCameraHardware::PushParameters(const GonkCameraParameters& aParams)
 {
-  if (IsTestCase("push-parameters-failure")) {
-    return TestCaseError(UNKNOWN_ERROR);
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  String8 s = aParams.Flatten();
+  nsresult rv = WaitWhileRunningOnMainThread(new PushParametersDelegate(this, &s));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return UNKNOWN_ERROR;
   }
-
-  nsString focusMode;
-  GonkCameraParameters& params = const_cast<GonkCameraParameters&>(aParams);
-  params.Get(CAMERA_PARAM_FOCUSMODE, focusMode);
-  if (focusMode.EqualsASCII("continuous-picture") ||
-      focusMode.EqualsASCII("continuous-video"))
-  {
-    if (IsTestCase("autofocus-moving-true")) {
-      return StartAutoFocusMoving(true);
-    } else if (IsTestCase("autofocus-moving-false")) {
-      return StartAutoFocusMoving(false);
-    }
-  }
-
-  return GonkCameraHardware::PushParameters(aParams);
+  return OK;
 }
 
 nsresult
 TestGonkCameraHardware::PullParameters(GonkCameraParameters& aParams)
 {
-  if (IsTestCase("pull-parameters-failure")) {
-    return static_cast<nsresult>(TestCaseError(UNKNOWN_ERROR));
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  nsString as;
+  nsresult rv = WaitWhileRunningOnMainThread(new PullParametersDelegate(this, &as));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
-  String8 s = mCamera->getParameters();
-  nsCString extra = GetExtraParameters();
-  if (!extra.IsEmpty()) {
-    s += ";";
-    s += extra.get();
+  String8 s(NS_LossyConvertUTF16toASCII(as).get());
+  aParams.Unflatten(s);
+  return NS_OK;
+}
+
+int
+TestGonkCameraHardware::PushParameters(const CameraParameters& aParams)
+{
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  String8 s = aParams.flatten();
+  nsresult rv = WaitWhileRunningOnMainThread(new PushParametersDelegate(this, &s));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return UNKNOWN_ERROR;
+  }
+  return OK;
+}
+
+void
+TestGonkCameraHardware::PullParameters(CameraParameters& aParams)
+{
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  nsString as;
+  nsresult rv = WaitWhileRunningOnMainThread(new PullParametersDelegate(this, &as));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    as.Truncate();
   }
 
-  return aParams.Unflatten(s);
+  String8 s(NS_LossyConvertUTF16toASCII(as).get());
+  aParams.unflatten(s);
 }
 
 int
 TestGonkCameraHardware::StartRecording()
 {
-  if (IsTestCase("start-recording-failure")) {
-    return TestCaseError(UNKNOWN_ERROR);
+  class Delegate : public ControlMessage
+  {
+  public:
+    Delegate(TestGonkCameraHardware* aTestHw)
+      : ControlMessage(aTestHw)
+    { }
+
+  protected:
+    NS_IMETHOD
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mJSTestWrapper->StartRecording();
+    }
+  };
+
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  nsresult rv = WaitWhileRunningOnMainThread(new Delegate(this));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return UNKNOWN_ERROR;
   }
-
-  return GonkCameraHardware::StartRecording();
+  return OK;
 }
 
 int
 TestGonkCameraHardware::StopRecording()
 {
-  if (IsTestCase("stop-recording-failure")) {
-    return TestCaseError(UNKNOWN_ERROR);
-  }
-
-  return GonkCameraHardware::StopRecording();
-}
+  class Delegate : public ControlMessage
+  {
+  public:
+    Delegate(TestGonkCameraHardware* aTestHw)
+      : ControlMessage(aTestHw)
+    { }
 
-int
-TestGonkCameraHardware::SetListener(const sp<GonkCameraListener>& aListener)
-{
-  if (IsTestCase("set-listener-failure")) {
-    return TestCaseError(UNKNOWN_ERROR);
+  protected:
+    NS_IMETHOD
+    RunImpl() MOZ_OVERRIDE
+    {
+      return mJSTestWrapper->StopRecording();
+    }
+  };
+
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  nsresult rv = WaitWhileRunningOnMainThread(new Delegate(this));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return UNKNOWN_ERROR;
   }
-
-  return GonkCameraHardware::SetListener(aListener);
+  return OK;
 }
 
 int
 TestGonkCameraHardware::StoreMetaDataInBuffers(bool aEnabled)
 {
-  if (IsTestCase("store-metadata-in-buffers-failure")) {
-    return TestCaseError(UNKNOWN_ERROR);
-  }
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  return OK;
+}
 
-  return GonkCameraHardware::StoreMetaDataInBuffers(aEnabled);
-}
--- a/dom/camera/TestGonkCameraHardware.h
+++ b/dom/camera/TestGonkCameraHardware.h
@@ -1,10 +1,10 @@
 /*
- * Copyright (C) 2013-2014 Mozilla Foundation
+ * Copyright (C) 2013-2015 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
@@ -13,64 +13,60 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #ifndef DOM_CAMERA_TESTGONKCAMERAHARDWARE_H
 #define DOM_CAMERA_TESTGONKCAMERAHARDWARE_H
 
 #include "GonkCameraHwMgr.h"
+#include "nsAutoPtr.h"
+#include "nsIDOMEventListener.h"
+#include "mozilla/CondVar.h"
 
-namespace android {
+namespace mozilla {
 
 class TestGonkCameraHardware : public android::GonkCameraHardware
 {
 public:
+  virtual nsresult Init() MOZ_OVERRIDE;
   virtual int AutoFocus() MOZ_OVERRIDE;
   virtual int StartFaceDetection() MOZ_OVERRIDE;
   virtual int StopFaceDetection() MOZ_OVERRIDE;
   virtual int TakePicture() MOZ_OVERRIDE;
+  virtual void CancelTakePicture() MOZ_OVERRIDE;
   virtual int StartPreview() MOZ_OVERRIDE;
+  virtual void StopPreview() MOZ_OVERRIDE;
   virtual int PushParameters(const mozilla::GonkCameraParameters& aParams) MOZ_OVERRIDE;
   virtual nsresult PullParameters(mozilla::GonkCameraParameters& aParams) MOZ_OVERRIDE;
   virtual int StartRecording() MOZ_OVERRIDE;
   virtual int StopRecording() MOZ_OVERRIDE;
-  virtual int SetListener(const sp<GonkCameraListener>& aListener) MOZ_OVERRIDE;
   virtual int StoreMetaDataInBuffers(bool aEnabled) MOZ_OVERRIDE;
-
-  virtual int
-  PushParameters(const CameraParameters& aParams) MOZ_OVERRIDE
-  {
-    return GonkCameraHardware::PushParameters(aParams);
-  }
-
-  virtual void
-  PullParameters(CameraParameters& aParams) MOZ_OVERRIDE
-  {
-    GonkCameraHardware::PullParameters(aParams);
-  }
+  virtual int PushParameters(const android::CameraParameters& aParams) MOZ_OVERRIDE;
+  virtual void PullParameters(android::CameraParameters& aParams) MOZ_OVERRIDE;
 
   TestGonkCameraHardware(mozilla::nsGonkCameraControl* aTarget,
                          uint32_t aCameraId,
-                         const sp<Camera>& aCamera);
-  virtual ~TestGonkCameraHardware();
-
-  virtual nsresult Init() MOZ_OVERRIDE;
+                         const android::sp<android::Camera>& aCamera);
 
 protected:
-  const nsCString TestCase();
-  const nsCString GetExtraParameters();
-  bool IsTestCaseInternal(const char* aTest, const char* aFile, int aLine);
-  int TestCaseError(int aDefaultError);
+  virtual ~TestGonkCameraHardware();
+
+  class ControlMessage;
+  class PushParametersDelegate;
+  class PullParametersDelegate;
 
-  int StartAutoFocusMoving(bool aIsMoving);
-  void InjectFakeSystemFailure();
+  nsresult WaitWhileRunningOnMainThread(nsRefPtr<ControlMessage> aRunnable);
+
+  nsCOMPtr<nsIDOMEventListener> mDomListener;
+  nsCOMPtr<nsIThread> mCameraThread;
+  mozilla::Mutex mMutex;
+  mozilla::CondVar mCondVar;
+  nsresult mStatus;
 
 private:
   TestGonkCameraHardware(const TestGonkCameraHardware&) = delete;
   TestGonkCameraHardware& operator=(const TestGonkCameraHardware&) = delete;
 };
 
-#define IsTestCase(test)  IsTestCaseInternal((test), __FILE__, __LINE__)
-
-} // namespace android
+} // namespace mozilla
 
 #endif // DOM_CAMERA_TESTGONKCAMERAHARDWARE_H
--- a/dom/camera/moz.build
+++ b/dom/camera/moz.build
@@ -20,27 +20,38 @@ UNIFIED_SOURCES += [
     'DOMCameraCapabilities.cpp',
     'DOMCameraControl.cpp',
     'DOMCameraControlListener.cpp',
     'DOMCameraDetectedFace.cpp',
     'DOMCameraManager.cpp',
 ]
 
 if CONFIG['MOZ_B2G_CAMERA']:
+    XPIDL_SOURCES += [
+        'nsICameraTestHardware.idl',
+    ]
+
+    XPIDL_MODULE = 'dom_camera'
+
     UNIFIED_SOURCES += [
         'GonkCameraControl.cpp',
         'GonkCameraHwMgr.cpp',
         'GonkCameraManager.cpp',
         'GonkCameraParameters.cpp',
         'GonkCameraSource.cpp',
         'GonkRecorder.cpp',
         'GonkRecorderProfiles.cpp',
         'TestGonkCameraControl.cpp',
         'TestGonkCameraHardware.cpp',
     ]
+
+    EXTRA_COMPONENTS += [
+        'CameraTestHardware.js',
+        'CameraTestHardware.manifest',
+    ]
 else:
     UNIFIED_SOURCES += [
         'FallbackCameraControl.cpp',
         'FallbackCameraManager.cpp',
     ]
 
 FAIL_ON_WARNINGS = True
 
new file mode 100644
--- /dev/null
+++ b/dom/camera/nsICameraTestHardware.idl
@@ -0,0 +1,191 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMBlob;
+interface nsIDOMEventListener;
+
+[scriptable, uuid(2e567730-f164-49d7-b975-862caa4425a5)]
+interface nsICameraTestHardware : nsISupports
+{
+  /* The following methods are intended to be used by the test cases
+     written in JavaScript to define the behaviour of the hardware: */
+
+  /* Attach a delegate handler object such that the test hardware
+     will call the given handlers for the given operations to decide
+     what to do. This allows a test case to define specific behaviours
+     on a fine grained basis.
+
+     The following handlers may be supplied as properties of the
+     given delagate handler object:
+       autoFocus
+       cancelAutoFocus
+       cancelTakePicture
+       init
+       pushParameters
+       pullParameters
+       startFaceDetection
+       startPreview
+       startRecording
+       stopFaceDetection
+       stopPreview
+       stopRecording
+       takePicture
+
+     Implementation notes for handlers:
+
+     - If the handler throws an error, we will the return code
+       of the driver operation.
+
+     - If the handler returns true, we will perform the default
+       action (if any) for the operation. */
+  void attach(in jsval mock);
+
+  /* Detach a delegate handler object such that the test hardware
+     will revert to default behaviour when a function is called. */
+  void detach();
+
+  /* Reset the state of the test hardware back to the initial state.
+     This is useful when one test case has been completed and we need
+     a clean slate for the next. */
+  void reset(in jsval window);
+
+  /* Trigger an OnAutoFocusMoving callback at the Gonk layer.
+
+     state is a boolean indicating where or not the camera focus
+     is moving. */
+  void fireAutoFocusComplete(in boolean state);
+
+  /* Trigger an OnAutoFocusComplete callback at the Gonk layer.
+
+     state is a boolean indicating where or not the camera is focused. */
+  void fireAutoFocusMoving(in boolean moving);
+
+  /* Trigger an OnTakePictureComplete callback at the Gonk layer.
+
+     blob should be a Blob object. The actual content of the blob
+     is unimportant since nothing processes it as an image internally. */
+  void fireTakePictureComplete(in nsIDOMBlob picture);
+
+  /* Trigger an OnTakePictureError callback at the Gonk layer. */
+  void fireTakePictureError();
+
+  /* Trigger an OnSystemError callback at the Gonk layer. */
+  void fireSystemError();
+
+  /* Trigger an OnShutter callback at the Gonk layer. */
+  void fireShutter();
+
+  /* Trigger an OnFacesDetected callback at the Gonk layer.
+
+     faces is an array of CameraDetectedFaceInit dictionaries although
+     hasLeftEye, hasRightEye and hasMouth may be omitted and will be
+     implied by the presence/absence of leftEye, rightEye and mouth. */
+  void fireFacesDetected(in jsval faces);
+
+  /* Object which stores the camera parameters read/written by the
+     camera control layer from the hardware. The test case may set
+     its own values to control the behaviour of the camera middleware.
+
+     E.g. params['preview-sizes'] = '320x240,640x480'; */
+  attribute jsval params;
+
+  /* The following methods are intended to be used by the Gonk layer
+     in order to call back into JavaScript to get test case defined
+     behaviour: */
+
+  /* Set a handler to capture asynchronous events triggered by the
+     test case via the fireXXX methods. E.g.:
+
+       nsCOMPtr<nsICameraHardware> wrapper =
+         do_GetService("@mozilla.org/cameratesthardware;1");
+
+       nsCOMPtr<nsIDOMEventListener> listener = new HwListener();
+
+       wrapper->setHander(listener);
+
+     where
+
+       class HwListener : public nsIDOMEventListener {
+         NS_IMETHODIMP HandleEvent(nsIDOMEvent *aEvent) {
+           nsString type;
+           aEvent->GetType(&type);
+           if (aEvent.EqualsLiteral("focus")) {
+              ...
+           } else {
+              ...
+           }
+         }
+       };
+
+     The following event types may be generated:
+       focus: CameraStateChangeEvent where newState should map
+       to the OnAutoFocusComplete and OnAutoFocusMoving callbacks:
+       -- focused: OnAutoFocusComplete(false)
+       -- unfocused: OnAutoFocusComplete(true)
+       -- focusing: OnAutoFocusMoving(true)
+       -- not_focusing: OnAutoFocusMoving(false)
+
+       picture: BlobEvent which contains the picture type and
+       data corresponding to the OnTakePictureComplete callback.
+
+       error: ErrorEvent corresponding to the various error callbacks,
+       where the message is:
+       -- picture: OnTakePictureError()
+       -- system: OnSystemError(100, 0)
+
+       facesdetected: CameraFacesDetectedEvent which contains the
+       faces data corresponding to OnFacesDetected callback.
+
+       shutter: Event which corresponds to the OnShutter callback. */
+  void setHandler(in nsIDOMEventListener handler);
+
+  /* Execute an intercepted Init() driver call. */
+  void initCamera();
+
+  /* Execute an intercepted AutoFocus() driver call. Default behaviour is
+     to trigger OnAutoFocusComplete where the camera is focused. */
+  void autoFocus();
+
+  /* Execute an intercepted CancelAutoFocus() driver call. */
+  void cancelAutoFocus();
+
+  /* Execute an intercepted StartFaceDetection() driver call. */
+  void startFaceDetection();
+
+  /* Execute an intercepted StopFaceDetection() driver call. */
+  void stopFaceDetection();
+
+  /* Execute an intercepted TakePicture() driver call. Default behaviour is
+     to trigger OnTakePictureComplete with a fake jpeg blob. */
+  void takePicture();
+
+  /* Execute an intercepted CancelTakePicture() driver call. */
+  void cancelTakePicture();
+
+  /* Execute an intercepted StartPreview() driver call. */
+  void startPreview();
+
+  /* Execute an intercepted StopPreview() driver call. */
+  void stopPreview();
+
+  /* Execute an intercepted StartRecording() driver call. */
+  void startRecording();
+
+  /* Execute an intercepted StopRecording() driver call. */
+  void stopRecording();
+
+  /* Execute an intercepted PushParameters() driver call. If the delegate
+     handler throws an error, it will restore the old parameters.
+     When the delegate is called, the new proposed parameters are
+     placed in this.params. */
+  void pushParameters(in DOMString params);
+
+  /* Execute an intercepted PullParameters() driver call. Unless the delegate
+     handler throws an error, it will return an assembled parameter
+     list derived from the this.params hash table. */
+  DOMString pullParameters();
+};
+
--- a/dom/camera/test/camera_common.js
+++ b/dom/camera/test/camera_common.js
@@ -1,169 +1,396 @@
-var CameraTest = (function() {
-  'use strict';
+function isDefinedObj(obj) {
+  return typeof(obj) !== 'undefined' && obj != null;
+}
+
+function isDefined(obj) {
+  return typeof(obj) !== 'undefined';
+}
+
+/* This is a simple test suite class removing the need to
+   write a lot of boilerplate for camera tests. It can
+   manage the platform configurations for testing, any
+   cleanup required, and common actions such as fetching
+   the camera or waiting for the preview to be completed.
+
+   To create the suite:
+     var suite = new CameraTestSuite();
+
+   To add a test case to the suite:
+     suite.test('test-name', function() {
+       function startAutoFocus(p) {
+         return suite.camera.autoFocus();
+       }
+
+       return suite.getCamera()
+         .then(startAutoFocus, suite.rejectGetCamera);
+     });
+
+   Finally, to execute the test cases:
+     suite.setup()
+       .then(suite.run);
+
+   Behind the scenes, suite configured the native camera
+   to use the JS hardware, setup that hardware such that
+   the getCamera would succeed, got a camera control
+   reference and saved it to suite.camera, and after the
+   tests were finished, it reset any modified state,
+   released the camera object, and concluded the mochitest
+   appropriately.
+*/
+function CameraTestSuite() {
+  SimpleTest.waitForExplicitFinish();
+
+  this._window = window;
+  this._document = document;
+  this.viewfinder = document.getElementById('viewfinder');
+  this._tests = [];
+  this.hwType = '';
+
+  /* Ensure that the this pointer is bound to all functions so that
+     they may be used as promise resolve/reject handlers without any
+     special effort, permitting code like this:
+
+       getCamera().catch(suite.rejectGetCamera);
+
+     instead of:
 
-  /**
-   * 'camera.control.test.enabled' is queried in Gecko to enable different
-   * test modes in the camera stack. The only currently-supported setting
-   * is 'hardware', which wraps the Gonk camera abstraction class in a
-   * shim class that supports injecting hardware camera API failures into
-   * the execution path.
-   *
-   * The affected API is specified by the 'camera.control.test.hardware'
-   * pref. Currently supported values should be determined by inspecting
-   * TestGonkCameraHardware.cpp.
-   *
-   * Some API calls are simple: e.g. 'start-recording-failure' will cause
-   * the DOM-facing startRecording() call to fail. More complex tests like
-   * 'take-picture-failure' will cause the takePicture() API to fail, while
-   * 'take-picture-process-failure' will simulate a failure of the
-   * asynchronous picture-taking process, even if the initial API call
-   * path seems to have succeeded.
-   *
-   * If 'camera.control.test.hardware.gonk.parameters' is set, it will cause
-   * the contents of that string to be appended to the string of parameters
-   * pulled from the Gonk camera library. This allows tests to inject fake
-   * settings/capabilities for features not supported by the emulator. These
-   * parameters are one or more semicolon-delimited key=value pairs, e.g. to
-   * pretend the emulator supports zoom:
-   *
-   *   zoom-ratios=100,150,200,300,400;max-zoom=4
-   *
-   * This means (of course) that neither the key not the value tokens can
-   * contain either equals signs or semicolons. The test shim doesn't enforce
-   * this so that we can test getting junk from the camera library as well.
-   */
-  const PREF_TEST_ENABLED = "camera.control.test.enabled";
-  const PREF_TEST_HARDWARE = "camera.control.test.hardware";
-  const PREF_TEST_EXTRA_PARAMETERS = "camera.control.test.hardware.gonk.parameters";
-  const PREF_TEST_FAKE_LOW_MEMORY = "camera.control.test.is_low_memory";
-  var oldTestEnabled;
-  var oldTestHw;
-  var testMode;
+       getCamera().catch(suite.rejectGetCamera.bind(suite));
+  */
+  this.setup = this._setup.bind(this);
+  this.teardown = this._teardown.bind(this);
+  this.test = this._test.bind(this);
+  this.run = this._run.bind(this);
+  this.waitPreviewStarted = this._waitPreviewStarted.bind(this);
+  this.waitParameterPush = this._waitParameterPush.bind(this);
+  this.initJsHw = this._initJsHw.bind(this);
+  this.getCamera = this._getCamera.bind(this);
+  this.setLowMemoryPlatform = this._setLowMemoryPlatform.bind(this);
+  this.logError = this._logError.bind(this);
+  this.expectedError = this._expectedError.bind(this);
+  this.expectedRejectGetCamera = this._expectedRejectGetCamera.bind(this);
+  this.expectedRejectAutoFocus = this._expectedRejectAutoFocus.bind(this);
+  this.expectedRejectTakePicture = this._expectedRejectTakePicture.bind(this);
+  this.rejectGetCamera = this._rejectGetCamera.bind(this);
+  this.rejectRelease = this._rejectRelease.bind(this);
+  this.rejectAutoFocus = this._rejectAutoFocus.bind(this);
+  this.rejectTakePicture = this._rejectTakePicture.bind(this);
+  this.rejectPreviewStarted = this._rejectPreviewStarted.bind(this);
+
+  var self = this;
+  this._window.addEventListener('beforeunload', function() {
+    if (isDefinedObj(self.viewfinder)) {
+      self.viewfinder.mozSrcObject = null;
+    }
+
+    self.hw = null;
+    if (isDefinedObj(self.camera)) {
+      ok(false, 'window unload triggered camera release instead of test completion');
+      self.camera.release();
+      self.camera = null;
+    }
+  });
+}
 
-  function testHardwareSetFakeParameters(parameters, callback) {
-    SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_EXTRA_PARAMETERS, parameters]]}, function() {
-      var setParams = SpecialPowers.getCharPref(PREF_TEST_EXTRA_PARAMETERS);
-      ise(setParams, parameters, "Extra test parameters '" + setParams + "'");
-      if (callback) {
-        callback(setParams);
-      }
+CameraTestSuite.prototype = {
+  camera: null,
+  hw: null,
+  _lowMemSet: false,
+
+  /* Returns a promise which is resolved when the test suite is ready
+     to be executing individual test cases. One may provide the expected
+     hardware type here if desired; the default is to use the JS test
+     hardware. Use '' for the native emulated camera hardware. */
+  _setup: function(hwType) {
+    if (!isDefined(hwType)) {
+      hwType = 'hardware';
+    }
+
+    this._hwType = hwType;
+    return new Promise(function(resolve, reject) {
+      SpecialPowers.pushPrefEnv({'set': [['camera.control.test.enabled', hwType]]}, function() {
+        resolve();
+      });
     });
-  }
+  },
+
+  /* Returns a promise which is resolved when all of the SpecialPowers
+     parameters that were set while testing are flushed. This includes
+     camera.control.test.enabled and camera.control.test.is_low_memory. */
+  _teardown: function() {
+    return new Promise(function(resolve, reject) {
+      SpecialPowers.flushPrefEnv(function() {
+        resolve();
+      });
+    });
+  },
 
-  function testHardwareClearFakeParameters(callback) {
-    SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_EXTRA_PARAMETERS]]}, callback);
-  }
+  /* Returns a promise which is resolved when the set low memory
+     parameter is set. If no value is given, it defaults to true.
+     This is intended to be used inside a test case at the beginning
+     of its promise chain to configure the platform as desired. */
+  _setLowMemoryPlatform: function(val) {
+    if (typeof(val) === 'undefined') {
+      val = true;
+    }
+
+    if (this._lowMemSet === val) {
+      return Promise.resolve();
+    }
 
-  function testHardwareSetFakeLowMemoryPlatform(callback) {
-    SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_FAKE_LOW_MEMORY, true]]}, function() {
-      var setParams = SpecialPowers.getBoolPref(PREF_TEST_FAKE_LOW_MEMORY);
-      ise(setParams, true, "Fake low memory platform");
-      if (callback) {
-        callback(setParams);
+    var self = this;
+    return new Promise(function(resolve, reject) {
+      SpecialPowers.pushPrefEnv({'set': [['camera.control.test.is_low_memory', val]]}, function() {
+        self._lowMemSet = val;
+        resolve();
+      });
+    }).catch(function(e) {
+      return self.logError('set low memory ' + val + ' failed', e);
+    });
+  },
+
+  /* Add a test case to the test suite to be executed later. */
+  _test: function(aName, aCb) {
+    this._tests.push({
+      name: aName,
+      cb: aCb
+    });
+  },
+
+  /* Execute all test cases (after setup is called). */
+  _run: function() {
+    var test = this._tests.shift();
+    var self = this;
+    if (test) {
+      info(test.name + ' started');
+
+      function runNextTest() {
+        self.run();
       }
-    });
-  }
 
-  function testHardwareClearFakeLowMemoryPlatform(callback) {
-    SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_FAKE_LOW_MEMORY]]}, callback);
-  }
+      function resetLowMem() {
+        return self.setLowMemoryPlatform(false);
+      }
+
+      function postTest(pass) {
+        ok(pass, test.name + ' finished');
+        var camera = self.camera;
+        self.viewfinder.mozSrcObject = null;
+        self.camera = null;
+
+        if (!isDefinedObj(camera)) {
+          return Promise.resolve();
+        }
 
-  function testHardwareSet(test, callback) {
-    SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_HARDWARE, test]]}, function() {
-      var setTest = SpecialPowers.getCharPref(PREF_TEST_HARDWARE);
-      ise(setTest, test, "Test subtype set to " + setTest);
-      if (callback) {
-        callback(setTest);
+        function handler(e) {
+          ok(typeof(e) === 'undefined', 'camera released');
+          return Promise.resolve();
+        }
+
+        return camera.release().then(handler).catch(handler);
       }
-    });
-  }
+
+      this.initJsHw();
+
+      var testPromise;
+      try {
+        testPromise = test.cb();
+        if (!isDefinedObj(testPromise)) {
+          testPromise = Promise.resolve();
+        }
+      } catch(e) {
+        ok(false, 'caught exception while running test: ' + e);
+        testPromise = Promise.reject(e);
+      }
 
-  function testHardwareDone(callback) {
-    testMode = null;
-    if (oldTestHw) {
-      SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_HARDWARE, oldTestHw]]}, callback);
+      testPromise
+        .then(function(p) {
+          return postTest(true);
+        }, function(e) {
+          self.logError('unhandled error', e);
+          return postTest(false);
+        })
+        .then(resetLowMem, resetLowMem)
+        .then(runNextTest, runNextTest);
     } else {
-      SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_HARDWARE]]}, callback);
+      ok(true, 'all tests completed');
+      var finish = SimpleTest.finish.bind(SimpleTest);
+      this.teardown().then(finish, finish);
     }
-  }
+  },
+
+  /* If the JS hardware is in use, get (and possibly initialize)
+     the service XPCOM object. The native Gonk layers are able
+     to get it via the same mechanism. Save a reference to it
+     so that the test case may manipulate it as it sees fit in
+     this.hw. Minimal setup is done for the test hardware such
+     that the camera is able to be brought up without issue.
+
+     This function has no effect if the JS hardware is not used. */
+  _initJsHw: function() {
+    if (this._hwType === 'hardware') {
+      this.hw = SpecialPowers.Cc['@mozilla.org/cameratesthardware;1']
+                .getService(SpecialPowers.Ci.nsICameraTestHardware);
+      this.hw.reset(this._window);
+
+      /* Minimum parameters required to get camera started */
+      this.hw.params['preview-size'] = '320x240';
+      this.hw.params['preview-size-values'] = '320x240';
+      this.hw.params['picture-size-values'] = '320x240';
+    } else {
+      this.hw = null;
+    }
+  },
 
-  function testBegin(mode, callback) {
-    SimpleTest.waitForExplicitFinish();
-    try {
-      oldTestEnabled = SpecialPowers.getCharPref(PREF_TEST_ENABLED);
-    } catch(e) { }
-    SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_ENABLED, mode]]}, function() {
-      var setMode = SpecialPowers.getCharPref(PREF_TEST_ENABLED);
-      ise(setMode, mode, "Test mode set to " + setMode);
-      if (setMode === "hardware") {
+  /* Returns a promise which resolves when the camera has
+     been successfully opened with the given name and
+     configuration. If no name is given, it uses the first
+     camera in the list from the camera manager. */
+  _getCamera: function(name, config) {
+    var cameraManager = navigator.mozCameras;
+    if (!isDefined(name)) {
+      name = cameraManager.getListOfCameras()[0];
+    }
+
+    var self = this;
+    return cameraManager.getCamera(name, config).then(
+      function(p) {
+        ok(isDefinedObj(p) && isDefinedObj(p.camera), 'got camera');
+        self.camera = p.camera;
+        /* Ensure a followup promise can verify config by
+           returning the same parameter again. */
+        return Promise.resolve(p);
+      }
+    );
+  },
+
+  /* Returns a promise which resolves when the camera has
+     successfully started the preview and is bound to the
+     given viewfinder object. Note that this requires that
+     a video element be present with the ID 'viewfinder'. */
+  _waitPreviewStarted: function() {
+    var self = this;
+
+    return new Promise(function(resolve, reject) {
+      function onPreviewStateChange(e) {
         try {
-          oldTestHw = SpecialPowers.getCharPref(PREF_TEST_HARDWARE);
-        } catch(e) { }
-        testMode = {
-          set: testHardwareSet,
-          setFakeParameters: testHardwareSetFakeParameters,
-          clearFakeParameters: testHardwareClearFakeParameters,
-          setFakeLowMemoryPlatform: testHardwareSetFakeLowMemoryPlatform,
-          clearFakeLowMemoryPlatform: testHardwareClearFakeLowMemoryPlatform,
-          done: testHardwareDone
-        };
-        if (callback) {
-          callback(testMode);
+          if (e.newState === 'started') {
+            ok(true, 'viewfinder is ready and playing');
+            self.camera.removeEventListener('previewstatechange', onPreviewStateChange);
+            resolve();
+          }
+        } catch(e) {
+          reject(e);
         }
       }
-    });
-  }
 
-  function testEnd(callback) {
-    // A chain of clean-up functions....
-    function allCleanedUp() {
-      SimpleTest.finish();
-      if (callback) {
-        callback();
-      }
-    }
-
-    function cleanUpTestEnabled() {
-      var next = allCleanedUp;
-      if (oldTestEnabled) {
-        SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_ENABLED, oldTestEnabled]]}, next);
-      } else {
-        SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_ENABLED]]}, next);
-      }
-    }
-    function cleanUpTest() {
-      var next = cleanUpTestEnabled;
-      if (testMode) {
-        testMode.done(next);
-        testMode = null;
-      } else {
-        next();
+      if (!isDefinedObj(self.viewfinder)) {
+        reject(new Error('no viewfinder object'));
+        return;
       }
-    }
-    function cleanUpLowMemoryPlatform() {
-      var next = cleanUpTest;
-      if (testMode) {
-        testMode.clearFakeLowMemoryPlatform(next);
-      } else {
-        next();
-      }
+
+      self.viewfinder.mozSrcObject = self.camera;
+      self.viewfinder.play();
+      self.camera.addEventListener('previewstatechange', onPreviewStateChange);
+    });
+  },
+
+  /* Returns a promise which resolves when the camera hardware
+     has received a push parameters request. This is useful
+     when setting camera parameters from the application and
+     you want confirmation when the operation is complete if
+     there is no asynchronous notification provided. */
+  _waitParameterPush: function() {
+    var self = this;
+
+    return new Promise(function(resolve, reject) {
+      self.hw.attach({
+        'pushParameters': function() {
+          self._window.setTimeout(resolve);
+        }
+      });
+    });
+  },
+
+  /* When an error occurs in the promise chain, all of the relevant rejection
+     functions will be triggered. Most of the time however we only want the
+     first rejection to be handled and then let the failure trickle down the
+     chain to terminate the test. There is no way to exit a promise chain
+     early so the convention is to handle the error in the first reject and
+     then give an empty error for subsequent reject handlers so they know
+     it is not for them.
+
+     For example:
+       function rejectSomething(e) {
+         return suite.logError('something call failed');
+       }
+
+       getCamera()
+         .then(, suite.rejectGetCamera)
+         .then(something)
+         .then(, rejectSomething)
+
+     If the getCamera promise is rejected, suite.rejectGetCamera reports an
+     error, but rejectSomething remains silent. */
+  _logError: function(msg, e) {
+    if (isDefined(e)) {
+      ok(false, msg + ': ' + e);
     }
-    function cleanUpExtraParameters() {
-      var next = cleanUpLowMemoryPlatform;
-      if (testMode) {
-        testMode.clearFakeParameters(next);
-      } else {
-        next();
-      }
-    }
+    // Make sure the error is undefined for later handlers
+    return Promise.reject();
+  },
+
+  /* The reject handlers below are intended to be used
+     when a test case does not expect a particular call
+     to fail but otherwise does not require any special
+     handling of that situation beyond failing the test
+     case and logging why.*/
+  _rejectGetCamera: function(e) {
+    return this.logError('get camera failed', e);
+  },
+
+  _rejectRelease: function(e) {
+    return this.logError('release camera failed', e);
+  },
+
+  _rejectAutoFocus: function(e) {
+    return this.logError('auto focus failed', e);
+  },
+
+  _rejectTakePicture: function(e) {
+    return this.logError('take picture failed', e);
+  },
+
+  _rejectPreviewStarted: function(e) {
+    return this.logError('preview start failed', e);
+  },
 
-    cleanUpExtraParameters();
-  }
+  /* The success handlers below are intended to be used
+     when a test case does not expect a particular call
+     to succed but otherwise does not require any special
+     handling of that situation beyond failing the test
+     case and logging why.*/
+  _expectedError: function(msg) {
+    ok(false, msg);
+    /* Since the original promise was technically resolved
+       we actually want to pass up a rejection to try and
+       end the test case sooner */
+    return Promise.reject();
+  },
 
-  ise(SpecialPowers.sanityCheck(), "foo", "SpecialPowers passed sanity check");
-  return {
-    begin: testBegin,
-    end: testEnd
-  };
+  _expectedRejectGetCamera: function(p) {
+    /* Copy handle to ensure it gets released at the end
+       of the test case */
+    self.camera = p.camera;
+    return this.expectedError('expected get camera to fail');
+  },
 
-})();
+  _expectedRejectAutoFocus: function(p) {
+    return this.expectedError('expected auto focus to fail');
+  },
+
+  _expectedRejectTakePicture: function(p) {
+    return this.expectedError('expected take picture to fail');
+  },
+};
+
+ise(SpecialPowers.sanityCheck(), "foo", "SpecialPowers passed sanity check");
--- a/dom/camera/test/test_bug1022766.html
+++ b/dom/camera/test/test_bug1022766.html
@@ -7,99 +7,57 @@
   <script type="text/javascript" src="camera_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <video id="viewfinder" width="200" height="200" autoplay></video>
 <img src="#" alt="This image is going to load" id="testimage"/>
 <script class="testbody" type="text/javascript;version=1.7">
 
-var whichCamera = navigator.mozCameras.getListOfCameras()[0];
-var config = {
-  mode: 'picture',
-  recorderProfile: 'high',
-  previewSize: {
-    width: 352,
-    height: 288
-  }
-};
-
-function onError(e) {
-  ok(false, "Error " + e);
-}
-
-var Camera = {
-  cameraObj: null,
-  _otherPictureSize: null,
-  get viewfinder() {
-    return document.getElementById('viewfinder');
-  },
+var suite = new CameraTestSuite();
 
-  firstCallFailed: false,
-  secondCallSucceeded: false,
-  callbackTriggered: false,
-  checkForDone: function test_checkForDone() {
-    if (Camera.firstCallFailed && Camera.secondCallSucceeded) {
-      ok(Camera.callbackTriggered, "Async callback triggered?");
-      Camera.cameraObj.release();
-      Camera.cameraObj = null;
-      CameraTest.end();
-    }
-  },
+suite.test('bug-1022766', function() {
+  function triggerAutoFocus(p) {
+    return new Promise(function(resolve, reject) {
+      var firstCall = false;
+      var secondCall = false;
 
-  successOne: function test_successOne(focused) {
-    ok(false, "First call to autoFocus() succeeded unexpectedly");
-  },
-  failureOne: function test_failureOne(error) {
-    ok(error.name == "NS_ERROR_IN_PROGRESS", "First call to autoFocus() failed with: "
-      + error.name);
-    Camera.firstCallFailed = true;
-    Camera.checkForDone();
-  },
-  successTwo: function test_successTwo(focused) {
-    ok(true, "Second call to autoFocus() succeeded");
-    Camera.secondCallSucceeded = true;
-    Camera.checkForDone();
-  },
-  failureTwo: function test_failureTwo(error) {
-    ok(false, "Second call to autoFocus() failed unexpectedly with: " + error);
-  },
-  callback: function test_callback(e) {
-    Camera.cameraObj.removeEventListener('focus', Camera.callback);
-    Camera.callbackTriggered = true;
-  },
-
-  start: function test_start() {
-    function onSuccess(d) {
-      var camera = d.camera;
-      Camera.cameraObj = camera;
-      Camera.viewfinder.mozSrcObject = camera;
-      Camera.viewfinder.play();
+      function end() {
+        if (firstCall && secondCall) {
+          resolve();
+        }
+      }
 
       // It doesn't matter if the emulator supports focus or not;
       // this is just testing the sequencing.
-      camera.addEventListener('focus', Camera.callback);
-      camera.autoFocus().then(Camera.successOne, Camera.failureOne);
-      camera.autoFocus().then(Camera.successTwo, Camera.failureTwo);
-    };
+      suite.camera.autoFocus().then(function(p) {
+        ok(false, "First call to autoFocus() succeeded unexpectedly");
+        firstCall = true;
+        end();
+      }, function(e) {
+        ok(e.name === 'NS_ERROR_IN_PROGRESS', 'First call to autoFocus() failed with: ' + e);
+        firstCall = true;
+        end();
+      });
 
-    navigator.mozCameras.getCamera(whichCamera, config).then(onSuccess, onError);
+      suite.camera.autoFocus().then(function(p) {
+        ok(true, "Second call to autoFocus() succeeded");
+        secondCall = true;
+        end();
+      }, function(e) {
+        ok(false, "Second call to autoFocus() failed unexpectedly with: " + e);
+        secondCall = true;
+        end();
+      });
+    });
   }
-}
 
-window.addEventListener('beforeunload', function() {
-  Camera.viewfinder.mozSrcObject = null;
-  if (Camera.cameraObj) {
-    Camera.cameraObj.release();
-    Camera.cameraObj = null;
-  }
+  return suite.getCamera()
+    .then(triggerAutoFocus, suite.rejectGetCamera)
 });
 
-CameraTest.begin("hardware", function(test) {
-  test.set("auto-focus-process-failure", function() {
-    Camera.start();
-  })
-});
+suite.setup()
+  .then(suite.run);
 
 </script>
 </body>
 
 </html>
--- a/dom/camera/test/test_bug975472.html
+++ b/dom/camera/test/test_bug975472.html
@@ -188,17 +188,17 @@ var Camera = {
   onCameraReady: function () {
     Camera.nextTest = function() {
       try {
         var t = testGenerator.next();
         info("test: " + t.key);
         t.func(Camera.cameraObj);
       } catch(e) {
         if (e instanceof StopIteration) {
-          CameraTest.end();
+          SimpleTest.finish();
         } else {
           throw e;
         }
       }
     };
     // Release the camera hardware, and call all of the asynchronous methods
     // to make sure they properly handle being in this state.
     Camera.cameraObj.release();
--- a/dom/camera/test/test_camera_fake_parameters.html
+++ b/dom/camera/test/test_camera_fake_parameters.html
@@ -9,479 +9,388 @@
 </head>
 <body>
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=976802">Mozilla Bug 976802</a>
   <video id="viewfinder" width="200" height="200" autoplay></video>
   <img src="#" alt="This image is going to load" id="testimage"/>
 
 <script class="testbody" type="text/javascript;version=1.7">
 
-var whichCamera = navigator.mozCameras.getListOfCameras()[0];
-var initialConfig = {
-  mode: 'picture',
-  recorderProfile: 'high',
-  previewSize: {
-    width: 352,
-    height: 288
-  }
-};
+var suite = new CameraTestSuite();
+
+suite.test('fake-zoom', function() {
+  suite.hw.params['zoom-ratios'] = '100,150,200,300,400';
+  suite.hw.params['max-zoom'] = '4';
 
-var cameraObj = null;
+  function resolve(p) {
+    var cam = suite.camera;
+    var cap = cam.capabilities;
 
-// Shorthand functions
-function onError(e) {
-  ok(false, "Error " + e);
-}
+    ok(cap.zoomRatios.length == 5, "zoom ratios length = " + cap.zoomRatios.length);
 
-function end() {
-  CameraTest.end();
-}
-function next() {
-  if (cameraObj) {
-    cameraObj.release().then(
-      function success() {
-        CameraTest.next();
-      },
-      onError
-    );
-    cameraObj = null;
-  } else {
-    CameraTest.next();
-  }
-}
-function run() {
-  CameraTest.run();
-}
+    // test individual zoom ratios
+    cap.zoomRatios.forEach(function(zoom, index) {
+      cam.zoom = zoom;
+      ok(cam.zoom === zoom,
+        "zoom[" + index + "] = " + zoom + "x, cam.zoom = " + cam.zoom + "x");
+    });
 
-// The array of tests. Because camera capabilities don't change over the life
-// of a CameraControl object, they are only read once when the CameraControl is
-// created; so we have to call the 'prep' function first to set the fake
-// capability pref, before we call getCamera() and call 'test' to run the test.
-var tests = [
-  {
-    key: "fake-zoom",
-    prep: function setupFakeZoom(test) {
-      test.setFakeParameters("zoom-ratios=100,150,200,300,400;max-zoom=4", function() {
-        run();
-      });
-    },
-    test: function testFakeZoom(cam, cap) {
-      ok(cap.zoomRatios.length == 5, "zoom ratios length = " + cap.zoomRatios.length);
+    // test below-lower-bound zoom ratio
+    var zoom = cap.zoomRatios[0] - 0.1;
+    cam.zoom = zoom;
+    ok(cam.zoom === cap.zoomRatios[0],
+      zoom + "x zoom clamps to minimum: " +
+      cap.zoomRatios[0] + "x, cam.zoom = " + cam.zoom + "x");
 
-      // test individual zoom ratios
-      cap.zoomRatios.forEach(function(zoom, index) {
-        cam.zoom = zoom;
-        ok(cam.zoom === zoom,
-          "zoom[" + index + "] = " + zoom + "x, cam.zoom = " + cam.zoom + "x");
-      });
+    // test above-upper-bound zoom ratio
+    zoom = cap.zoomRatios.slice(-1)[0] + 1.0;
+    cam.zoom = zoom;
+    ok(cam.zoom === cap.zoomRatios.slice(-1)[0],
+      zoom + "x zoom clamps to maximum: " + cap.zoomRatios.slice(-1)[0] +
+      "x, cam.zoom = " + cam.zoom + "x");
 
-      // test below-lower-bound zoom ratio
-      var zoom = cap.zoomRatios[0] - 0.1;
+    // test snapping to supported zoom ratio
+    if (cap.zoomRatios.length > 1) {
+      zoom = (cap.zoomRatios[0] + cap.zoomRatios[1]) / 2;
       cam.zoom = zoom;
       ok(cam.zoom === cap.zoomRatios[0],
-        zoom + "x zoom clamps to minimum: " +
-        cap.zoomRatios[0] + "x, cam.zoom = " + cam.zoom + "x");
-
-      // test above-upper-bound zoom ratio
-      zoom = cap.zoomRatios.slice(-1)[0] + 1.0;
-      cam.zoom = zoom;
-      ok(cam.zoom === cap.zoomRatios.slice(-1)[0],
-        zoom + "x zoom clamps to maximum: " + cap.zoomRatios.slice(-1)[0] +
+        zoom + "x zoom rounded down to: " + cap.zoomRatios[0] +
         "x, cam.zoom = " + cam.zoom + "x");
-
-      // test snapping to supported zoom ratio
-      if (cap.zoomRatios.length > 1) {
-        zoom = (cap.zoomRatios[0] + cap.zoomRatios[1]) / 2;
-        cam.zoom = zoom;
-        ok(cam.zoom === cap.zoomRatios[0],
-          zoom + "x zoom rounded down to: " + cap.zoomRatios[0] +
-          "x, cam.zoom = " + cam.zoom + "x");
-      }
-
-      next();
-    }
-  },
-  {
-    key: "fake-zoom-out-of-order",
-    prep: function setupFakeZoomOutOfOrder(test) {
-      // We expect the camera library to give us zoom ratios in order; if
-      // it doesn't we ignore the list and just return 1x support.
-      test.setFakeParameters("zoom-ratios=100,150,200,400,300;max-zoom=4", function () {
-        run();
-      });
-    },
-    test: function testFakeZoomOutOfOrder(cam, cap) {
-      ok(cap.zoomRatios.length == 1, "zoom ratios length = " + cap.zoomRatios.length);
-      ok(cap.zoomRatios[0] == 1.0, "only supported zoom = " + cap.zoomRatios[0] + "x");
-      next();
-    }
-  },
-  {
-    key: "fake-high-memory-platform",
-    prep: function setupFakeHighMemoryPlatform(test) {
-      test.setFakeParameters("scene-mode-values=none,snow,beach,hdr,nothdr", function () {
-        run();
-      });
-    },
-    test: function testFakeHighMemoryPlatform(cam, cap) {
-      ok(cap.sceneModes.length == 5, "scene modes length = " + cap.zoomRatios.length);
-
-      // make sure expected values are present and can be set
-      [ "none", "snow", "beach", "hdr", "nothdr" ].forEach(function(mode) {
-        ok(cap.sceneModes.indexOf(mode) != -1, "Scene mode '" + mode + "' is present");
-        cam.sceneMode = mode;
-        ok(cam.sceneMode == mode, "Scene mode '" + cam.sceneMode + "' is set");
-      });
-
-      next();
     }
-  },
-  {
-    key: "fake-low-memory-platform",
-    prep: function setupFakeLowMemoryPlatform(test) {
-      test.setFakeLowMemoryPlatform(function() {
-        test.setFakeParameters("scene-mode-values=none,hdr,snow,beach,hdr,nothdr", function () {
-          run();
-        });
-      });
-    },
-    test: function testFakeLowMemoryPlatform(cam, cap) {
-      ok(cap.sceneModes.length == 4, "scene modes length = " + cap.zoomRatios.length);
+  }
+
+  return suite.getCamera()
+    .then(resolve, suite.rejectGetCamera);
+});
+
+
+suite.test('fake-zoom-out-of-order', function() {
+  // We expect the camera library to give us zoom ratios in order; if
+  // it doesn't we ignore the list and just return 1x support.
+  suite.hw.params['zoom-ratios'] = '100,150,200,400,300';
+  suite.hw.params['max-zoom'] = '4';
+
+  function resolve(p) {
+    var cap = suite.camera.capabilities;
+    ok(cap.zoomRatios.length == 1, "zoom ratios length = " + cap.zoomRatios.length);
+    ok(cap.zoomRatios[0] == 1.0, "only supported zoom = " + cap.zoomRatios[0] + "x");
+  }
+
+  return suite.getCamera()
+    .then(resolve, suite.rejectGetCamera);
+});
+
+suite.test('fake-high-memory-platform', function() {
+  suite.hw.params['scene-mode-values'] = 'none,snow,beach,hdr,nothdr';
+
+  function resolve(p) {
+    var cam = suite.camera;
+    var cap = cam.capabilities;
+    ok(cap.sceneModes.length == 5, "scene modes length = " + cap.zoomRatios.length);
+
+    // make sure expected values are present and can be set
+    [ "none", "snow", "beach", "hdr", "nothdr" ].forEach(function(mode) {
+      ok(cap.sceneModes.indexOf(mode) != -1, "Scene mode '" + mode + "' is present");
+      cam.sceneMode = mode;
+      ok(cam.sceneMode == mode, "Scene mode '" + cam.sceneMode + "' is set");
+    });
+  }
+
+  return suite.getCamera()
+    .then(resolve, suite.rejectGetCamera);
+});
+
+suite.test('fake-low-memory-platform', function() {
+  suite.hw.params['scene-mode-values'] = 'none,hdr,snow,beach,hdr,nothdr';
+
+  function resolve(p) {
+    var cam = suite.camera;
+    var cap = cam.capabilities;
+    ok(cap.sceneModes.length == 4, "scene modes length = " + cap.zoomRatios.length);
+
+    // make sure expected values are present and can be set
+    [ "none", "snow", "beach", "nothdr" ].forEach(function(mode) {
+      ok(cap.sceneModes.indexOf(mode) != -1, "Scene mode '" + mode + "' is present");
+      cam.sceneMode = mode;
+      ok(cam.sceneMode == mode, "Scene mode '" + cam.sceneMode + "' is set");
+    });
 
-      // make sure expected values are present and can be set
-      [ "none", "snow", "beach", "nothdr" ].forEach(function(mode) {
-        ok(cap.sceneModes.indexOf(mode) != -1, "Scene mode '" + mode + "' is present");
+    // make sure unsupported values have been removed, and can't be set
+    var sceneMode = cam.sceneMode;
+    [ "hdr" ].forEach(function(mode) {
+      ok(cap.sceneModes.indexOf(mode) == -1, "Scene mode '" + mode + "' is not present");
+      try {
         cam.sceneMode = mode;
-        ok(cam.sceneMode == mode, "Scene mode '" + cam.sceneMode + "' is set");
-      });
+      } catch(e) {
+      }
+      ok(cam.sceneMode != mode, "Scene mode '" + cam.sceneMode + "' is still set, '"
+        + mode + "' rejected");
+    });
+    ok(cam.sceneMode == sceneMode, "Scene mode '" + cam.sceneMode + "' is still set");
+  }
+
+  return suite.setLowMemoryPlatform()
+    .then(suite.getCamera)
+    .then(resolve, suite.rejectGetCamera);
+});
+
+suite.test('fake-iso', function() {
+  suite.hw.params['iso'] = 'auto';
+  suite.hw.params['iso-values'] = 'auto,ISO_HJR,ISO100,foo,ISObar,ISO150moz,ISO200,400,ISO800,1600';
+
+  function resolve(p) {
+    var cam = suite.camera;
+    var cap = cam.capabilities;
+    ok(cap.isoModes.length == 7, "ISO modes length = " + cap.isoModes.length);
 
-      // make sure unsupported values have been removed, and can't be set
-      var sceneMode = cam.sceneMode;
-      [ "hdr" ].forEach(function(mode) {
-        ok(cap.sceneModes.indexOf(mode) == -1, "Scene mode '" + mode + "' is not present");
-        try {
-          cam.sceneMode = mode;
-        } catch(e) {
-        }
-        ok(cam.sceneMode != mode, "Scene mode '" + cam.sceneMode + "' is still set, '"
-          + mode + "' rejected");
-      });
-      ok(cam.sceneMode == sceneMode, "Scene mode '" + cam.sceneMode + "' is still set");
+    // make sure we're not leaking any unexpected values formats
+    [ "ISO_HJR", "_HJR", "HJR", "ISO100", "ISO200", "ISO800" ].forEach(function(iso) {
+      ok(cap.isoModes.indexOf(iso) == -1, "ISO mode '" + iso + "' does not appear");
+    });
+
+    // make sure any weird values are dropped entirely
+    [ "foo", "ISObar", "bar", "ISO150moz", "150moz", "150" ].forEach(function(iso) {
+      ok(cap.isoModes.indexOf(iso) == -1, "Unknown ISO mode '" + iso + "' is ignored");
+    });
+
+    // make sure expected values are present
+    [ "auto", "hjr", "100", "200", "400", "800", "1600" ].forEach(function(iso) {
+      ok(cap.isoModes.indexOf(iso) != -1, "ISO mode '" + iso + "' is present");
+    });
+
+    // test setters/getters for individual ISO modes
+    cap.isoModes.forEach(function(iso, index) {
+      cam.iso = iso;
+      ok(cam.iso === iso,
+        "ISO[" + index + "] = " + iso + ", cam.iso = " + cam.iso);
+    });
+  }
+
+  return suite.getCamera()
+    .then(resolve, suite.rejectGetCamera);
+});
+
+suite.test('fake-metering-areas', function() {
+  suite.hw.params['max-num-metering-areas'] = '1';
+
+  function resolve(p) {
+    var cam = suite.camera;
+    var cap = cam.capabilities;
 
-      next();
-    }
-  },
-  {
-    key: "fake-iso",
-    prep: function setupFakeIso(test) {
-      // we should recognize 'auto', 'hjr', and numeric modes; anything else
-      // from the driver is ignored, which this test also verifies.
-      test.setFakeParameters(
-        "iso=auto;iso-values=auto,ISO_HJR,ISO100,foo,ISObar,ISO150moz,ISO200,400,ISO800,1600",
-        function () {
-        run();
-      });
-    },
-    test: function testFakeIso(cam, cap) {
-      ok(cap.isoModes.length == 7, "ISO modes length = " + cap.isoModes.length);
+    ok(cap.maxMeteringAreas == 1, "maxMeteringAreas = " + cap.maxMeteringAreas);
+    cam.setMeteringAreas([
+      {top: -500, bottom: 500, left: -500, right: 500, weight: 100}
+    ]);
+    areas = cam.getMeteringAreas();
+    ok(areas.length == 1, "areas length = " + areas.length);
+    ok(areas[0].top == -500, "area[0] top = " + areas[0].top);
+    ok(areas[0].bottom == 500, "area[0] bottom = " + areas[0].bottom);
+    ok(areas[0].left == -500, "area[0] left = " + areas[0].left);
+    ok(areas[0].right == 500, "area[0] right = " + areas[0].right);
+    ok(areas[0].weight == 100, "area[0] weight = " + areas[0].weight);
+    cam.setMeteringAreas([
+      {top: -501, bottom: 502, left: -503, right: 504, weight: 105},
+      {top: -500, bottom: 500, left: -500, right: 500, weight: 100}
+    ]);
+    areas = cam.getMeteringAreas();
+    ok(areas.length == 1, "areas length = " + areas.length);
+    ok(areas[0].top == -501, "area[0] top = " + areas[0].top);
+    ok(areas[0].bottom == 502, "area[0] bottom = " + areas[0].bottom);
+    ok(areas[0].left == -503, "area[0] left = " + areas[0].left);
+    ok(areas[0].right == 504, "area[0] right = " + areas[0].right);
+    ok(areas[0].weight == 105, "area[0] weight = " + areas[0].weight);
+  }
+
+  return suite.getCamera()
+    .then(resolve, suite.rejectGetCamera);
+});
+
+suite.test('fake-focus-areas', function() {
+  suite.hw.params['max-num-focus-areas'] = '1';
+
+  function resolve(p) {
+    var cam = suite.camera;
+    var cap = cam.capabilities;
 
-      // make sure we're not leaking any unexpected values formats
-      [ "ISO_HJR", "_HJR", "HJR", "ISO100", "ISO200", "ISO800" ].forEach(function(iso) {
-        ok(cap.isoModes.indexOf(iso) == -1, "ISO mode '" + iso + "' does not appear");
-      });
+    ok(cap.maxFocusAreas == 1, "maxFocusAreas = " + cap.maxFocusAreas);
+    cam.setFocusAreas([
+      {top: -500, bottom: 500, left: -500, right: 500, weight: 100}
+    ]);
+    areas = cam.getFocusAreas();
+    ok(areas.length == 1, "areas length = " + areas.length);
+    ok(areas[0].top == -500, "area[0] top = " + areas[0].top);
+    ok(areas[0].bottom == 500, "area[0] bottom = " + areas[0].bottom);
+    ok(areas[0].left == -500, "area[0] left = " + areas[0].left);
+    ok(areas[0].right == 500, "area[0] right = " + areas[0].right);
+    ok(areas[0].weight == 100, "area[0] weight = " + areas[0].weight);
+    cam.setFocusAreas([
+      {top: -501, bottom: 502, left: -503, right: 504, weight: 105},
+      {top: -500, bottom: 500, left: -500, right: 500, weight: 100}
+    ]);
+    areas = cam.getFocusAreas();
+    ok(areas.length == 1, "areas length = " + areas.length);
+    ok(areas[0].top == -501, "area[0] top = " + areas[0].top);
+    ok(areas[0].bottom == 502, "area[0] bottom = " + areas[0].bottom);
+    ok(areas[0].left == -503, "area[0] left = " + areas[0].left);
+    ok(areas[0].right == 504, "area[0] right = " + areas[0].right);
+    ok(areas[0].weight == 105, "area[0] weight = " + areas[0].weight);
+  }
+
+  return suite.getCamera()
+    .then(resolve, suite.rejectGetCamera);
+});
+
+suite.test('fake-exposure-compensation', function() {
+  suite.hw.params['max-num-focus-areas'] = '1';
+  suite.hw.params['exposure-compensation'] = '-1';
+  suite.hw.params['max-exposure-compensation'] = '6';
+  suite.hw.params['min-exposure-compensation'] = '-6';
+  suite.hw.params['exposure-compensation-step'] = '0.5'
+
+  function resolve(p) {
+    var cam = suite.camera;
+    var cap = cam.capabilities;
 
-      // make sure any weird values are dropped entirely
-      [ "foo", "ISObar", "bar", "ISO150moz", "150moz", "150" ].forEach(function(iso) {
-        ok(cap.isoModes.indexOf(iso) == -1, "Unknown ISO mode '" + iso + "' is ignored");
-      });
+    ok(cap.exposureCompensationStep == 0.5,
+       "exposureCompensationStep = " + cap.exposureCompensationStep);
+    ok(cap.minExposureCompensation == -3.0,
+       "minExposureCompensation = " + cap.minExposureCompensation);
+    ok(cap.maxExposureCompensation == 3.0,
+       "maxExposureCompensation = " + cap.maxExposureCompensation);
+    ok(cam.exposureCompensation == -0.5,
+       "exposureCompensation = " + cam.exposureCompensation);
 
-      // make sure expected values are present
-      [ "auto", "hjr", "100", "200", "400", "800", "1600" ].forEach(function(iso) {
-        ok(cap.isoModes.indexOf(iso) != -1, "ISO mode '" + iso + "' is present");
-      });
+    // Check normal values
+    cam.exposureCompensation = 0.0;
+    ok(cam.exposureCompensation == 0.0,
+       "exposureCompensation = " + cam.exposureCompensation);
+    cam.exposureCompensation = cap.minExposureCompensation;
+    ok(cam.exposureCompensation == cap.minExposureCompensation,
+       "exposureCompensation(min) = " + cam.exposureCompensation);
+    cam.exposureCompensation = cap.maxExposureCompensation;
+    ok(cam.exposureCompensation == cap.maxExposureCompensation,
+       "exposureCompensation(max) = " + cam.exposureCompensation);
 
-      // test setters/getters for individual ISO modes
-      cap.isoModes.forEach(function(iso, index) {
-        cam.iso = iso;
-        ok(cam.iso === iso,
-          "ISO[" + index + "] = " + iso + ", cam.iso = " + cam.iso);
-      });
+    // Rounding
+    cam.exposureCompensation = 1.24;
+    ok(cam.exposureCompensation == 1.0,
+       "exposureCompensation(1.24) = " + cam.exposureCompensation);
+    cam.exposureCompensation = 1.25;
+    ok(cam.exposureCompensation == 1.5,
+       "exposureCompensation(1.25) = " + cam.exposureCompensation);
+    cam.exposureCompensation = -1.24;
+    ok(cam.exposureCompensation == -1.0,
+       "exposureCompensation(-1.24) = " + cam.exposureCompensation);
+    cam.exposureCompensation = -1.25;
+    ok(cam.exposureCompensation == -1.5,
+       "exposureCompensation(-1.25) = " + cam.exposureCompensation);
+    // Check out-of-bounds values
+    cam.exposureCompensation = cap.minExposureCompensation - 1.0;
+    ok(cam.exposureCompensation == cap.minExposureCompensation,
+       "exposureCompensation(min - 1.0) = " + cam.exposureCompensation);
+    cam.exposureCompensation = cap.maxExposureCompensation + 1.0;
+    ok(cam.exposureCompensation == cap.maxExposureCompensation,
+       "exposureCompensation(max + 1.0) = " + cam.exposureCompensation);
 
-      next();
-    }
-  },
-  {
-    key: "fake-metering-areas",
-    prep: function setupFakeMeteringAreas(test) {
-      test.setFakeParameters("max-num-metering-areas=1", function () {
-        run();
+    // Check extreme values
+    cam.exposureCompensation = -1 * Math.pow(2, 32);
+    ok(cam.exposureCompensation == cap.minExposureCompensation,
+       "exposureCompensation(-2^32) = " + cam.exposureCompensation);
+    cam.exposureCompensation = Math.pow(2, 32);
+    ok(cam.exposureCompensation == cap.maxExposureCompensation,
+       "exposureCompensation(2^32) = " + cam.exposureCompensation);
+  }
+
+  return suite.getCamera()
+    .then(resolve, suite.rejectGetCamera);
+});
+
+suite.test('bug-1054803', function() {
+  // The important part of this test is that 3264 * 1836 = 5,992,704 = 2448 * 2448,
+  // so we need to make sure that the size-matching algorithm picks the right size.
+  suite.hw.params['picture-size-values'] = '3264x1836,2448x2448,1836x3264';
+
+  function verify(p) {
+    var cam = suite.camera;
+    var cap = cam.capabilities;
+
+    var expSizes = [ { height: 3264, width: 1836 },
+                     { height: 1836, width: 3264 },
+                     { height: 2448, width: 2448 } ];
+
+    // validate the capability attribute
+    ok(cap.pictureSizes.length == expSizes.length, "pictureSizes.length = " + cap.pictureSizes.length);
+    var found = 0;
+    expSizes.forEach(function(size) {
+      found = 0;
+      cap.pictureSizes.forEach(function(capSize) {
+        if (capSize.height == size.height && capSize.width == size.width) {
+          ++found;
+        }
       });
-    },
-    test: function testFakeMeteringAreas(cam, cap) {
-      ok(cap.maxMeteringAreas == 1, "maxMeteringAreas = " + cap.maxMeteringAreas);
-      cam.setMeteringAreas([
-        {top: -500, bottom: 500, left: -500, right: 500, weight: 100}
-      ]);
-      areas = cam.getMeteringAreas();
-      ok(areas.length == 1, "areas length = " + areas.length);
-      ok(areas[0].top == -500, "area[0] top = " + areas[0].top);
-      ok(areas[0].bottom == 500, "area[0] bottom = " + areas[0].bottom);
-      ok(areas[0].left == -500, "area[0] left = " + areas[0].left);
-      ok(areas[0].right == 500, "area[0] right = " + areas[0].right);
-      ok(areas[0].weight == 100, "area[0] weight = " + areas[0].weight);
-      cam.setMeteringAreas([
-        {top: -501, bottom: 502, left: -503, right: 504, weight: 105},
-        {top: -500, bottom: 500, left: -500, right: 500, weight: 100}
-      ]);
-      areas = cam.getMeteringAreas();
-      ok(areas.length == 1, "areas length = " + areas.length);
-      ok(areas[0].top == -501, "area[0] top = " + areas[0].top);
-      ok(areas[0].bottom == 502, "area[0] bottom = " + areas[0].bottom);
-      ok(areas[0].left == -503, "area[0] left = " + areas[0].left);
-      ok(areas[0].right == 504, "area[0] right = " + areas[0].right);
-      ok(areas[0].weight == 105, "area[0] weight = " + areas[0].weight);
-
-      next();
-    },
-  },
-  {
-    key: "fake-focus-areas",
-    prep: function setupFakeFocusAreas(test) {
-      test.setFakeParameters("max-num-focus-areas=1", function () {
-        run();
-      });
-    },
-    test: function testFakeFocusAreas(cam, cap) {
-      ok(cap.maxFocusAreas == 1, "maxFocusAreas = " + cap.maxFocusAreas);
-      cam.setFocusAreas([
-        {top: -500, bottom: 500, left: -500, right: 500, weight: 100}
-      ]);
-      areas = cam.getFocusAreas();
-      ok(areas.length == 1, "areas length = " + areas.length);
-      ok(areas[0].top == -500, "area[0] top = " + areas[0].top);
-      ok(areas[0].bottom == 500, "area[0] bottom = " + areas[0].bottom);
-      ok(areas[0].left == -500, "area[0] left = " + areas[0].left);
-      ok(areas[0].right == 500, "area[0] right = " + areas[0].right);
-      ok(areas[0].weight == 100, "area[0] weight = " + areas[0].weight);
-      cam.setFocusAreas([
-        {top: -501, bottom: 502, left: -503, right: 504, weight: 105},
-        {top: -500, bottom: 500, left: -500, right: 500, weight: 100}
-      ]);
-      areas = cam.getFocusAreas();
-      ok(areas.length == 1, "areas length = " + areas.length);
-      ok(areas[0].top == -501, "area[0] top = " + areas[0].top);
-      ok(areas[0].bottom == 502, "area[0] bottom = " + areas[0].bottom);
-      ok(areas[0].left == -503, "area[0] left = " + areas[0].left);
-      ok(areas[0].right == 504, "area[0] right = " + areas[0].right);
-      ok(areas[0].weight == 105, "area[0] weight = " + areas[0].weight);
-
-      next();
-    },
-  },
-  {
-    key: "fake-exposure-compensation",
-    prep: function setupFakeExposureCompensation(test) {
-      test.setFakeParameters("exposure-compensation=-1;max-exposure-compensation=6;min-exposure-compensation=-6;exposure-compensation-step=0.5", function () {
-        run();
-      });
-    },
-    test: function testFakeFocusAreas(cam, cap) {
-      ok(cap.exposureCompensationStep == 0.5,
-         "exposureCompensationStep = " + cap.exposureCompensationStep);
-      ok(cap.minExposureCompensation == -3.0,
-         "minExposureCompensation = " + cap.minExposureCompensation);
-      ok(cap.maxExposureCompensation == 3.0,
-         "maxExposureCompensation = " + cap.maxExposureCompensation);
-      ok(cam.exposureCompensation == -0.5,
-         "exposureCompensation = " + cam.exposureCompensation);
+      ok(found == 1, "found size " + size.toSource() + " in pictureSizes");
+    });
 
-      // Check normal values
-      cam.exposureCompensation = 0.0;
-      ok(cam.exposureCompensation == 0.0,
-         "exposureCompensation = " + cam.exposureCompensation);
-      cam.exposureCompensation = cap.minExposureCompensation;
-      ok(cam.exposureCompensation == cap.minExposureCompensation,
-         "exposureCompensation(min) = " + cam.exposureCompensation);
-      cam.exposureCompensation = cap.maxExposureCompensation;
-      ok(cam.exposureCompensation == cap.maxExposureCompensation,
-         "exposureCompensation(max) = " + cam.exposureCompensation);
-
-      // Rounding
-      cam.exposureCompensation = 1.24;
-      ok(cam.exposureCompensation == 1.0,
-         "exposureCompensation(1.24) = " + cam.exposureCompensation);
-      cam.exposureCompensation = 1.25;
-      ok(cam.exposureCompensation == 1.5,
-         "exposureCompensation(1.25) = " + cam.exposureCompensation);
-      cam.exposureCompensation = -1.24;
-      ok(cam.exposureCompensation == -1.0,
-         "exposureCompensation(-1.24) = " + cam.exposureCompensation);
-      cam.exposureCompensation = -1.25;
-      ok(cam.exposureCompensation == -1.5,
-         "exposureCompensation(-1.25) = " + cam.exposureCompensation);
-      // Check out-of-bounds values
-      cam.exposureCompensation = cap.minExposureCompensation - 1.0;
-      ok(cam.exposureCompensation == cap.minExposureCompensation,
-         "exposureCompensation(min - 1.0) = " + cam.exposureCompensation);
-      cam.exposureCompensation = cap.maxExposureCompensation + 1.0;
-      ok(cam.exposureCompensation == cap.maxExposureCompensation,
-         "exposureCompensation(max + 1.0) = " + cam.exposureCompensation);
-
-      // Check extreme values
-      cam.exposureCompensation = -1 * Math.pow(2, 32);
-      ok(cam.exposureCompensation == cap.minExposureCompensation,
-         "exposureCompensation(-2^32) = " + cam.exposureCompensation);
-      cam.exposureCompensation = Math.pow(2, 32);
-      ok(cam.exposureCompensation == cap.maxExposureCompensation,
-         "exposureCompensation(2^32) = " + cam.exposureCompensation);
-
-      next();
-    },
-  },
-  {
-    key: "bug-1054803",
-    prep: function setupFakePictureSizes(test) {
-      // The important part of this test is that 3264 * 1836 = 5,992,704 = 2448 * 2448,
-      // so we need to make sure that the size-matching algorithm picks the right size.
-      test.setFakeParameters("picture-size-values=3264x1836,2448x2448,1836x3264", function () {
-        run();
-      });
-    },
-    test: function testFakePictureSizes(cam, cap) {
-      // validate the capability attribute
-      ok(cap.pictureSizes.length == 3, "pictureSizes.length = " + cap.pictureSizes.length);
-      var found = 0;
-      [ { height: 3264, width: 1836 },
-        { height: 1836, width: 3264 },
-        { height: 2448, width: 2448 } ].forEach(function(size) {
-        found = 0;
-        cap.pictureSizes.forEach(function(capSize) {
-          if (capSize.height == size.height && capSize.width == size.width) {
-            ++found;
-          }
-        });
-        ok(found == 1, "found size " + size.toSource() + " in pictureSizes");
-      });
-
-      // test setters and getters
-      var sync = new Promise(function(resolve, reject) {
-        // Use setConfiguration() (which will fail on the profile)
-        // to signify that the CameraControl thread has run and our
-        // settings are applied. Yes--this is an ugly hack.
-        cam.setConfiguration({ mode: 'video',
-                               recorderProfile: 'weird-unsupported-profile'
-                             }).then(resolve, resolve);
-      });
-      var sizeGenerator = function() {
-        var sizes = [ { height: 3264, width: 1836 },
-                      { height: 1836, width: 3264 },
-                      { height: 2448, width: 2448 } ];
-        for (var i = 0; i < sizes.length; ++i) {
-          yield sizes[i];
-        }
-      }();
-
+    var sizeGenerator = Iterator(expSizes);
+    return new Promise(function(resolve, reject) {
       function nextSize() {
         try {
-          var size = sizeGenerator.next();
+          var size = sizeGenerator.next()[1];
+          var sync = suite.waitParameterPush();
           cam.setPictureSize(size);
           sync.then(function() {
             var got = cam.getPictureSize();
             ok(got.width == size.width && got.height == size.height,
               "Set size " + size.toSource() + ", got size " + got.toSource());
             nextSize();
-          }, onError);
+          }, reject);
         } catch(e) {
           if (e instanceof StopIteration) {
-            next();
+            resolve();
           } else {
-            throw e;
+            reject(e);
           }
         }
       }
 
       nextSize();
-    },
-  },
-  {
-    key: "bug-1052851",
-    prep: function setupFakeMetering(test) {
-      // We should reject duplicate values.
-      test.setFakeParameters(
-        "auto-exposure=frame-average;auto-exposure-values=spot,frame-average,center-weighted,spot,center-weighted",
-        function () {
-        run();
-      });
-    },
-    test: function testFakeMetering(cam, cap) {
-      ok(cap.meteringModes.length == 3, "Metering modes length = " + cap.meteringModes.length);
-
-      // make sure expected values are present
-      [ "spot", "frame-average", "center-weighted" ].forEach(function(mode) {
-        ok(cap.meteringModes.indexOf(mode) != -1, "Metering mode '" + mode + "' is present");
-      });
+    });
+  }
 
-      // test setters/getters for individual metering modes
-      cap.meteringModes.forEach(function(mode, index) {
-        cam.meteringMode = mode;
-        ok(cam.meteringMode === mode,
-          "Metering Mode[" + index + "] = " + mode + ", cam.meteringMode = " + cam.meteringMode);
-      });
-
-      next();
-    }
-  },
-];
-
-var testGenerator = function() {
-  for (var i = 0; i < tests.length; ++i) {
-    yield tests[i];
-  }
-}();
-
-window.addEventListener('beforeunload', function() {
-  document.getElementById('viewfinder').mozSrcObject = null;
-  if (cameraObj) {
-    cameraObj.release();
-    cameraObj = null;
-  }
+  return suite.getCamera()
+    .then(verify, suite.rejectGetCamera);
 });
 
-CameraTest.begin("hardware", function(test) {
-  function onError(error) {
-    ok(false, "getCamera() failed with: " + error);
-    end();
+suite.test('bug-1052851', function() {
+  // We should reject duplicate values.
+  suite.hw.params['auto-exposure'] = 'frame-average';
+  suite.hw.params['auto-exposure-values'] = 'spot,frame-average,center-weighted,spot,center-weighted';
+
+  function resolve(p) {
+    var cam = suite.camera;
+    var cap = cam.capabilities;
+
+    ok(cap.meteringModes.length == 3, "Metering modes length = " + cap.meteringModes.length);
+
+    // make sure expected values are present
+    [ "spot", "frame-average", "center-weighted" ].forEach(function(mode) {
+      ok(cap.meteringModes.indexOf(mode) != -1, "Metering mode '" + mode + "' is present");
+    });
+
+    // test setters/getters for individual metering modes
+    cap.meteringModes.forEach(function(mode, index) {
+      cam.meteringMode = mode;
+      ok(cam.meteringMode === mode,
+        "Metering Mode[" + index + "] = " + mode + ", cam.meteringMode = " + cam.meteringMode);
+    });
   }
 
-  CameraTest.next = function() {
-    try {
-      var t = testGenerator.next();
-      info("test: " + t.key);
-      function onSuccess(d) {
-        cameraObj = d.camera;
-        document.getElementById('viewfinder').mozSrcObject = d.camera;
-        var onPreviewStateChange = function (evt) {
-          if (evt.newState === "started") {
-            cameraObj.removeEventListener('previewstatechange', onPreviewStateChange);
-            t.test(cameraObj, cameraObj.capabilities);
-          }
-        };
-        cameraObj.addEventListener('previewstatechange', onPreviewStateChange);
-      }
-      CameraTest.run = function() {
-        navigator.mozCameras.getCamera(whichCamera, initialConfig).then(onSuccess, onError);
-      };
-      t.prep(test);
-    } catch(e) {
-      if (e instanceof StopIteration) {
-        end();
-      } else {
-        throw e;
-      }
-    }
-  };
-  next();
+  return suite.getCamera()
+    .then(resolve, suite.rejectGetCamera);
 });
 
+suite.setup()
+  .then(suite.run);
+
 </script>
 </body>
 
 </html>
--- a/dom/camera/test/test_camera_hardware_auto_focus_moving_cb.html
+++ b/dom/camera/test/test_camera_hardware_auto_focus_moving_cb.html
@@ -12,111 +12,36 @@ https://bugzilla.mozilla.org/show_bug.cg
 </head>
 <body>
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=965421">Mozilla Bug 965421</a>
   <video id="viewfinder" width = "200" height = "200" autoplay></video>
   <img src="#" alt="This image is going to load" id="testimage"/>
 
 <script class="testbody" type="text/javascript;version=1.7">
 
-var whichCamera = navigator.mozCameras.getListOfCameras()[0];
-var initialConfig = {
-  mode: 'picture',
-  recorderProfile: 'high',
-  previewSize: {
-    width: 352,
-    height: 288
-  }
-};
-
-const PREF_AUTOFOCUSCALLBACK_ENABLED = "camera.control.autofocus_moving_callback.enabled";
+var suite = new CameraTestSuite();
 
-var cameraObj;
-var oldPref;
+suite.test('auto-focus-moving', function() {
+  function triggerAutoFocusMoving(p) {
+    var sync = new Promise(function(resolve, reject) {
+      function onEvent(e) {
+        suite.camera.removeEventListener('focus', onEvent);
+        ok(e.newState === 'focusing', 'autofocus event state focusing == ' + e.newState);
+        resolve();
+      }
+      suite.camera.addEventListener('focus', onEvent);
+    });
 
-// Shorthand functions
-function end() {
-  function reallyEnd() {
-    CameraTest.end();
-  }
-  if (oldPref) {
-    SpecialPowers.pushPrefEnv(
-      {'set': [[PREF_AUTOFOCUSCALLBACK_ENABLED, oldPref]]}, reallyEnd);
-  } else {
-    SpecialPowers.pushPrefEnv(
-      {'clear': [[PREF_AUTOFOCUSCALLBACK_ENABLED]]}, reallyEnd);
+    suite.hw.fireAutoFocusMoving(true);
+    return sync;
   }
-}
-function next() {
-  CameraTest.next();
-}
 
-var tests = [
-  {
-    key: "autofocus-moving-true",
-    func: function testAutoFocusMovingIsTrue(camera) {
-      var handler = function(evt) {
-        camera.removeEventListener("focus", handler);
-        ok(evt.newState == "focusing", "autofocus event state focusing == " + evt.newState);
-        camera.focusMode = 'auto';
-        next();
-      }
-      camera.addEventListener("focus", handler);
-      camera.focusMode = 'continuous-picture';
-    }
-  },
-];
-
-var testGenerator = function() {
-  for (var i = 0; i < tests.length; ++i ) {
-    yield tests[i];
-  }
-}();
-
-window.addEventListener('beforeunload', function() {
-  document.getElementById('viewfinder').mozSrcObject = null;
-  cameraObj.release();
-  cameraObj = null;
+  return suite.getCamera()
+    .then(triggerAutoFocusMoving);
 });
 
-// Must call CameraTest.begin() before any other async methods.
-CameraTest.begin("hardware", function(test) {
-  // If the pref doesn't exist, this get will fail; catch it and continue.
-  try {
-    oldPref = SpecialPowers.getBoolPref(PREF_AUTOFOCUSCALLBACK_ENABLED);
-  } catch(e) { }
-
-  SpecialPowers.pushPrefEnv({'set': [[PREF_AUTOFOCUSCALLBACK_ENABLED, true]]}, function() {
-    var enabled;
-    try {
-      enabled = SpecialPowers.getBoolPref(PREF_AUTOFOCUSCALLBACK_ENABLED);
-    } catch(e) { }
-    ok(enabled, PREF_AUTOFOCUSCALLBACK_ENABLED + " is " + enabled);
-
-    function onSuccess(d) {
-      document.getElementById('viewfinder').mozSrcObject = d.camera;
-      cameraObj = d.camera;
-      CameraTest.next = function() {
-        try {
-          var t = testGenerator.next();
-          test.set(t.key, t.func.bind(undefined, d.camera));
-        } catch(e) {
-          if (e instanceof StopIteration) {
-            end();
-          } else {
-            throw e;
-          }
-        }
-      };
-      next();
-    }
-    function onError(error) {
-      ok(false, "getCamera() failed with: " + error);
-      end();
-    }
-    navigator.mozCameras.getCamera(whichCamera, initialConfig).then(onSuccess, onError);
-  })
-});
+suite.setup()
+  .then(suite.run);
 
 </script>
 </body>
 
 </html>
--- a/dom/camera/test/test_camera_hardware_face_detection.html
+++ b/dom/camera/test/test_camera_hardware_face_detection.html
@@ -12,48 +12,16 @@ https://bugzilla.mozilla.org/show_bug.cg
 </head>
 <body>
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=965420">Mozilla Bug 965420</a>
   <video id="viewfinder" width = "200" height = "200" autoplay></video>
   <img src="#" alt="This image is going to load" id="testimage"/>
 
 <script class="testbody" type="text/javascript;version=1.7">
 
-var whichCamera = navigator.mozCameras.getListOfCameras()[0];
-var initialConfig = {
-  mode: 'picture',
-  recorderProfile: 'high',
-  previewSize: {
-    width: 352,
-    height: 288
-  }
-};
-
-const PREF_FACEDETECTION_ENABLED = "camera.control.face_detection.enabled";
-
-var cameraObj;
-var oldPref;
-
-// Shorthand functions
-function end() {
-  function reallyEnd() {
-    CameraTest.end();
-  }
-  if (oldPref) {
-    SpecialPowers.pushPrefEnv(
-      {'set': [[PREF_FACEDETECTION_ENABLED, oldPref]]}, reallyEnd);
-  } else {
-    SpecialPowers.pushPrefEnv(
-      {'clear': [[PREF_FACEDETECTION_ENABLED]]}, reallyEnd);
-  }
-}
-function next() {
-  CameraTest.next();
-}
-
 function compareFaces(aFaces, expected)
 {
   ok(aFaces, "have detected faces object");
   ok(aFaces.length == expected.faces.length,
     "expected=" + expected.faces.length + ", got=" + aFaces.length);
   aFaces.forEach(function (face, index) {
     let result = compareFace(face, expected.faces[index]);
     ok(result === "ok", "face check: " + result);
@@ -117,21 +85,41 @@ function compareFace(aFace, expected)
       (aFace.mouth.x != expected.mouth.x || aFace.mouth.y != expected.mouth.y)) {
     return "expected face.mouth=" + expected.mouth.toSource() +
       ", got=({x:" + aFace.mouth.x + ", y:" + aFace.mouth.y + "})";
   }
 
   return "ok";
 }
 
-var tests = [
-  {
-    key: "face-detection-detected-one-face",
-    func: function testFaceDetectionFoundOneFace(camera) {
-      var expected = {
+var suite = new CameraTestSuite();
+
+suite.test('face-detection', function() {
+  function detectFace(msg, expected) {
+    var sync = new Promise(function(resolve, reject) {
+      function onEvent(evt) {
+        try {
+          suite.camera.removeEventListener('facesdetected', onEvent);
+          ok(compareFaces(evt.faces, expected),
+            "facedetected event received " + msg + " correctly");
+          resolve();
+        } catch(e) {
+          reject(e);
+        }
+      }
+      suite.camera.addEventListener('facesdetected', onEvent);
+    });
+
+    suite.hw.fireFacesDetected(expected);
+    return sync;
+  }
+
+  function detectOneFace(p) {
+    return detectFace('one-face',
+      {
         faces: [ {
           id:       1,
           score:    2,
           bounds: {
             left:   3,
             top:    4,
             right:  5,
             bottom: 6
@@ -144,34 +132,23 @@ var tests = [
             x:      9,
             y:      10
           },
           mouth: {
             x:      11,
             y:      12
           }
         } ]
-      };
+      }
+    );
+  }
 
-      var handler = function(evt) {
-        ok(compareFaces(evt.faces, expected),
-          "facedetected event received the detected faces correctly");
-        camera.stopFaceDetection();
-        camera.removeEventListener('facesdetected', handler);
-        next();
-      };
-
-      camera.addEventListener('facesdetected', handler);
-      camera.startFaceDetection();
-    }
-  },
-  {
-    key: "face-detection-detected-two-faces",
-    func: function testFaceDetectionFoundTwoFace(camera) {
-      var expected = {
+  function detectTwoFaces(p) {
+    return detectFace('two-faces',
+      {
         faces: [ {
           id:       1,
           score:    2,
           bounds: {
             left:   3,
             top:    4,
             right:  5,
             bottom: 6
@@ -206,131 +183,54 @@ var tests = [
             x:      21,
             y:      22
           },
           mouth: {
             x:      23,
             y:      24
           }
         } ]
-      };
+      }
+    );
+  }
 
-      var handler = function(evt) {
-        ok(compareFaces(evt.faces, expected),
-          "facedetected event received the detected faces correctly");
-        camera.stopFaceDetection();
-        camera.removeEventListener('facesdetected', handler);
-        next();
-      };
-
-      camera.addEventListener('facesdetected', handler);
-      camera.startFaceDetection();
-    }
-  },
-  {
-    key: "face-detection-detected-one-face-no-features",
-    func: function (camera) {
-      var expected = {
+  function detectOneFaceNoFeatures() {
+    return detectFace('one-face-no-features',
+      {
         faces: [ {
           id:       1,
           score:    100,
           bounds: {
             left:   3,
             top:    4,
             right:  5,
             bottom: 6
           },
           leftEye:  null,
           rightEye: null,
           mouth:    null
         } ]
-      };
-
-      var handler = function(evt) {
-        ok(compareFaces(evt.faces, expected),
-          "facedetected event received the detected faces correctly");
-        camera.stopFaceDetection();
-        camera.removeEventListener('facesdetected', handler);
-        next();
-      };
-
-      camera.addEventListener('facesdetected', handler);
-      camera.startFaceDetection();
-    }
-  },
-  {
-    key: "face-detection-no-faces-detected",
-    func: function (camera) {
-      var expected = {
-        faces: []
-      };
+      }
+    );
+  }
 
-      var handler = function(evt) {
-        ok(compareFaces(evt.faces, expected),
-          "facedetected event received the detected faces correctly");
-        camera.stopFaceDetection();
-        camera.removeEventListener('facesdetected', handler);
-        next();
-      };
+  function detectNoFaces() {
+    return detectFace('no-faces',
+      {
+        faces: []
+      }
+    );
+  }
 
-      camera.addEventListener('facesdetected', handler);
-      camera.startFaceDetection();
-    }
-  },
-];
-
-var testGenerator = function() {
-  for (var i = 0; i < tests.length; ++i ) {
-    yield tests[i];
-  }
-}();
-
-window.addEventListener('beforeunload', function() {
-  document.getElementById('viewfinder').mozSrcObject = null;
-  if (cameraObj) {
-    cameraObj.release();
-    cameraObj = null;
-  }
+  return suite.getCamera()
+    .then(detectOneFace, suite.rejectGetCamera)
+    .then(detectTwoFaces)
+    .then(detectOneFaceNoFeatures)
+    .then(detectNoFaces);
 });
 
-// Must call CameraTest.begin() before any other async methods.
-CameraTest.begin("hardware", function(test) {
-  // If the pref doesn't exist, this get will fail; catch it and continue.
-  try {
-    oldPref = SpecialPowers.getBoolPref(PREF_FACEDETECTION_ENABLED);
-  } catch(e) { }
-
-  SpecialPowers.pushPrefEnv({'set': [[PREF_FACEDETECTION_ENABLED, true]]}, function() {
-    var enabled;
-    try {
-      enabled = SpecialPowers.getBoolPref(PREF_FACEDETECTION_ENABLED);
-    } catch(e) { }
-    ok(enabled, PREF_FACEDETECTION_ENABLED + " is " + enabled);
-
-    function onSuccess(d) {
-      cameraObj = d.camera;
-      document.getElementById('viewfinder').mozSrcObject = cameraObj;
-      CameraTest.next = function() {
-        try {
-          var t = testGenerator.next();
-          test.set(t.key, t.func.bind(undefined, cameraObj));
-        } catch(e) {
-          if (e instanceof StopIteration) {
-            end();
-          } else {
-            throw e;
-          }
-        }
-      };
-      next();
-    }
-    function onError(error) {
-      ok(false, "getCamera() failed with: " + error);
-      end();
-    }
-    navigator.mozCameras.getCamera(whichCamera, initialConfig).then(onSuccess, onError);
-  })
-});
+suite.setup()
+  .then(suite.run);
 
 </script>
 </body>
 
 </html>
--- a/dom/camera/test/test_camera_hardware_failures.html
+++ b/dom/camera/test/test_camera_hardware_failures.html
@@ -12,134 +12,162 @@ https://bugzilla.mozilla.org/show_bug.cg
 </head>
 <body>
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=940424">Mozilla Bug 940424</a>
   <video id="viewfinder" width = "200" height = "200" autoplay></video>
   <img src="#" alt="This image is going to load" id="testimage"/>
 
 <script class="testbody" type="text/javascript;version=1.7">
 
-var whichCamera = navigator.mozCameras.getListOfCameras()[0];
-var initialConfig = {
-  mode: 'picture',
-  recorderProfile: 'high',
-  previewSize: {
-    width: 352,
-    height: 288
-  }
-};
-
-var cameraObj;
-
-// Shorthand functions
-function end() {
-  CameraTest.end();
-}
-function next() {
-  CameraTest.next();
-}
+var suite = new CameraTestSuite();
 
-// The array of tests
-var tests = [
-  {
-    key: "auto-focus-failure",
-    func: function testAutoFocusApiFailure(camera) {
-      function onSuccess(success) {
-        ok(false, "autoFocus() succeeded incorrectly");
-        end();
-      }
-      function onError(error) {
-        ok(true, "autoFocus() failed correctly with: " + error);
-        next();
+/* Each discrete test case can be added to the test queue here.
+   They won't be run until you call suite setup and run. */
+suite.test('auto-focus-failures', function() {
+  function startAutoFocusFail(p) {
+    suite.hw.attach({
+      autoFocus: function() {
+        suite.hw.fireAutoFocusComplete(false);
       }
-      camera.autoFocus().then(onSuccess, onError);
-    }
-  },
-  {
-    key: "auto-focus-process-failure",
-    func: function testAutoFocusProcessFailure(camera) {
-      function onSuccess(success) {
-        if (success) {
-          ok(false, "autoFocus() process succeeded incorrectly");
-          end();
-        } else {
-          ok(true, "autoFocus() process failed correctly");
-          next();
-        }
+    });
+    return suite.camera.autoFocus();
+  }
+
+  function resolveAutoFocusFail(focused) {
+    ok(!focused, 'autoFocus() should be unfocused: ' + focused);
+  }
+
+  function startAutoFocusError(p) {
+    suite.hw.attach({
+      autoFocus: function() {
+        throw SpecialPowers.Cr.NS_ERROR_FAILURE;
       }
-      function onError(error) {
-        ok(false, "autoFocus() process failed incorrectly with: " + error);
-        end();
-      }
-      camera.autoFocus().then(onSuccess, onError);
-    }
-  },
-  {
-    key: "take-picture-failure",
-    func: function testTakePictureApiFailure(camera) {
-      function onSuccess(picture) {
-        ok(false, "takePicture() succeeded incorrectly");
-        end();
-      }
-      function onError(error) {
-        ok(true, "takePicture() failed correctly with: " + error);
-        next();
-      }
-      camera.takePicture(null).then(onSuccess, onError);
-    }
-  },
-  {
-    key: "take-picture-process-failure",
-    func: function testTakePictureProcessFailure(camera) {
-      function onSuccess(picture) {
-        ok(false, "takePicture() process succeeded incorrectly");
-        end();
-      }
-      function onError(error) {
-        ok(true, "takePicture() process failed correctly with: " + error);
-        next();
-      }
-      camera.takePicture(null).then(onSuccess, onError);
-    }
-  },
-];
+    });
+    return suite.camera.autoFocus();
+  }
+
+  function rejectAutoFocusError(e) {
+    ok(e.name === 'NS_ERROR_FAILURE', 'autoFocus() should fail: ' + e);
+  }
+
+  /* This is the promise chain which drives the test execution.
+
+     suite.getCamera
+       This returns a promise which is resolved when the suite
+       acquires the camera object (i.e. returns as if one
+       called cameraManager.getCamera() directly). It saves
+       the CameraControl reference in suite.camera and will
+       automatically release it for you when the test is
+       completed.
+
+     .then(startAutoFocusFail, suite.rejectGetCamera)
+       If the getCamera promise is resolved, startAutoFocusFail
+       will be called. That function attaches a handler to
+       the camera test hardware to intercept the auto focus
+       driver call so that we can change the behaviour to
+       trigger an OnAutoFocusComplete(false) event. It then
+       calls camera.autoFocus() and returns the promise for
+       that camera call so that the next promise in the chain
+       can block waiting for that promise to be fulfilled.
+
+       If the getCamera promise is rejected,
+       suite.rejectGetCamera will be called. This is a helper
+       handler provided by the test suite; it will log
+       a get camera failure and fail the test via ok, and
+       return a new promise rejecting *without* an error
+       object (i.e. promise parameter is undefined). This is
+       important because all reject handlers further down
+       the promise chain will still be called due to the
+       failure. However since we replaced the promise with
+       an error object, with a promise without one, we can
+       use this to identify errors that have already been
+       handled.
 
-var testGenerator = function() {
-  for (var i = 0; i < tests.length; ++i ) {
-    yield tests[i];
-  }
-}();
+     .then(resolveAutoFocusFail, suite.rejectAutoFocus)
+       If the suite.camera.autoFocus() promise is resolved,
+       resolveAutoFocusFail will be called. That function
+       simply verifies the result from the autoFocus() call.
+       It should be false, given the modified behaviour we
+       gave the driver. Since it doesn't return a new promise
+       explicitly, it will generate a new promise which is
+       already resolved (without the focused state parameter,
+       will now be undefined to the next .then handler).
+
+       If the suite.camera.autoFocus() promise is rejected,
+       we want to fail the test case again, just like when
+       suite.getCamera() failed.
+
+    .then(startAutoFocusError)
+       Assuming the first suite.camera.autoFocus() promise
+       was resolved, startAutoFocusError will be called.
+       That function is similar to startAutoFocusFail but
+       now it throws an error in the intercepted auto focus
+       driver call in order to trigger an OnUserError(kAutoFocus)
+       event. It then calls and returns the promise from
+       suite.camera.autoFocus().
 
-window.addEventListener('beforeunload', function() {
-  document.getElementById('viewfinder').mozSrcObject = null;
-  cameraObj.release();
-  cameraObj = null;
+    .then(suite.expectedRejectAutoFocus, rejectAutoFocusError)
+       Now we are expecting the previous suite.camera.autoFocus()
+       promise to be rejected, which would call
+       rejectAutoFocusError. This simply verifies that we
+       got the error we expected.
+
+       If, on the other hand, it somehow succeeded, we
+       let suite.expectedRejectAutoFocus handle it, which
+       similar to the suite.rejectAutoFocus method, will
+       fail and return an empty rejected promise.
+
+    Note that this method itself returns the promise chain.
+    This allows the test suite to 1) capture any unhandled
+    errors in the promise chain and ensure the test case
+    fails as a result, and 2) perform any cleanup operations
+    such as resetting low memory state, releasing the camera,
+    etc, before starting the next test.
+*/
+  return suite.getCamera()
+    .then(startAutoFocusFail, suite.rejectGetCamera)
+    .then(resolveAutoFocusFail, suite.rejectAutoFocus)
+    .then(startAutoFocusError)
+    .then(suite.expectedRejectAutoFocus, rejectAutoFocusError)
 });
 
-CameraTest.begin("hardware", function(test) {
-  function onSuccess(d) {
-    cameraObj = d.camera;
-    document.getElementById('viewfinder').mozSrcObject = cameraObj;
-    CameraTest.next = function() {
-      try {
-        var t = testGenerator.next();
-        test.set(t.key, t.func.bind(undefined, cameraObj));
-      } catch(e) {
-        if (e instanceof StopIteration) {
-          end();
-        } else {
-          throw e;
-        }
+suite.test('take-picture-failures', function() {
+  function startTakePictureProcessError(p) {
+    suite.hw.attach({
+      takePicture: function() {
+        suite.hw.fireTakePictureError();
+      }
+    });
+    return suite.camera.takePicture();
+  }
+
+  function rejectTakePictureProcessError(e) {
+    ok(e.name === 'NS_ERROR_FAILURE', 'takePicture() process should fail: ' + e);
+  }
+
+  function startTakePictureError(p) {
+    suite.hw.attach({
+      takePicture: function() {
+        throw SpecialPowers.Cr.NS_ERROR_FAILURE;
       }
-    };
-    next();
+    });
+    return suite.camera.takePicture();
+  }
+
+  function rejectTakePictureError(e) {
+    ok(e.name === 'NS_ERROR_FAILURE', 'takePicture() should fail: ' + e);
   }
-  function onError(error) {
-    ok(false, "getCamera() failed with: " + error);
-    end();
-  }
-  navigator.mozCameras.getCamera(whichCamera, initialConfig).then(onSuccess, onError);
+
+  return suite.getCamera()
+    .catch(suite.rejectGetCamera)
+    .then(startTakePictureProcessError)
+    .then(suite.expectedRejectTakePicture, rejectTakePictureProcessError)
+    .then(startTakePictureError)
+    .then(suite.expectedRejectTakePicture, rejectTakePictureError)
 });
 
+suite.setup()
+  .then(suite.run);
+
 </script>
 </body>
 
 </html>
--- a/dom/camera/test/test_camera_hardware_init_failure.html
+++ b/dom/camera/test/test_camera_hardware_init_failure.html
@@ -12,139 +12,77 @@ https://bugzilla.mozilla.org/show_bug.cg
 </head>
 <body>
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=940424">Mozilla Bug 940424</a>
   <video id="viewfinder" width="200" height="200" autoplay></video>
   <img src="#" alt="This image is going to load" id="testimage"/>
 
 <script class="testbody" type="text/javascript;version=1.7">
 
-SimpleTest.waitForExplicitFinish();
+var suite = new CameraTestSuite();
+
+suite.test('init-failure', function() {
+  var cameraManager = navigator.mozCameras;
+  var whichCamera = cameraManager.getListOfCameras()[0];
 
-var whichCamera = navigator.mozCameras.getListOfCameras()[0];
-var initialConfig = {
-  mode: 'picture',
-  recorderProfile: 'high',
-  previewSize: {
-    width: 352,
-    height: 288
-  }
-};
+  suite.hw.attach({
+    init: function() {
+      throw SpecialPowers.Cr.NS_ERROR_NOT_INITIALIZED;
+    }
+  });
 
-var tests = [
-  {
-    name: "init-failure",
-    key: "init-failure",
-    func: function testInitFailure(test) {
-      function onSuccess(d) {
-        ok(false, "onSuccess called incorrectly");
-        d.camera.release();
-        test.next();
-      }
-      function onError(error) {
-        ok(true, "onError called correctly on init failure");
-        test.next();
-      }
-      info("Running test: init-failure");
-      navigator.mozCameras.getCamera(whichCamera, initialConfig).then(onSuccess, onError);
-    }
-  },
-  /* This test case (init-success) *must* follow the preceeding test case
-     (init-failure) in order for the desired condition to be verified */
-  {
-    name: "init-success",
-    key: "",
-    func: function(test) {
-      function onSuccess(d) {
-        ok(true, "onSuccess called correctly");
-        d.camera.release().then(test.next);
-      }
-      function onError(error) {
-        ok(false, "onError called incorrectly: " + error);
-        test.next();
-      }
-      info("Running test: init-success");
-      navigator.mozCameras.getCamera(whichCamera, initialConfig).then(onSuccess, onError)
-    }
-  },
-  /* Test for bug 1099390 to make sure events related to the underlying
-     platform failing are generated and handled properly. */
-  {
-    name: "post-init-system-failure",
-    key: "post-init-system-failure",
-    func: function(test) {
-      var gotReleasePromise = false;
-      var gotCloseEvent = false;
+  function rejectGetCamera(e) {
+    ok(e.name === 'NS_ERROR_NOT_AVAILABLE', 'onError called correctly on getCamera init failure: ' + e);
+    // Let the next getCamera attempt succeed
+    suite.initJsHw();
+    return Promise.resolve();
+  }
+
+  function resolveGetCamera(p) {
+    ok(true, 'onSuccess called correctly for second getCamera request');
+  }
+
+  return cameraManager.getCamera(whichCamera)
+    .then(suite.expectedRejectGetCamera, rejectGetCamera)
+    .then(suite.getCamera)
+    .then(resolveGetCamera, suite.rejectGetCamera)
+});
 
-      function gotAll() {
-        var all = gotReleasePromise && gotCloseEvent;
-        if (all) {
-          info("Got all expected notifications");
-        }
-        return all;
+/* Test for bug 1099390 to make sure events related to the underlying
+   platform failing are generated and handled properly. */
+suite.test('post-init-system-failure', function() {
+  function triggerSystemFailure(p) {
+    var sync = new Promise(function(resolve, reject) {
+      function onClosedEvent(e) {
+        suite.camera.removeEventListener('close', onClosedEvent);
+        ok(e.reason === 'SystemFailure', 'reason is: ' + e.reason);
+        resolve();
       }
-
-      function onSuccess(d) {
-        var onClosedEvent = function(e) {
-          d.camera.removeEventListener('close', onClosedEvent);
+      suite.camera.addEventListener('close', onClosedEvent);
+    });
 
-          ok(e.reason === "SystemFailure", "reason is: " + e.reason);
-          ok(!gotCloseEvent, "gotCloseEvent was " + gotCloseEvent);
-          gotCloseEvent = true;
-          if (gotAll()) {
-            test.next();
-          }
-
-          d.camera.release().then(
-            function resolve(e) {
-              ok(true, "release() promise resolved");
-              ok(!gotReleasePromise, "gotReleasePromise was " + gotReleasePromise);
-              gotReleasePromise = true;
-              if (gotAll()) {
-                test.next();
-              }
-            },
-            function reject(e) {
-              ok(false, "release() promise unexpected rejected: " + e);
-            }
-          );
-        };
+    suite.hw.fireSystemError();
+    return sync;
+  }
 
-        d.camera.addEventListener('close', onClosedEvent);
-      }
-
-      function onError(error) {
-        ok(false, "onError called incorrectly: " + error);
-        test.next();
-      }
+  function releaseCamera(p) {
+    var camera = suite.camera;
+    suite.camera = null;
+    return camera.release();
+  }
 
-      info("Running test: post-init-system-failure");
-      navigator.mozCameras.getCamera(whichCamera, initialConfig).then(onSuccess, onError)
-    }
-  },
-];
-
-var testGenerator = function() {
-  for (var i = 0; i < tests.length; ++i) {
-    yield tests[i];
+  function releasedCamera(p) {
+    ok(true, 'camera released after system failure');
   }
-}();
 
-CameraTest.begin("hardware", function(test) {
-  CameraTest.next = function() {
-    try {
-      var t = testGenerator.next();
-      test.set(t.key, t.func.bind(undefined, CameraTest));
-    } catch(e) {
-      if (e instanceof StopIteration) {
-        CameraTest.end();
-      } else {
-        throw e;
-      }
-    }
-  };
-  CameraTest.next();
+  return suite.getCamera()
+    .then(triggerSystemFailure, suite.rejectGetCamera)
+    .then(releaseCamera)
+    .then(releasedCamera, suite.rejectRelease);
 });
 
+suite.setup()
+  .then(suite.run);
+
 </script>
 </body>
 
 </html>
--- a/dom/fmradio/FMRadio.cpp
+++ b/dom/fmradio/FMRadio.cpp
@@ -55,18 +55,16 @@ public:
     MOZ_ASSERT(aType >= FMRadioRequestArgs::T__None &&
                aType <= FMRadioRequestArgs::T__Last,
                "Wrong FMRadioRequestArgs in FMRadioRequest");
 
     mFMRadio = do_GetWeakReference(static_cast<nsIDOMEventTarget*>(aFMRadio));
     mType = aType;
   }
 
-  ~FMRadioRequest() { }
-
   NS_IMETHODIMP
   Run()
   {
     MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
 
     nsCOMPtr<nsIDOMEventTarget> target = do_QueryReferent(mFMRadio);
     if (!target) {
       return NS_OK;
@@ -92,16 +90,19 @@ public:
         break;
       default:
         MOZ_CRASH();
     }
 
     return NS_OK;
   }
 
+protected:
+  ~FMRadioRequest() { }
+
 private:
   FMRadioRequestArgs::Type mType;
   nsWeakPtr mFMRadio;
 };
 
 NS_IMPL_ISUPPORTS_INHERITED0(FMRadioRequest, DOMRequest)
 
 FMRadio::FMRadio()
--- a/dom/fmradio/FMRadioService.cpp
+++ b/dom/fmradio/FMRadioService.cpp
@@ -233,16 +233,19 @@ public:
     MOZ_ASSERT(mPendingRequest == fmRadioService->mPendingRequest);
 
     fmRadioService->TransitionState(ErrorResponse(
       NS_LITERAL_STRING("Unexpected error")), Disabled);
 
     return NS_OK;
   }
 
+protected:
+  ~ReadAirplaneModeSettingTask() {}
+
 private:
   nsRefPtr<FMRadioReplyRunnable> mPendingRequest;
 };
 
 NS_IMPL_ISUPPORTS(ReadAirplaneModeSettingTask, nsISettingsServiceCallback)
 
 class DisableRunnable MOZ_FINAL : public nsRunnable
 {
--- a/dom/fmradio/FMRadioService.h
+++ b/dom/fmradio/FMRadioService.h
@@ -154,17 +154,16 @@ class FMRadioService MOZ_FINAL : public 
 {
   friend class ReadAirplaneModeSettingTask;
   friend class EnableRunnable;
   friend class DisableRunnable;
   friend class NotifyRunnable;
 
 public:
   static FMRadioService* Singleton();
-  virtual ~FMRadioService();
 
   NS_DECL_ISUPPORTS
 
   virtual bool IsEnabled() const MOZ_OVERRIDE;
   virtual bool IsRDSEnabled() const MOZ_OVERRIDE;
   virtual double GetFrequency() const MOZ_OVERRIDE;
   virtual double GetFrequencyUpperBound() const MOZ_OVERRIDE;
   virtual double GetFrequencyLowerBound() const MOZ_OVERRIDE;
@@ -196,16 +195,17 @@ public:
   void Notify(const hal::FMRadioOperationInformation& aInfo) MOZ_OVERRIDE;
   /* FMRadioRDSObserver */
   void Notify(const hal::FMRadioRDSGroup& aRDSGroup) MOZ_OVERRIDE;
 
   NS_DECL_NSIOBSERVER
 
 protected:
   FMRadioService();
+  virtual ~FMRadioService();
 
 private:
   int32_t RoundFrequency(double aFrequencyInMHz);
 
   void NotifyFMRadioEvent(FMRadioEventType aType);
   void DoDisable();
   void TransitionState(const FMRadioResponseType& aResponse, FMRadioState aState);
   void SetState(FMRadioState aState);
--- a/dom/html/reftests/autofocus/reftest.list
+++ b/dom/html/reftests/autofocus/reftest.list
@@ -1,13 +1,13 @@
 default-preferences pref(dom.forms.number,true)
-skip-if(B2G) needs-focus == input-load.html input-ref.html # B2G timed out waiting for reftest-wait to be removed
-skip-if(B2G) needs-focus == input-create.html input-ref.html # B2G timed out waiting for reftest-wait to be removed
-skip-if(B2G) needs-focus == input-number.html input-number-ref.html # B2G timed out waiting for reftest-wait to be removed
-skip-if(B2G) needs-focus == button-load.html button-ref.html # B2G timed out waiting for reftest-wait to be removed
-skip-if(B2G) needs-focus == button-create.html button-ref.html # B2G timed out waiting for reftest-wait to be removed
-skip-if(B2G) needs-focus == textarea-load.html textarea-ref.html # B2G timed out waiting for reftest-wait to be removed
-skip-if(B2G) needs-focus == textarea-create.html textarea-ref.html # B2G timed out waiting for reftest-wait to be removed
-skip-if(B2G) needs-focus == select-load.html select-ref.html # B2G timed out waiting for reftest-wait to be removed
-skip-if(B2G) needs-focus == select-create.html select-ref.html # B2G timed out waiting for reftest-wait to be removed
+skip-if(B2G||Mulet) needs-focus == input-load.html input-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) needs-focus == input-create.html input-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) needs-focus == input-number.html input-number-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) needs-focus == button-load.html button-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) needs-focus == button-create.html button-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) needs-focus == textarea-load.html textarea-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) needs-focus == textarea-create.html textarea-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) needs-focus == select-load.html select-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) needs-focus == select-create.html select-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
 needs-focus == autofocus-after-load.html autofocus-after-load-ref.html
-fails-if(B2G) needs-focus == autofocus-leaves-iframe.html autofocus-leaves-iframe-ref.html # B2G focus difference between test and reference
-skip-if(B2G) needs-focus == autofocus-after-body-focus.html autofocus-after-body-focus-ref.html # bug 773482
+fails-if(B2G||Mulet) needs-focus == autofocus-leaves-iframe.html autofocus-leaves-iframe-ref.html # B2G focus difference between test and reference # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) needs-focus == autofocus-after-body-focus.html autofocus-after-body-focus-ref.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
--- a/dom/media/encoder/fmp4_muxer/MuxerOperation.h
+++ b/dom/media/encoder/fmp4_muxer/MuxerOperation.h
@@ -44,13 +44,14 @@ public:
 
   // Find the box type via its name (name is the box type defined in 14496-12;
   // for example, 'moov' is the name of MovieBox).
   // It can only look child boxes including itself and the box in the boxes
   // list if exists. It can't look parent boxes.
   virtual nsresult Find(const nsACString& aType,
                         nsTArray<nsRefPtr<MuxerOperation>>& aOperations) = 0;
 
+protected:
   virtual ~MuxerOperation() {}
 };
 
 }
 #endif
--- a/dom/media/omx/MediaCodecReader.h
+++ b/dom/media/omx/MediaCodecReader.h
@@ -270,20 +270,22 @@ private:
   };
 
   class SignalObject
   {
   public:
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SignalObject)
 
     SignalObject(const char* aName);
-    ~SignalObject();
     void Wait();
     void Signal();
 
+  protected:
+    ~SignalObject();
+
   private:
     // Forbidden
     SignalObject() = delete;
     SignalObject(const SignalObject &rhs) = delete;
     const SignalObject &operator=(const SignalObject &rhs) = delete;
 
     Monitor mMonitor;
     bool mSignaled;
--- a/dom/media/omx/OMXCodecWrapper.h
+++ b/dom/media/omx/OMXCodecWrapper.h
@@ -273,17 +273,16 @@ private:
   int64_t mSampleDuration;
 };
 
 /**
  * Video encoder.
  */
 class OMXVideoEncoder MOZ_FINAL : public OMXCodecWrapper
 {
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OMXVideoEncoder)
 public:
   // Types of output blob format.
   enum BlobFormat {
     AVC_MP4, // MP4 file config descripter (defined in ISO/IEC 14496-15 5.2.4.1.1)
     AVC_NAL  // NAL (Network Abstract Layer) (defined in ITU-T H.264 7.4.1)
   };
 
   /**
--- a/dom/media/omx/RtspOmxReader.cpp
+++ b/dom/media/omx/RtspOmxReader.cpp
@@ -43,16 +43,17 @@ RtspOmxReader::Seek(int64_t aTime, int64
     mRtspResource->SeekTime(aTime);
     mRtspResource->EnablePlayoutDelay();
   }
 
   // Call |MediaOmxReader::Seek| to notify the OMX decoder we are performing a
   // seek operation. The function will clear the |mVideoQueue| and |mAudioQueue|
   // that store the decoded data and also call the |DecodeToTarget| to pass
   // the seek time to OMX a/v decoders.
+  mEnsureActiveFromSeek = true;
   return MediaOmxReader::Seek(aTime, aEndTime);
 }
 
 void RtspOmxReader::SetIdle() {
   // Call parent class to set OMXCodec idle.
   MediaOmxReader::SetIdle();
 
   // Need to pause RTSP streaming OMXCodec decoding.
@@ -66,19 +67,23 @@ void RtspOmxReader::SetIdle() {
   }
 }
 
 void RtspOmxReader::EnsureActive() {
   // Need to start RTSP streaming OMXCodec decoding.
   if (mRtspResource) {
     nsIStreamingProtocolController* controller =
         mRtspResource->GetMediaStreamController();
-    if (controller) {
+    // We do not have to call Play if the EnsureActive request is from Seek
+    // operation because RTSP connection must already be established before
+    // performing Seek.
+    if (controller && !mEnsureActiveFromSeek) {
       controller->Play();
     }
+    mEnsureActiveFromSeek = false;
     mRtspResource->SetSuspend(false);
   }
 
   // Call parent class to set OMXCodec active.
   MediaOmxReader::EnsureActive();
 }
 
 nsresult RtspOmxReader::ReadMetadata(MediaInfo *aInfo, MetadataTags **aTags)
--- a/dom/media/omx/RtspOmxReader.h
+++ b/dom/media/omx/RtspOmxReader.h
@@ -27,17 +27,19 @@ class RtspOmxReader : public MediaOmxRea
 {
 protected:
   // Provide a Rtsp extractor.
   nsresult InitOmxDecoder() MOZ_FINAL MOZ_OVERRIDE;
   virtual void EnsureActive() MOZ_OVERRIDE;
 
 public:
   RtspOmxReader(AbstractMediaDecoder* aDecoder)
-    : MediaOmxReader(aDecoder) {
+    : MediaOmxReader(aDecoder)
+    , mEnsureActiveFromSeek(false)
+  {
     MOZ_COUNT_CTOR(RtspOmxReader);
     NS_ASSERTION(mDecoder, "RtspOmxReader mDecoder is null.");
     NS_ASSERTION(mDecoder->GetResource(),
                  "RtspOmxReader mDecoder->GetResource() is null.");
     mRtspResource = mDecoder->GetResource()->GetRtspPointer();
     MOZ_ASSERT(mRtspResource);
   }
 
@@ -68,13 +70,15 @@ public:
     MOZ_FINAL MOZ_OVERRIDE;
 
 private:
   // A pointer to RtspMediaResource for calling the Rtsp specific function.
   // The lifetime of mRtspResource is controlled by MediaDecoder. MediaDecoder
   // holds the MediaDecoderStateMachine and RtspMediaResource.
   // And MediaDecoderStateMachine holds this RtspOmxReader.
   RtspMediaResource* mRtspResource;
+
+  bool mEnsureActiveFromSeek;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/nfc/MozNDEFRecord.cpp
+++ b/dom/nfc/MozNDEFRecord.cpp
@@ -2,35 +2,36 @@
 /* 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/. */
 
 /* Copyright © 2013 Deutsche Telekom, Inc. */
 
 #include "MozNDEFRecord.h"
+#include "js/StructuredClone.h"
 #include "mozilla/dom/MozNDEFRecordBinding.h"
 #include "mozilla/HoldDropJSObjects.h"
 #include "nsContentUtils.h"
-
+#include "nsIGlobalObject.h"
+#include "nsString.h"
 
 namespace mozilla {
 namespace dom {
 
-
 NS_IMPL_CYCLE_COLLECTION_CLASS(MozNDEFRecord)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MozNDEFRecord)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
   tmp->DropData();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MozNDEFRecord)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(MozNDEFRecord)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mType)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mId)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPayload)
   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
@@ -64,18 +65,17 @@ MozNDEFRecord::DropData()
   mozilla::DropJSObjects(this);
 }
 
 /**
  * Validate TNF.
  * See section 3.3 THE NDEF Specification Test Requirements,
  * NDEF specification 1.0
  */
-/* static */
-bool
+/* static */ bool
 MozNDEFRecord::ValidateTNF(const MozNDEFRecordOptions& aOptions,
                            ErrorResult& aRv)
 {
   // * The TNF field MUST have a value between 0x00 and 0x06.
   // * The TNF value MUST NOT be 0x07.
   // These two requirements are already handled by WebIDL bindings.
 
   // If the TNF value is 0x00 (Empty), the TYPE, ID, and PAYLOAD fields MUST be
@@ -95,74 +95,293 @@ MozNDEFRecord::ValidateTNF(const MozNDEF
     NS_WARNING("tnf is unknown/unchanged but type  is not null.");
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return false;
   }
 
   return true;
 }
 
-/* static */
-already_AddRefed<MozNDEFRecord>
+/* static */ already_AddRefed<MozNDEFRecord>
 MozNDEFRecord::Constructor(const GlobalObject& aGlobal,
                            const MozNDEFRecordOptions& aOptions,
                            ErrorResult& aRv)
 {
-  nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aGlobal.GetAsSupports());
-  if (!win) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return nullptr;
-  }
-
   if (!ValidateTNF(aOptions, aRv)) {
     return nullptr;
   }
 
-  nsRefPtr<MozNDEFRecord> ndefrecord = new MozNDEFRecord(aGlobal.Context(),
-                                                         win, aOptions);
-  if (!ndefrecord) {
+  nsCOMPtr<nsISupports> parent = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!parent) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  JSContext* context = aGlobal.Context();
+  nsRefPtr<MozNDEFRecord> ndefRecord = new MozNDEFRecord(parent, aOptions.mTnf);
+  ndefRecord->InitType(context, aOptions.mType);
+  ndefRecord->InitId(context, aOptions.mId);
+  ndefRecord->InitPayload(context, aOptions.mPayload);
+
+  return ndefRecord.forget();
+}
+
+/* static */ already_AddRefed<MozNDEFRecord>
+MozNDEFRecord::Constructor(const GlobalObject& aGlobal,
+                           const nsAString& aUri,
+                           ErrorResult& aRv)
+{
+  if (aUri.IsVoid()) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  nsCOMPtr<nsISupports> parent = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!parent) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
-  return ndefrecord.forget();
+
+  nsRefPtr<MozNDEFRecord> ndefRecord = new MozNDEFRecord(parent, TNF::Well_known);
+  ndefRecord->InitType(aGlobal.Context(), RTD::U);
+  ndefRecord->InitPayload(aGlobal.Context(), aUri);
+  return ndefRecord.forget();
+}
+
+MozNDEFRecord::MozNDEFRecord(nsISupports* aParent, TNF aTnf)
+  : mParent(aParent) // For GetParentObject()
+  , mTnf(aTnf)
+  , mSize(3) // 1(flags) + 1(type_length) + 1(payload_length)
+{
+  HoldData();
+}
+
+void
+MozNDEFRecord::GetAsURI(nsAString& aRetVal)
+{
+  aRetVal.SetIsVoid(true);
+  if (mTnf != TNF::Well_known) {
+    return;
+  }
+
+  JS::AutoCheckCannotGC nogc;
+  uint8_t* typeData = JS_GetUint8ArrayData(mType, nogc);
+  const char* uVal = RTDValues::strings[static_cast<uint32_t>(RTD::U)].value;
+  if (typeData[0] != uVal[0]) {
+    return;
+  }
+
+  uint32_t payloadLen;
+  uint8_t* payloadData;
+  js::GetUint8ArrayLengthAndData(mPayload, &payloadLen, &payloadData);
+  uint8_t id = payloadData[0];
+  if (id >= static_cast<uint8_t>(WellKnownURIPrefix::EndGuard_)) {
+    return;
+  }
+
+  using namespace mozilla::dom::WellKnownURIPrefixValues;
+  aRetVal.AssignASCII(strings[id].value);
+  aRetVal.Append(NS_ConvertUTF8toUTF16(
+    nsDependentCSubstring(reinterpret_cast<char*>(&payloadData[1]), payloadLen - 1)));
 }
 
-MozNDEFRecord::MozNDEFRecord(JSContext* aCx, nsPIDOMWindow* aWindow,
-                             const MozNDEFRecordOptions& aOptions)
+bool
+MozNDEFRecord::WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter) const
 {
-  mWindow = aWindow; // For GetParentObject()
+  uint8_t* dummy;
+  uint32_t typeLen = 0, idLen = 0, payloadLen = 0;
+  if (mType) {
+    js::GetUint8ArrayLengthAndData(mType, &typeLen, &dummy);
+  }
+
+  if (mId) {
+    js::GetUint8ArrayLengthAndData(mId, &idLen, &dummy);
+  }
+
+  if (mPayload) {
+    js::GetUint8ArrayLengthAndData(mPayload, &payloadLen, &dummy);
+  }
+
+  return JS_WriteUint32Pair(aWriter, static_cast<uint32_t>(mTnf), typeLen) &&
+         JS_WriteUint32Pair(aWriter, idLen, payloadLen) &&
+         WriteUint8Array(aCx, aWriter, mType, typeLen) &&
+         WriteUint8Array(aCx, aWriter, mId, idLen) &&
+         WriteUint8Array(aCx, aWriter, mPayload, payloadLen);
+}
+
+bool
+MozNDEFRecord::ReadStructuredClone(JSContext* aCx, JSStructuredCloneReader* aReader)
+{
+  uint32_t tnf, typeLen, idLen, payloadLen;
+
+  if (!JS_ReadUint32Pair(aReader, &tnf, &typeLen) ||
+      !JS_ReadUint32Pair(aReader, &idLen, &payloadLen)) {
+    return false;
+  }
+
+  mTnf = static_cast<TNF>(tnf);
 
-  mTnf = aOptions.mTnf;
-  mSize = 3; // 1(flags) + 1(type_length) + 1(payload_length)
+  if (typeLen) {
+    JS::Rooted<JS::Value> value(aCx);
+    if (!JS_ReadTypedArray(aReader, &value)) {
+      return false;
+    }
+    MOZ_ASSERT(value.isObject());
+    InitType(aCx, value.toObject(), typeLen);
+  }
+
+  if (idLen) {
+    JS::Rooted<JS::Value> value(aCx);
+    if (!JS_ReadTypedArray(aReader, &value)) {
+      return false;
+    }
+    MOZ_ASSERT(value.isObject());
+    InitId(aCx, value.toObject(), idLen);
+  }
 
-  if (aOptions.mType.WasPassed()) {
-    const Uint8Array& type = aOptions.mType.Value();
-    type.ComputeLengthAndData();
-    mType = Uint8Array::Create(aCx, this, type.Length(), type.Data());
-    mSize += type.Length();
+  if (payloadLen) {
+    JS::Rooted<JS::Value> value(aCx);
+    if (!JS_ReadTypedArray(aReader, &value)) {
+      return false;
+    }
+    MOZ_ASSERT(value.isObject());
+    InitPayload(aCx, value.toObject(), payloadLen);
+  }
+
+  return true;
+}
+
+void
+MozNDEFRecord::InitType(JSContext* aCx, const Optional<Nullable<Uint8Array>>& aType)
+{
+  if (!aType.WasPassed() || aType.Value().IsNull()) {
+    return;
   }
 
-  if (aOptions.mId.WasPassed()) {
-    const Uint8Array& id = aOptions.mId.Value();
-    id.ComputeLengthAndData();
-    mId = Uint8Array::Create(aCx, this, id.Length(), id.Data());
-    mSize += 1 /* id_length */ + id.Length();
+  const Uint8Array& type = aType.Value().Value();
+  type.ComputeLengthAndData();
+  mType = Uint8Array::Create(aCx, this, type.Length(), type.Data());
+  IncSize(type.Length());
+}
+
+void
+MozNDEFRecord::InitType(JSContext* aCx, RTD rtd)
+{
+  EnumEntry rtdType = RTDValues::strings[static_cast<uint32_t>(rtd)];
+  mType = Uint8Array::Create(aCx, rtdType.length,
+                             reinterpret_cast<const uint8_t*>(rtdType.value));
+  IncSize(rtdType.length);
+}
+
+void
+MozNDEFRecord::InitType(JSContext* aCx, JSObject& aType, uint32_t aLen)
+{
+  mType = &aType;
+  IncSize(aLen);
+}
+
+void
+MozNDEFRecord::InitId(JSContext* aCx, const Optional<Nullable<Uint8Array>>& aId)
+{
+  if (!aId.WasPassed() || aId.Value().IsNull()) {
+    return;
+  }
+
+  const Uint8Array& id = aId.Value().Value();
+  id.ComputeLengthAndData();
+  mId = Uint8Array::Create(aCx, this, id.Length(), id.Data());
+  IncSize(1 /* id_length */ + id.Length());
+}
+
+void
+MozNDEFRecord::InitId(JSContext* aCx, JSObject& aId, uint32_t aLen)
+{
+  mId = &aId;
+  IncSize(1 /* id_length */ + aLen);
+}
+
+void
+MozNDEFRecord::InitPayload(JSContext* aCx, const Optional<Nullable<Uint8Array>>& aPayload)
+{
+  if (!aPayload.WasPassed() || aPayload.Value().IsNull()) {
+    return;
   }
 
-  if (aOptions.mPayload.WasPassed()) {
-    const Uint8Array& payload = aOptions.mPayload.Value();
-    payload.ComputeLengthAndData();
-    mPayload = Uint8Array::Create(aCx, this, payload.Length(), payload.Data());
-    if (payload.Length() > 0xff) {
-      mSize += 3;
-    }
-    mSize += payload.Length();
+  const Uint8Array& payload = aPayload.Value().Value();
+  payload.ComputeLengthAndData();
+  mPayload = Uint8Array::Create(aCx, this, payload.Length(), payload.Data());
+  IncSizeForPayload(payload.Length());
+}
+
+void
+MozNDEFRecord::InitPayload(JSContext* aCx, const nsAString& aUri)
+{
+  using namespace mozilla::dom::WellKnownURIPrefixValues;
+
+  nsCString uri = NS_ConvertUTF16toUTF8(aUri);
+  uint8_t id = GetURIIdentifier(uri);
+  uri = Substring(uri, strings[id].length);
+  mPayload = Uint8Array::Create(aCx, this, uri.Length() + 1);
+
+  JS::AutoCheckCannotGC nogc;
+  uint8_t* data = JS_GetUint8ArrayData(mPayload, nogc);
+  data[0] = id;
+  memcpy(&data[1], reinterpret_cast<const uint8_t*>(uri.Data()), uri.Length());
+  IncSizeForPayload(uri.Length() + 1);
+}
+
+void
+MozNDEFRecord::InitPayload(JSContext* aCx, JSObject& aPayload, uint32_t aLen)
+{
+  mPayload = &aPayload;
+  IncSizeForPayload(aLen);
+}
+
+void
+MozNDEFRecord::IncSize(uint32_t aCount)
+{
+  mSize += aCount;
+}
+
+void
+MozNDEFRecord::IncSizeForPayload(uint32_t aLen)
+{
+  if (aLen > 0xff) {
+    IncSize(3);
   }
 
-  HoldData();
+  IncSize(aLen);
+}
+
+bool
+MozNDEFRecord::WriteUint8Array(JSContext* aCx, JSStructuredCloneWriter* aWriter, JSObject* aObj, uint32_t aLen) const
+{
+  if (!aLen) {
+    return true;
+  }
+
+  JS::Rooted<JSObject*> obj(aCx, aObj);
+  JSAutoCompartment ac(aCx, obj);
+  JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*obj));
+  return JS_WriteTypedArray(aWriter, value);
+}
+
+/* static */ uint32_t
+MozNDEFRecord::GetURIIdentifier(const nsCString& aUri)
+{
+  using namespace mozilla::dom::WellKnownURIPrefixValues;
+
+  // strings[0] is "", so we start from 1.
+  for (uint32_t i = 1; i < static_cast<uint32_t>(WellKnownURIPrefix::EndGuard_); i++) {
+    if (StringBeginsWith(aUri, nsDependentCString(strings[i].value))) {
+      return i;
+    }
+  }
+
+  return 0;
 }
 
 MozNDEFRecord::~MozNDEFRecord()
 {
   DropData();
 }
 
 JSObject*
--- a/dom/nfc/MozNDEFRecord.h
+++ b/dom/nfc/MozNDEFRecord.h
@@ -14,93 +14,117 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 #include "jsapi.h"
 
 #include "mozilla/dom/MozNDEFRecordBinding.h"
 #include "mozilla/dom/TypedArray.h"
 #include "jsfriendapi.h"
 #include "js/GCAPI.h"
-#include "nsPIDOMWindow.h"
+#include "nsISupports.h"
 
+class nsIGlobalObject;
 struct JSContext;
+struct JSStructuredCloneWriter;
 
 namespace mozilla {
 namespace dom {
 
 class MozNDEFRecordOptions;
 
 class MozNDEFRecord MOZ_FINAL : public nsISupports,
                                 public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MozNDEFRecord)
 
 public:
-
-  MozNDEFRecord(JSContext* aCx, nsPIDOMWindow* aWindow,
-                const MozNDEFRecordOptions& aOptions);
+  MozNDEFRecord(nsISupports* aParent, TNF aTnf = TNF::Empty);
 
-  ~MozNDEFRecord();
-
-  nsIDOMWindow* GetParentObject() const
+  nsISupports* GetParentObject() const
   {
-    return mWindow;
+    return mParent;
   }
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   static already_AddRefed<MozNDEFRecord>
   Constructor(const GlobalObject& aGlobal,
               const MozNDEFRecordOptions& aOptions,
               ErrorResult& aRv);
 
+  static already_AddRefed<MozNDEFRecord>
+  Constructor(const GlobalObject& aGlobal,
+              const nsAString& aURI,
+              ErrorResult& aRv);
+
   TNF Tnf() const
   {
     return mTnf;
   }
 
-  void GetType(JSContext* cx, JS::MutableHandle<JSObject*> retval) const
+  void GetType(JSContext* /* unused */, JS::MutableHandle<JSObject*> aRetVal) const
   {
     if (mType) {
       JS::ExposeObjectToActiveJS(mType);
     }
-    retval.set(mType);
+    aRetVal.set(mType);
   }
 
-  void GetId(JSContext* cx, JS::MutableHandle<JSObject*> retval) const
+  void GetId(JSContext* /* unused */, JS::MutableHandle<JSObject*> aRetVal) const
   {
     if (mId) {
       JS::ExposeObjectToActiveJS(mId);
     }
-    retval.set(mId);
+    aRetVal.set(mId);
   }
 
-  void GetPayload(JSContext* cx, JS::MutableHandle<JSObject*> retval) const
+  void GetPayload(JSContext* /* unused */, JS::MutableHandle<JSObject*> aRetVal) const
   {
     if (mPayload) {
       JS::ExposeObjectToActiveJS(mPayload);
     }
-    retval.set(mPayload);
+    aRetVal.set(mPayload);
   }
 
   uint32_t Size() const
   {
     return mSize;
   }
 
+  void GetAsURI(nsAString& aRetVal);
+
+  // Structured clone methods use these to clone MozNDEFRecord.
+  bool WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter) const;
+  bool ReadStructuredClone(JSContext* aCx, JSStructuredCloneReader* aReader);
+
+protected:
+  ~MozNDEFRecord();
+
 private:
   MozNDEFRecord() = delete;
-  nsRefPtr<nsPIDOMWindow> mWindow;
+  nsRefPtr<nsISupports> mParent;
   void HoldData();
   void DropData();
+  void InitType(JSContext* aCx, const Optional<Nullable<Uint8Array>>& aType);
+  void InitType(JSContext* aCx, const RTD rtd);
+  void InitType(JSContext* aCx, JSObject& aType, uint32_t aLen);
+  void InitId(JSContext* aCx, const Optional<Nullable<Uint8Array>>& aId);
+  void InitId(JSContext* aCx, JSObject& aId, uint32_t aLen);
+  void InitPayload(JSContext* aCx, const Optional<Nullable<Uint8Array>>& aPayload);
+  void InitPayload(JSContext* aCx, const nsAString& aUri);
+  void InitPayload(JSContext* aCx, JSObject& aPayload, uint32_t aLen);
+  void IncSize(uint32_t aCount);
+  void IncSizeForPayload(uint32_t aLen);
+  bool WriteUint8Array(JSContext* aCx, JSStructuredCloneWriter* aWriter, JSObject* aObj, uint32_t aLen) const;
 
   static bool
   ValidateTNF(const MozNDEFRecordOptions& aOptions, ErrorResult& aRv);
+  static uint32_t GetURIIdentifier(const nsCString& aUri);
 
   TNF mTnf;
   JS::Heap<JSObject*> mType;
   JS::Heap<JSObject*> mId;
   JS::Heap<JSObject*> mPayload;
   uint32_t mSize;
 };
 
--- a/dom/nfc/NfcContentHelper.js
+++ b/dom/nfc/NfcContentHelper.js
@@ -139,34 +139,16 @@ NfcContentHelper.prototype = {
       }
     }
   },
 
   queryRFState: function queryRFState() {
     return this._rfState;
   },
 
-  encodeNDEFRecords: function encodeNDEFRecords(records) {
-    if (!Array.isArray(records)) {
-      return null;
-    }
-
-    let encodedRecords = [];
-    for (let i = 0; i < records.length; i++) {
-      let record = records[i];
-      encodedRecords.push({
-        tnf: record.tnf,
-        type: record.type || undefined,
-        id: record.id || undefined,
-        payload: record.payload || undefined,
-      });
-    }
-    return encodedRecords;
-  },
-
   setFocusApp: function setFocusApp(tabId, isFocus) {
     cpmm.sendAsyncMessage("NFC:SetFocusApp", {
       tabId: tabId,
       isFocus: isFocus
     });
   },
 
   // NFCTag interface
@@ -179,21 +161,20 @@ NfcContentHelper.prototype = {
       sessionToken: sessionToken
     });
   },
 
   writeNDEF: function writeNDEF(records, sessionToken, callback) {
     let requestId = callback.getCallbackId();
     this._requestMap[requestId] = callback;
 
-    let encodedRecords = this.encodeNDEFRecords(records);
     cpmm.sendAsyncMessage("NFC:WriteNDEF", {
       requestId: requestId,
       sessionToken: sessionToken,
-      records: encodedRecords
+      records: records
     });
   },
 
   makeReadOnly: function makeReadOnly(sessionToken, callback) {
     let requestId = callback.getCallbackId();
     this._requestMap[requestId] = callback;
 
     cpmm.sendAsyncMessage("NFC:MakeReadOnly", {
@@ -278,21 +259,20 @@ NfcContentHelper.prototype = {
     cpmm.sendAsyncMessage("NFC:ChangeRFState",
                           {requestId: requestId,
                            rfState: rfState});
   },
 
   callDefaultFoundHandler: function callDefaultFoundHandler(sessionToken,
                                                             isP2P,
                                                             records) {
-    let encodedRecords = this.encodeNDEFRecords(records);
     cpmm.sendAsyncMessage("NFC:CallDefaultFoundHandler",
                           {sessionToken: sessionToken,
                            isP2P: isP2P,
-                           records: encodedRecords});
+                           records: records});
   },
 
   callDefaultLostHandler: function callDefaultLostHandler(sessionToken, isP2P) {
     cpmm.sendAsyncMessage("NFC:CallDefaultLostHandler",
                           {sessionToken: sessionToken,
                            isP2P: isP2P});
   },
 
--- a/dom/nfc/gonk/Nfc.js
+++ b/dom/nfc/gonk/Nfc.js
@@ -658,19 +658,26 @@ Nfc.prototype = {
      */
     gSystemMessenger.broadcastMessage("nfc-hci-event-transaction", message);
   },
 
   /**
    * Process a message from the gMessageManager.
    */
   receiveMessage: function receiveMessage(message) {
-      if (["NFC:ChangeRFState",
-           "NFC:SendFile",
-           "NFC:QueryInfo"].indexOf(message.name) == -1) {
+    // Return early if we don't need the NFC Service.
+    switch (message.name) {
+      case "NFC:QueryInfo":
+        return {rfState: this.rfState};
+      default:
+        break;
+    }
+
+    if (["NFC:ChangeRFState",
+         "NFC:SendFile"].indexOf(message.name) == -1) {
       // Update the current sessionId before sending to the NFC service.
       message.data.sessionId = SessionHelper.getId(message.data.sessionToken);
     }
 
     switch (message.name) {
       case "NFC:ChangeRFState":
         this.sendToNfcService(NfcRequestType.CHANGE_RF_STATE, message.data);
         break;
@@ -699,18 +706,16 @@ Nfc.prototype = {
 
         // Notify system app to initiate BT send file operation
         let sysMsg = new NfcSendFileSysMsg(message.data.requestId,
                                            message.data.sessionToken,
                                            message.data.blob);
         gSystemMessenger.broadcastMessage("nfc-manager-send-file",
                                           sysMsg);
         break;
-      case "NFC:QueryInfo":
-        return {rfState: this.rfState};
       default:
         debug("UnSupported : Message Name " + message.name);
         return null;
     }
     this.targetsByRequestId[message.data.requestId] = message.target;
 
     return null;
   },
--- a/dom/nfc/gonk/NfcOptions.h
+++ b/dom/nfc/gonk/NfcOptions.h
@@ -53,30 +53,33 @@ struct CommandOptions
     }
 
     mozilla::dom::Sequence<mozilla::dom::MozNDEFRecordOptions> const & currentValue = aOther.mRecords.InternalValue();
     int count = currentValue.Length();
     for (int32_t i = 0; i < count; i++) {
       NDEFRecordStruct record;
       record.mTnf = currentValue[i].mTnf;
 
-      if (currentValue[i].mType.WasPassed()) {
-        const dom::Uint8Array& type = currentValue[i].mType.Value();
+      if (currentValue[i].mType.WasPassed() &&
+          !currentValue[i].mType.Value().IsNull()) {
+        const dom::Uint8Array& type = currentValue[i].mType.Value().Value();
         type.ComputeLengthAndData();
         record.mType.AppendElements(type.Data(), type.Length());
       }
 
-      if (currentValue[i].mId.WasPassed()) {
-        const dom::Uint8Array& id = currentValue[i].mId.Value();
+      if (currentValue[i].mId.WasPassed() &&
+          !currentValue[i].mId.Value().IsNull()) {
+        const dom::Uint8Array& id = currentValue[i].mId.Value().Value();
         id.ComputeLengthAndData();
         record.mId.AppendElements(id.Data(), id.Length());
       }
 
-      if (currentValue[i].mPayload.WasPassed()) {
-        const dom::Uint8Array& payload = currentValue[i].mPayload.Value();
+      if (currentValue[i].mPayload.WasPassed() &&
+          !currentValue[i].mPayload.Value().IsNull()) {
+        const dom::Uint8Array& payload = currentValue[i].mPayload.Value().Value();
         payload.ComputeLengthAndData();
         record.mPayload.AppendElements(payload.Data(), payload.Length());
       }
 
       mRecords.AppendElement(record);
     }
 
 #undef COPY_FIELD
--- a/dom/nfc/gonk/NfcService.cpp
+++ b/dom/nfc/gonk/NfcService.cpp
@@ -156,27 +156,27 @@ public:
         NDEFRecordStruct& recordStruct = mEvent.mRecords[i];
         MozNDEFRecordOptions& record = *event.mRecords.Value().AppendElement();
 
         record.mTnf = recordStruct.mTnf;
         MOZ_ASSERT(record.mTnf < TNF::EndGuard_);
 
         if (recordStruct.mType.Length() > 0) {
           record.mType.Construct();
-          record.mType.Value().Init(Uint8Array::Create(cx, recordStruct.mType.Length(), recordStruct.mType.Elements()));
+          record.mType.Value().SetValue().Init(Uint8Array::Create(cx, recordStruct.mType.Length(), recordStruct.mType.Elements()));
         }
 
         if (recordStruct.mId.Length() > 0) {
           record.mId.Construct();
-          record.mId.Value().Init(Uint8Array::Create(cx, recordStruct.mId.Length(), recordStruct.mId.Elements()));
+          record.mId.Value().SetValue().Init(Uint8Array::Create(cx, recordStruct.mId.Length(), recordStruct.mId.Elements()));
         }
 
         if (recordStruct.mPayload.Length() > 0) {
           record.mPayload.Construct();
-          record.mPayload.Value().Init(Uint8Array::Create(cx, recordStruct.mPayload.Length(), recordStruct.mPayload.Elements()));
+          record.mPayload.Value().SetValue().Init(Uint8Array::Create(cx, recordStruct.mPayload.Length(), recordStruct.mPayload.Elements()));
         }
       }
     }
 
     COPY_OPT_FIELD(mIsP2P, -1)
 
     if (mEvent.mTagType != -1) {
       event.mTagType.Construct();
--- a/dom/system/gonk/AudioChannelManager.h
+++ b/dom/system/gonk/AudioChannelManager.h
@@ -21,17 +21,16 @@ namespace system {
 
 class AudioChannelManager MOZ_FINAL
   : public DOMEventTargetHelper
   , public hal::SwitchObserver
   , public nsIDOMEventListener
 {
 public:
   AudioChannelManager();
-  virtual ~AudioChannelManager();
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIDOMEVENTLISTENER
 
   void Notify(const hal::SwitchEvent& aEvent);
 
   void Init(nsPIDOMWindow* aWindow);
 
@@ -57,16 +56,19 @@ public:
   }
 
   bool SetVolumeControlChannel(const nsAString& aChannel);
 
   bool GetVolumeControlChannel(nsAString& aChannel);
 
   IMPL_EVENT_HANDLER(headphoneschange)
 
+protected:
+  virtual ~AudioChannelManager();
+
 private:
   void NotifyVolumeControlChannelChanged();
 
   hal::SwitchState mState;
   int32_t mVolumeChannel;
 };
 
 } // namespace system
--- a/dom/system/gonk/AudioManager.cpp
+++ b/dom/system/gonk/AudioManager.cpp
@@ -180,16 +180,19 @@ public:
   }
 
   NS_IMETHOD HandleError(const nsAString& aName)
   {
     LOG("AudioChannelVolInitCallback::HandleError: %s\n",
       NS_ConvertUTF16toUTF8(aName).get());
     return NS_OK;
   }
+
+protected:
+  ~AudioChannelVolInitCallback() {}
 };
 
 NS_IMPL_ISUPPORTS(AudioChannelVolInitCallback, nsISettingsServiceCallback)
 } /* namespace gonk */
 } /* namespace dom */
 } /* namespace mozilla */
 
 static void
--- a/dom/system/gonk/AutoMounter.cpp
+++ b/dom/system/gonk/AutoMounter.cpp
@@ -225,22 +225,16 @@ public:
     // so we call CheckVolumeSettings here to cover that case. Otherwise,
     // we'll pick it up when the VolumeManage state changes to VOLUMES_READY.
     CheckVolumeSettings();
 
     DBG("Calling UpdateState from constructor");
     UpdateState();
   }
 
-  ~AutoMounter()
-  {
-    Volume::UnregisterVolumeObserver(&mVolumeEventObserver, "AutoMounter");
-    VolumeManager::UnregisterStateObserver(&mVolumeManagerStateObserver);
-  }
-
   void CheckVolumeSettings()
   {
     if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
       DBG("CheckVolumeSettings: VolumeManager is NOT READY yet");
       return;
     }
     DBG("CheckVolumeSettings: VolumeManager is READY");
 
@@ -384,16 +378,23 @@ public:
     }
     vol->SetMountRequested(false);
     vol->SetUnmountRequested(true);
     DBG("Calling UpdateState due to volume %s unmounting set to %d",
         vol->NameStr(), (int)vol->IsUnmountRequested());
     UpdateState();
   }
 
+protected:
+  ~AutoMounter()
+  {
+    Volume::UnregisterVolumeObserver(&mVolumeEventObserver, "AutoMounter");
+    VolumeManager::UnregisterStateObserver(&mVolumeManagerStateObserver);
+  }
+
 private:
 
   enum STATE
   {
     // IDLE - Nothing is being shared
     STATE_IDLE,
 
     // We've detected that conditions are right to enable mtp. So we've
--- a/dom/system/gonk/AutoMounterSetting.cpp
+++ b/dom/system/gonk/AutoMounterSetting.cpp
@@ -56,16 +56,19 @@ public:
     return NS_OK;
   }
 
   NS_IMETHOD HandleError(const nsAString& aName)
   {
     ERR("SettingsCallback::HandleError: %s\n", NS_LossyConvertUTF16toASCII(aName).get());
     return NS_OK;
   }
+
+protected:
+  ~SettingsServiceCallback() {}
 };
 
 NS_IMPL_ISUPPORTS(SettingsServiceCallback, nsISettingsServiceCallback)
 
 class CheckVolumeSettingsCallback MOZ_FINAL : public nsISettingsServiceCallback
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
@@ -82,16 +85,20 @@ public:
     return NS_OK;
   }
 
   NS_IMETHOD HandleError(const nsAString& aName)
   {
     ERR("CheckVolumeSettingsCallback::HandleError: %s\n", NS_LossyConvertUTF16toASCII(aName).get());
     return NS_OK;
   }
+
+protected:
+  ~CheckVolumeSettingsCallback() {}
+
 private:
   nsCString mVolumeName;
 };
 
 NS_IMPL_ISUPPORTS(CheckVolumeSettingsCallback, nsISettingsServiceCallback)
 
 AutoMounterSetting::AutoMounterSetting()
   : mStatus(AUTOMOUNTER_STATUS_DISABLED)
--- a/dom/system/gonk/AutoMounterSetting.h
+++ b/dom/system/gonk/AutoMounterSetting.h
@@ -14,24 +14,26 @@ class ResultListener;
 
 class AutoMounterSetting : public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   AutoMounterSetting();
-  virtual ~AutoMounterSetting();
 
   static void CheckVolumeSettings(const nsACString& aVolumeName);
 
   int32_t GetStatus() { return mStatus; }
   void SetStatus(int32_t aStatus);
   const char *StatusStr(int32_t aStatus);
 
+protected:
+  virtual ~AutoMounterSetting();
+
 private:
   int32_t mStatus;
 };
 
 }   // namespace system
 }   // namespace mozilla
 
 #endif  // mozilla_system_automountersetting_h__
--- a/dom/system/gonk/MozMtpDatabase.h
+++ b/dom/system/gonk/MozMtpDatabase.h
@@ -25,17 +25,16 @@ class RefCountedMtpServer;
 using namespace android;
 
 class MozMtpDatabase MOZ_FINAL : public MtpDatabase
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozMtpDatabase)
 
   MozMtpDatabase();
-  virtual ~MozMtpDatabase();
 
   // called from SendObjectInfo to reserve a database entry for the incoming file
   virtual MtpObjectHandle beginSendObject(const char* aPath,
                                           MtpObjectFormat aFormat,
                                           MtpObjectHandle aParent,
                                           MtpStorageID aStorageID,
                                           uint64_t aSize,
                                           time_t aModified);
@@ -115,19 +114,22 @@ public:
 
   void AddStorage(MtpStorageID aStorageID, const char* aPath, const char *aName);
   void RemoveStorage(MtpStorageID aStorageID);
 
   void FileWatcherUpdate(RefCountedMtpServer* aMtpServer,
                          DeviceStorageFile* aFile,
                          const nsACString& aEventType);
 
+protected:
+  virtual ~MozMtpDatabase();
+
 private:
 
-  struct DbEntry
+  struct DbEntry MOZ_FINAL
   {
     DbEntry()
       : mHandle(0),
         mStorageID(0),
         mObjectFormat(MTP_FORMAT_DEFINED),
         mParent(0),
         mObjectSize(0),
         mDateCreated(0),
@@ -140,16 +142,19 @@ private:
     nsCString       mObjectName;
     MtpObjectFormat mObjectFormat;  // uint16_t
     MtpObjectHandle mParent;        // uint32_t
     uint64_t        mObjectSize;
     nsCString       mDisplayName;
     nsCString       mPath;
     PRTime          mDateCreated;
     PRTime          mDateModified;
+
+  protected:
+    ~DbEntry() {}
   };
 
   template<class T>
   class ProtectedTArray : private nsTArray<T>
   {
   public:
     typedef T elem_type;
     typedef typename nsTArray<T>::size_type size_type;
@@ -201,23 +206,26 @@ private:
     }
 
   private:
     mozilla::Mutex& mMutex;
   };
   typedef nsTArray<mozilla::RefPtr<DbEntry> > UnprotectedDbArray;
   typedef ProtectedTArray<mozilla::RefPtr<DbEntry> > ProtectedDbArray;
 
-  struct StorageEntry
+  struct StorageEntry MOZ_FINAL
   {
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StorageEntry)
 
     MtpStorageID  mStorageID;
     nsCString     mStoragePath;
     nsCString     mStorageName;
+
+  protected:
+    ~StorageEntry() {}
   };
   typedef ProtectedTArray<mozilla::RefPtr<StorageEntry> > StorageArray;
 
   enum MatchType
   {
     MatchAll,
     MatchHandle,
     MatchParent,
--- a/dom/system/gonk/MozMtpServer.cpp
+++ b/dom/system/gonk/MozMtpServer.cpp
@@ -83,24 +83,16 @@ public:
     mIOThread = new LazyIdleThread(
       DEFAULT_THREAD_TIMEOUT_MS,
       NS_LITERAL_CSTRING("MTP FileWatcherUpdate"));
 
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->AddObserver(this, "file-watcher-update", false);
   }
 
-  ~FileWatcherUpdate()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-    obs->RemoveObserver(this, "file-watcher-update");
-  }
-
   NS_IMETHOD
   Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (strcmp(aTopic, "file-watcher-update")) {
       // We're only interested in file-watcher-update events
       return NS_OK;
@@ -127,16 +119,25 @@ public:
 
     nsRefPtr<FileWatcherUpdateRunnable> r =
       new FileWatcherUpdateRunnable(mozMtpDatabase, mtpServer, file, eventType);
     mIOThread->Dispatch(r, NS_DISPATCH_NORMAL);
 
     return NS_OK;
   }
 
+protected:
+  ~FileWatcherUpdate()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    obs->RemoveObserver(this, "file-watcher-update");
+  }
+
 private:
   nsRefPtr<MozMtpServer> mMozMtpServer;
   nsCOMPtr<nsIThread> mIOThread;
 };
 NS_IMPL_ISUPPORTS(FileWatcherUpdate, nsIObserver)
 static StaticRefPtr<FileWatcherUpdate> sFileWatcherUpdate;
 
 class AllocFileWatcherUpdateRunnable MOZ_FINAL : public nsRunnable
--- a/dom/system/gonk/MozMtpServer.h
+++ b/dom/system/gonk/MozMtpServer.h
@@ -30,29 +30,35 @@ class RefCountedMtpServer : public MtpSe
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCountedMtpServer)
 
   RefCountedMtpServer(int aFd, MtpDatabase* aDatabase, bool aPtp,
                       int aFileGroup, int aFilePerm, int aDirectoryPerm)
     : MtpServer(aFd, aDatabase, aPtp, aFileGroup, aFilePerm, aDirectoryPerm)
   {
   }
+
+protected:
+  virtual ~RefCountedMtpServer() {}
 };
 
 class MozMtpServer
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozMtpServer)
 
   bool Init();
   void Run();
 
   already_AddRefed<RefCountedMtpServer> GetMtpServer();
   already_AddRefed<MozMtpDatabase> GetMozMtpDatabase();
 
+protected:
+  virtual ~MozMtpServer() {}
+
 private:
   nsRefPtr<RefCountedMtpServer> mMtpServer;
   nsRefPtr<MozMtpDatabase> mMozMtpDatabase;
   nsCOMPtr<nsIThread> mServerThread;
   ScopedClose mMtpUsbFd;
 };
 
 END_MTP_NAMESPACE
--- a/dom/system/gonk/TimeZoneSettingObserver.cpp
+++ b/dom/system/gonk/TimeZoneSettingObserver.cpp
@@ -40,18 +40,20 @@ namespace {
 
 class TimeZoneSettingObserver : public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   TimeZoneSettingObserver();
+  static nsresult SetTimeZone(const JS::Value &aValue, JSContext *aContext);
+
+protected:
   virtual ~TimeZoneSettingObserver();
-  static nsresult SetTimeZone(const JS::Value &aValue, JSContext *aContext);
 };
 
 class TimeZoneSettingCb MOZ_FINAL : public nsISettingsServiceCallback
 {
 public:
   NS_DECL_ISUPPORTS
 
   TimeZoneSettingCb() {}
@@ -106,16 +108,19 @@ public:
 
     return NS_OK;
   }
 
   NS_IMETHOD HandleError(const nsAString &aName) {
     ERR("TimeZoneSettingCb::HandleError: %s\n", NS_LossyConvertUTF16toASCII(aName).get());
     return NS_OK;
   }
+
+protected:
+  ~TimeZoneSettingCb() {}
 };
 
 NS_IMPL_ISUPPORTS(TimeZoneSettingCb, nsISettingsServiceCallback)
 
 TimeZoneSettingObserver::TimeZoneSettingObserver()
 {
   // Setup an observer to watch changes to the setting.
   nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
--- a/dom/system/gonk/Volume.h
+++ b/dom/system/gonk/Volume.h
@@ -86,16 +86,19 @@ public:
   void SetUnmountRequested(bool aUnmountRequested);
 
   typedef mozilla::Observer<Volume *>     EventObserver;
 
   // NOTE: that observers must live in the IOThread.
   static void RegisterVolumeObserver(EventObserver* aObserver, const char* aName);
   static void UnregisterVolumeObserver(EventObserver* aObserver, const char* aName);
 
+protected:
+  ~Volume() {}
+
 private:
   friend class AutoMounter;         // Calls StartXxx
   friend class nsVolume;            // Calls UpdateMountLock
   friend class VolumeManager;       // Calls HandleVoldResponse
   friend class VolumeListCallback;  // Calls SetMountPoint, SetState
 
   // The StartXxx functions will queue up a command to the VolumeManager.
   // You can queue up as many commands as you like, and aCallback will
--- a/dom/system/gonk/nsVolumeStat.h
+++ b/dom/system/gonk/nsVolumeStat.h
@@ -15,16 +15,19 @@ namespace system {
 class nsVolumeStat MOZ_FINAL : public nsIVolumeStat
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIVOLUMESTAT
 
   nsVolumeStat(const nsAString& aPath);
 
+protected:
+  ~nsVolumeStat() {}
+
 private:
   struct statfs mStat;
 };
 
 } // system
 } // mozilla
 
 #endif  // mozilla_system_nsvolumestat_h__
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -224,16 +224,17 @@ this.ERROR_CANCELLED = 7;
 this.ERROR_OP_NOT_ALLOWED_DURING_VOICE_CALL = 8;
 this.ERROR_OP_NOT_ALLOWED_BEFORE_REG_TO_NW = 9;
 this.ERROR_SMS_SEND_FAIL_RETRY = 10;
 this.ERROR_SIM_ABSENT = 11;
 this.ERROR_SUBSCRIPTION_NOT_AVAILABLE = 12;
 this.ERROR_MODE_NOT_SUPPORTED = 13;
 this.ERROR_FDN_CHECK_FAILURE = 14;
 this.ERROR_ILLEGAL_SIM_OR_ME = 15;
+this.ERROR_MISSING_RESOURCE = 16;
 this.ERROR_DIAL_MODIFIED_TO_USSD = 17;
 this.ERROR_DIAL_MODIFIED_TO_SS = 18;
 this.ERROR_DIAL_MODIFIED_TO_DIAL = 19;
 this.ERROR_USSD_MODIFIED_TO_DIAL = 20;
 this.ERROR_USSD_MODIFIED_TO_SS = 21;
 this.ERROR_USSD_MODIFIED_TO_USSD = 22;
 this.ERROR_SS_MODIFIED_TO_DIAL = 23;
 this.ERROR_SS_MODIFIED_TO_USSD = 24;
@@ -253,16 +254,17 @@ this.GECKO_ERROR_CANCELLED = "Cancelled"
 this.GECKO_ERROR_ILLEGAL_SIM_OR_ME = "IllegalSIMorME";
 this.GECKO_ERROR_OP_NOT_ALLOWED_DURING_VOICE_CALL = "OpNotAllowedDuringVoiceCall";
 this.GECKO_ERROR_OP_NOT_ALLOWED_BEFORE_REG_TO_NW = "OpNotAllowedBeforeRegToNw";
 this.GECKO_ERROR_SMS_SEND_FAIL_RETRY = "SmsSendFailRetry";
 this.GECKO_ERROR_SIM_ABSENT = "SimAbsent";
 this.GECKO_ERROR_SUBSCRIPTION_NOT_AVAILABLE = "SubscriptionNotAvailable";
 this.GECKO_ERROR_MODE_NOT_SUPPORTED = "ModeNotSupported";
 this.GECKO_ERROR_FDN_CHECK_FAILURE = "FdnCheckFailure";
+this.GECKO_ERROR_MISSING_RESOURCE = "MissingResource";
 this.GECKO_ERROR_DIAL_MODIFIED_TO_USSD = "DialModifiedToUssd";
 this.GECKO_ERROR_DIAL_MODIFIED_TO_SS = "DialModifiedToSs";
 this.GECKO_ERROR_DIAL_MODIFIED_TO_DIAL = "DialModifiedToDial";
 this.GECKO_ERROR_USSD_MODIFIED_TO_DIAL = "UssdModifiedToDial";
 this.GECKO_ERROR_USSD_MODIFIED_TO_SS = "UssdModifiedToSs";
 this.GECKO_ERROR_USSD_MODIFIED_TO_USSD = "UssdModifiedToUssd";
 this.GECKO_ERROR_SS_MODIFIED_TO_DIAL = "SsModifiedToDial";
 this.GECKO_ERROR_SS_MODIFIED_TO_USSD = "SsModifiedToUssd";
@@ -283,16 +285,17 @@ RIL_ERROR_TO_GECKO_ERROR[ERROR_CANCELLED
 RIL_ERROR_TO_GECKO_ERROR[ERROR_OP_NOT_ALLOWED_DURING_VOICE_CALL] = GECKO_ERROR_OP_NOT_ALLOWED_DURING_VOICE_CALL;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_OP_NOT_ALLOWED_BEFORE_REG_TO_NW] = GECKO_ERROR_OP_NOT_ALLOWED_BEFORE_REG_TO_NW;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_SMS_SEND_FAIL_RETRY] = GECKO_ERROR_SMS_SEND_FAIL_RETRY;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_SIM_ABSENT] = GECKO_ERROR_SIM_ABSENT;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_SUBSCRIPTION_NOT_AVAILABLE] = GECKO_ERROR_SUBSCRIPTION_NOT_AVAILABLE;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_MODE_NOT_SUPPORTED] = GECKO_ERROR_MODE_NOT_SUPPORTED;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_FDN_CHECK_FAILURE] = GECKO_ERROR_FDN_CHECK_FAILURE;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_ILLEGAL_SIM_OR_ME] = GECKO_ERROR_ILLEGAL_SIM_OR_ME;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_MISSING_RESOURCE] = GECKO_ERROR_MISSING_RESOURCE;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_DIAL_MODIFIED_TO_USSD] = GECKO_ERROR_DIAL_MODIFIED_TO_USSD;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_DIAL_MODIFIED_TO_SS] = GECKO_ERROR_DIAL_MODIFIED_TO_SS;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_DIAL_MODIFIED_TO_DIAL] = GECKO_ERROR_DIAL_MODIFIED_TO_DIAL;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_USSD_MODIFIED_TO_DIAL] = GECKO_ERROR_USSD_MODIFIED_TO_DIAL;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_USSD_MODIFIED_TO_SS] = GECKO_ERROR_USSD_MODIFIED_TO_SS;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_USSD_MODIFIED_TO_USSD] = GECKO_ERROR_USSD_MODIFIED_TO_USSD;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_SS_MODIFIED_TO_DIAL] = GECKO_ERROR_SS_MODIFIED_TO_DIAL;
 RIL_ERROR_TO_GECKO_ERROR[ERROR_SS_MODIFIED_TO_USSD] = GECKO_ERROR_SS_MODIFIED_TO_USSD;
--- a/dom/webidl/CameraControl.webidl
+++ b/dom/webidl/CameraControl.webidl
@@ -418,17 +418,19 @@ interface CameraControl : MediaStream
        { x:  1000, y:  1000 } is the bottom-right corner
 
    'rightEye' is the coordinates of the detected right eye; null if not
    supported or detected. Same boundary conditions as 'leftEye'.
 
    'mouth' is the coordinates of the detected mouth; null if not supported or
    detected. Same boundary conditions as 'leftEye'.
 */
-[Pref="camera.control.face_detection.enabled", Func="DOMCameraDetectedFace::HasSupport"]
+[Pref="camera.control.face_detection.enabled",
+ Func="DOMCameraDetectedFace::HasSupport",
+ Constructor(optional CameraDetectedFaceInit initDict)]
 interface CameraDetectedFace
 {
   readonly attribute unsigned long id;
 
   readonly attribute unsigned long score;
 
   readonly attribute DOMRect bounds;
 
@@ -437,16 +439,29 @@ interface CameraDetectedFace
 
   readonly attribute boolean hasRightEye;
   readonly attribute DOMPoint? rightEye;
 
   readonly attribute boolean hasMouth;
   readonly attribute DOMPoint? mouth;
 };
 
+dictionary CameraDetectedFaceInit
+{
+  unsigned long id = 0;
+  unsigned long score = 100;
+  CameraRegion bounds;
+  boolean hasLeftEye = false;
+  DOMPointInit leftEye;
+  boolean hasRightEye = false;
+  DOMPointInit rightEye;
+  boolean hasMouth = false;
+  DOMPointInit mouth;
+};
+
 callback CameraFaceDetectionCallback = void (sequence<CameraDetectedFace> faces);
 
 partial interface CameraControl
 {
   /* Starts the face detection. This should be called after the preview is
      started. The camera will periodically call 'onFacesDetected' with a
      sequence of zero or one or more detected faces in the preview frame.
 
--- a/dom/webidl/MozNDEFRecord.webidl
+++ b/dom/webidl/MozNDEFRecord.webidl
@@ -1,26 +1,86 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Copyright © 2013 Deutsche Telekom, Inc. */
 
+/**
+ * Type Name Format.
+ *
+ * @see NFCForum-TS-NDEF 3.2.6 TNF
+ */
 enum TNF {
   "empty",
   "well-known",
   "media-type",
   "absolute-uri",
   "external",
   "unknown",
   "unchanged"
 };
 
-[Constructor(optional MozNDEFRecordOptions options)]
+/**
+ * Prefixes of well-known URI.
+ *
+ * @see NFCForum-TS-RTD_URI Table 3. Abbreviation Table.
+ */
+enum WellKnownURIPrefix {
+  "",
+  "http://www.",
+  "https://www.",
+  "http://",
+  "https://",
+  "tel:",
+  "mailto:",
+  "ftp://anonymous:anonymous@",
+  "ftp://ftp.",
+  "ftps://",
+  "sftp://",
+  "smb://",
+  "nfs://",
+  "ftp://",
+  "dav://",
+  "news:",
+  "telnet://",
+  "imap:",
+  "rtsp://",
+  "urn:",
+  "pop:",
+  "sip:",
+  "sips:",
+  "tftp:",
+  "btspp://",
+  "btl2cap://",
+  "btgoep://",
+  "tcpobex://",
+  "irdaobex://",
+  "file://",
+  "urn:epc:id:",
+  "urn:epc:tag:",
+  "urn:epc:pat:",
+  "urn:epc:raw:",
+  "urn:epc:",
+  "urn:nfc:"
+};
+
+/**
+ * Record Type Description.
+ *
+ * Record Types from well-known NDEF Records.
+ * @see NFCForum-TS-RTD
+ */
+enum RTD {
+  "U", // URI
+};
+
+[Constructor(optional MozNDEFRecordOptions options),
+ Constructor(DOMString uri)]
 interface MozNDEFRecord
 {
   /**
    * Type Name Field - Specifies the NDEF record type in general.
    */
   [Constant]
   readonly attribute TNF tnf;
 
@@ -43,16 +103,22 @@ interface MozNDEFRecord
   [Constant]
   readonly attribute Uint8Array? payload;
 
   /**
    * Get the size of this NDEF Record.
    */
   [Constant]
   readonly attribute unsigned long size;
+
+  /**
+   * Returns this NDEF Record as URI, return null if this record cannot be
+   * decoded as a well-known URI record.
+   */
+  DOMString? getAsURI();
 };
 
 dictionary MozNDEFRecordOptions {
   TNF tnf = "empty";
-  Uint8Array type;
-  Uint8Array id;
-  Uint8Array payload;
+  Uint8Array? type;
+  Uint8Array? id;
+  Uint8Array? payload;
 };
--- a/dom/webidl/MozWifiManager.webidl
+++ b/dom/webidl/MozWifiManager.webidl
@@ -53,16 +53,17 @@ dictionary NetworkProperties {
   DOMString auth_alg;
   DOMString phase1;
   DOMString phase2;
   DOMString eap;
   DOMString pin;
   boolean dontConnect;
   DOMString serverCertificate;
   DOMString subjectMatch;
+  DOMString userCertificate;
 };
 
 [Constructor(optional NetworkProperties properties),
  JSImplementation="@mozilla.org/mozwifinetwork;1",
  Func="Navigator::HasWifiManagerSupport"]
 interface MozWifiNetwork {
   readonly attribute DOMString ssid;
   readonly attribute long mode;
@@ -91,16 +92,17 @@ interface MozWifiNetwork {
            attribute DOMString? auth_alg;
            attribute DOMString? phase1;
            attribute DOMString? phase2;
            attribute DOMString? eap;
            attribute DOMString? pin;
            attribute boolean? dontConnect;
            attribute DOMString? serverCertificate;
            attribute DOMString? subjectMatch;
+           attribute DOMString? userCertificate;
 };
 
 [JSImplementation="@mozilla.org/mozwificonnection;1",
  ChromeOnly]
 interface MozWifiConnection {
   readonly attribute ConnectionStatus status;
   readonly attribute MozWifiNetwork? network;
 };
--- a/dom/wifi/WifiCertService.cpp
+++ b/dom/wifi/WifiCertService.cpp
@@ -466,16 +466,46 @@ WifiCertService::ImportCert(int32_t aId,
 
 NS_IMETHODIMP
 WifiCertService::DeleteCert(int32_t aId, const nsAString& aCertNickname)
 {
   RefPtr<CryptoTask> task = new DeleteCertTask(aId, aCertNickname);
   return task->Dispatch("WifiDeleteCert");
 }
 
+NS_IMETHODIMP
+WifiCertService::HasPrivateKey(const nsAString& aCertNickname, bool *aHasKey)
+{
+  *aHasKey = false;
+
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsCString certNickname;
+  CopyUTF16toUTF8(aCertNickname, certNickname);
+
+  ScopedCERTCertificate cert(
+    CERT_FindCertByNickname(CERT_GetDefaultCertDB(), certNickname.get())
+  );
+  if (!cert) {
+    return NS_OK;
+  }
+
+  ScopedPK11SlotInfo slot(
+    PK11_KeyForCertExists(cert, nullptr, nullptr)
+  );
+  if (slot) {
+    *aHasKey = true;
+  }
+
+  return NS_OK;
+}
+
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(WifiCertService,
                                          WifiCertService::FactoryCreate)
 
 NS_DEFINE_NAMED_CID(NS_WIFICERTSERVICE_CID);
 
 static const mozilla::Module::CIDEntry kWifiCertServiceCIDs[] = {
   { &kNS_WIFICERTSERVICE_CID, false, nullptr, WifiCertServiceConstructor },
   { nullptr }
--- a/dom/wifi/WifiCertService.h
+++ b/dom/wifi/WifiCertService.h
@@ -3,34 +3,37 @@
  * 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 WifiCertService_h
 #define WifiCertService_h
 
 #include "nsIWifiCertService.h"
 #include "nsCOMPtr.h"
+#include "nsNSSShutDown.h"
 #include "nsThread.h"
 #include "mozilla/dom/WifiOptionsBinding.h"
 
 namespace mozilla {
 namespace dom {
 
-class WifiCertService MOZ_FINAL : public nsIWifiCertService
+class WifiCertService MOZ_FINAL : public nsIWifiCertService,
+                                  public nsNSSShutDownObject
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIWIFICERTSERVICE
 
   static already_AddRefed<WifiCertService>
   FactoryCreate();
   void DispatchResult(const mozilla::dom::WifiCertServiceResultOptions& aOptions);
 
 private:
   WifiCertService();
   ~WifiCertService();
+  virtual void virtualDestroyNSSReference() {};
   nsCOMPtr<nsIWifiEventListener> mListener;
 };
 
 } // namespce dom
 } // namespace mozilla
 
 #endif // WifiCertService_h
--- a/dom/wifi/WifiWorker.js
+++ b/dom/wifi/WifiWorker.js
@@ -124,17 +124,17 @@ var WifiManager = (function() {
     };
   }
 
   let {sdkVersion, unloadDriverEnabled, schedScanRecovery,
        driverDelay, p2pSupported, eapSimSupported, ibssSupported, ifname} = getStartupPrefs();
 
   let capabilities = {
     security: ["OPEN", "WEP", "WPA-PSK", "WPA-EAP"],
-    eapMethod: ["PEAP", "TTLS"],
+    eapMethod: ["PEAP", "TTLS", "TLS"],
     eapPhase2: ["MSCHAPV2"],
     certificate: ["SERVER"],
     mode: [MODE_ESS]
   };
   if (eapSimSupported) {
     capabilities.eapMethod.unshift("SIM");
   }
   if (ibssSupported) {
@@ -1206,16 +1206,21 @@ var WifiManager = (function() {
     {name: "auth_alg",      type: "string"},
     {name: "phase1",        type: "string"},
     {name: "phase2",        type: "string"},
     {name: "eap",           type: "string"},
     {name: "pin",           type: "string"},
     {name: "pcsc",          type: "string"},
     {name: "ca_cert",       type: "string"},
     {name: "subject_match", type: "string"},
+    {name: "client_cert",   type: "string"},
+    {name: "private_key",   type: "stirng"},
+    {name: "engine",        type: "integer"},
+    {name: "engine_id",     type: "string"},
+    {name: "key_id",        type: "string"},
     {name: "frequency",     type: "integer"},
     {name: "mode",          type: "integer"}
   ];
   // These fields are only handled in IBSS (aka ad-hoc) mode
   var ibssNetworkConfigurationFields = [
     "frequency", "mode"
   ];
 
@@ -1530,16 +1535,20 @@ var WifiManager = (function() {
     var id = idgen++;
     if (callback) {
       controlCallbacks[id] = callback;
     }
 
     wifiCertService.deleteCert(id, caInfo.certNickname);
   }
 
+  manager.sdkVersion = function() {
+    return sdkVersion;
+  }
+
   return manager;
 })();
 
 // Get unique key for a network, now the key is created by escape(SSID)+Security.
 // So networks of same SSID but different security mode can be identified.
 function getNetworkKey(network)
 {
   var ssid = "",
@@ -1686,17 +1695,18 @@ Network.api = {
   psk: "rw",
   identity: "rw",
   wep: "rw",
   hidden: "rw",
   eap: "rw",
   pin: "rw",
   phase1: "rw",
   phase2: "rw",
-  serverCertificate: "rw"
+  serverCertificate: "rw",
+  userCertificate: "rw"
 };
 
 // Note: We never use ScanResult.prototype, so the fact that it's unrelated to
 // Network.prototype is OK.
 function ScanResult(ssid, bssid, frequency, flags, signal) {
   Network.call(this, ssid, getMode(flags), frequency,
                getKeyManagement(flags), undefined, getCapabilities(flags));
   this.bssid = bssid;
@@ -1931,16 +1941,20 @@ function WifiWorker() {
       pub.hidden = true;
     if ("ca_cert" in net && net.ca_cert &&
         net.ca_cert.indexOf("keystore://WIFI_SERVERCERT_" === 0)) {
       pub.serverCertificate = net.ca_cert.substr(27);
     }
     if(net.subject_match) {
       pub.subjectMatch = net.subject_match;
     }
+    if ("client_cert" in net && net.client_cert &&
+        net.client_cert.indexOf("keystore://WIFI_USERCERT_" === 0)) {
+      pub.userCertificate = net.client_cert.substr(25);
+    }
     return pub;
   };
 
   netFromDOM = function(net, configured) {
     // Takes a network from the DOM and makes it suitable for insertion into
     // self.configuredNetworks (that is calling addNetwork will do the right
     // thing).
     // NB: Modifies net in place: safe since we don't share objects between
@@ -2020,16 +2034,34 @@ function WifiWorker() {
         }
       }
 
       if (hasValidProperty("serverCertificate"))
         net.ca_cert = quote("keystore://WIFI_SERVERCERT_" + net.serverCertificate);
 
       if (hasValidProperty("subjectMatch"))
         net.subject_match = quote(net.subjectMatch);
+
+      if (hasValidProperty("userCertificate")) {
+        let userCertName = "WIFI_USERCERT_" + net.userCertificate;
+        net.client_cert = quote("keystore://" + userCertName);
+
+        let wifiCertService = Cc["@mozilla.org/wifi/certservice;1"].
+                                getService(Ci.nsIWifiCertService);
+        if (wifiCertService.hasPrivateKey(userCertName)) {
+          if (WifiManager.sdkVersion() >= 19) {
+            // Use openssol engine instead of keystore protocol after Kitkat.
+            net.engine = 1;
+            net.engine_id = quote("keystore");
+            net.key_id = quote("WIFI_USERKEY_" + net.userCertificate);
+          } else {
+            net.private_key = quote("keystore://WIFI_USERKEY_" + net.userCertificate);
+          }
+        }
+      }
     }
 
     return net;
   };
 
   WifiManager.onsupplicantconnection = function() {
     debug("Connected to supplicant");
     // Give it a state other than UNINITIALIZED, INITIALIZING or DISABLING
--- a/dom/wifi/nsIWifiCertService.idl
+++ b/dom/wifi/nsIWifiCertService.idl
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIDOMBlob;
 interface nsIWifiEventListener;
 
-[scriptable, uuid(2712a791-f720-484d-8820-c4085629f657)]
+[scriptable, uuid(5d0edcd3-c2f1-4946-aae5-06adcbdf0992)]
 interface nsIWifiCertService : nsISupports
 {
   const unsigned short WIFI_CERT_USAGE_FLAG_SERVER = 0x01;
   const unsigned short WIFI_CERT_USAGE_FLAG_USER   = 0x02;
 
   void start(in nsIWifiEventListener listener);
   void shutdown();
 
@@ -38,9 +38,17 @@ interface nsIWifiCertService : nsISuppor
    *
    * @param id
    *        Request ID.
    * @param certNickname
    *        Certificate nickname to delete.
    */
   void deleteCert(in long id,
                   in DOMString certNickname);
+
+  /**
+   * Check if certificate has private key.
+   *
+   * @param certNickname
+   *        Certificate nickname to check for private key.
+   */
+   boolean hasPrivateKey(in DOMString certNickname);
 };
--- a/editor/reftests/reftest.list
+++ b/editor/reftests/reftest.list
@@ -12,110 +12,110 @@ include xul/reftest.list
 == dynamic-type-4.html dynamic-ref.html
 == passwd-1.html passwd-ref.html
 != passwd-2.html passwd-ref.html
 == passwd-3.html passwd-ref.html
 needs-focus == passwd-4.html passwd-ref.html
 == emptypasswd-1.html emptypasswd-ref.html
 == emptypasswd-2.html emptypasswd-ref.html
 == caret_on_positioned.html caret_on_positioned-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-input-disabled.html spellcheck-input-ref.html
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-input-disabled.html spellcheck-input-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == spellcheck-input-attr-before.html spellcheck-input-nofocus-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-input-attr-before.html spellcheck-input-ref.html
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-input-attr-before.html spellcheck-input-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == spellcheck-input-attr-after.html spellcheck-input-nofocus-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-input-attr-after.html spellcheck-input-ref.html
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-input-attr-after.html spellcheck-input-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == spellcheck-input-attr-inherit.html spellcheck-input-nofocus-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-input-attr-inherit.html spellcheck-input-ref.html
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-input-attr-inherit.html spellcheck-input-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == spellcheck-input-attr-dynamic.html spellcheck-input-nofocus-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-input-attr-dynamic.html spellcheck-input-ref.html
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-input-attr-dynamic.html spellcheck-input-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == spellcheck-input-attr-dynamic-inherit.html spellcheck-input-nofocus-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-input-attr-dynamic-inherit.html spellcheck-input-ref.html
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-input-attr-dynamic-inherit.html spellcheck-input-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == spellcheck-input-property-dynamic.html spellcheck-input-nofocus-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-input-property-dynamic.html spellcheck-input-ref.html
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-input-property-dynamic.html spellcheck-input-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == spellcheck-input-property-dynamic-inherit.html spellcheck-input-nofocus-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-input-property-dynamic-inherit.html spellcheck-input-ref.html
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-input-property-dynamic-inherit.html spellcheck-input-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == spellcheck-input-attr-dynamic-override.html spellcheck-input-nofocus-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-input-attr-dynamic-override.html spellcheck-input-ref.html
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-input-attr-dynamic-override.html spellcheck-input-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == spellcheck-input-attr-dynamic-override-inherit.html spellcheck-input-nofocus-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-input-attr-dynamic-override-inherit.html spellcheck-input-ref.html
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-input-attr-dynamic-override-inherit.html spellcheck-input-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == spellcheck-input-property-dynamic-override.html spellcheck-input-nofocus-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-input-property-dynamic-override.html spellcheck-input-ref.html
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-input-property-dynamic-override.html spellcheck-input-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == spellcheck-input-property-dynamic-override-inherit.html spellcheck-input-nofocus-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-input-property-dynamic-override-inherit.html spellcheck-input-ref.html
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-input-property-dynamic-override-inherit.html spellcheck-input-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == spellcheck-textarea-attr.html spellcheck-textarea-nofocus-ref.html
 #the random-if(Android) tests pass on android native, but fail on android-xul, see bug 728942
-skip-if(B2G) random-if(Android) needs-focus != spellcheck-textarea-attr.html spellcheck-textarea-ref.html
+skip-if(B2G||Mulet) random-if(Android) needs-focus != spellcheck-textarea-attr.html spellcheck-textarea-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 needs-focus == spellcheck-textarea-focused.html spellcheck-textarea-ref.html
 needs-focus == spellcheck-textarea-focused-reframe.html spellcheck-textarea-ref.html
 needs-focus == spellcheck-textarea-focused-notreadonly.html spellcheck-textarea-ref2.html
-skip-if(B2G) random-if(Android) needs-focus != spellcheck-textarea-nofocus.html spellcheck-textarea-ref.html
-skip-if(B2G) random-if(Android) needs-focus != spellcheck-textarea-disabled.html spellcheck-textarea-ref.html
-skip-if(B2G) random-if(Android) needs-focus != spellcheck-textarea-attr-inherit.html spellcheck-textarea-ref.html
-skip-if(B2G) random-if(Android) needs-focus != spellcheck-textarea-attr-dynamic.html spellcheck-textarea-ref.html
-skip-if(B2G) random-if(Android) needs-focus != spellcheck-textarea-attr-dynamic-inherit.html spellcheck-textarea-ref.html
-skip-if(B2G) random-if(Android) needs-focus != spellcheck-textarea-property-dynamic.html spellcheck-textarea-ref.html
-skip-if(B2G) random-if(Android) needs-focus != spellcheck-textarea-property-dynamic-inherit.html spellcheck-textarea-ref.html
-skip-if(B2G) random-if(Android) needs-focus != spellcheck-textarea-attr-dynamic-override.html spellcheck-textarea-ref.html
-skip-if(B2G) random-if(Android) needs-focus != spellcheck-textarea-attr-dynamic-override-inherit.html spellcheck-textarea-ref.html
-skip-if(B2G) random-if(Android) needs-focus != spellcheck-textarea-property-dynamic-override.html spellcheck-textarea-ref.html
-skip-if(B2G) random-if(Android) needs-focus != spellcheck-textarea-property-dynamic-override-inherit.html spellcheck-textarea-ref.html
+skip-if(B2G||Mulet) random-if(Android) needs-focus != spellcheck-textarea-nofocus.html spellcheck-textarea-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) random-if(Android) needs-focus != spellcheck-textarea-disabled.html spellcheck-textarea-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) random-if(Android) needs-focus != spellcheck-textarea-attr-inherit.html spellcheck-textarea-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) random-if(Android) needs-focus != spellcheck-textarea-attr-dynamic.html spellcheck-textarea-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) random-if(Android) needs-focus != spellcheck-textarea-attr-dynamic-inherit.html spellcheck-textarea-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) random-if(Android) needs-focus != spellcheck-textarea-property-dynamic.html spellcheck-textarea-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) random-if(Android) needs-focus != spellcheck-textarea-property-dynamic-inherit.html spellcheck-textarea-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) random-if(Android) needs-focus != spellcheck-textarea-attr-dynamic-override.html spellcheck-textarea-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) random-if(Android) needs-focus != spellcheck-textarea-attr-dynamic-override-inherit.html spellcheck-textarea-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) random-if(Android) needs-focus != spellcheck-textarea-property-dynamic-override.html spellcheck-textarea-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) random-if(Android) needs-focus != spellcheck-textarea-property-dynamic-override-inherit.html spellcheck-textarea-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 needs-focus pref(touchcaret.enabled,false) == caret_on_focus.html caret_on_focus-ref.html
 needs-focus != caret_on_textarea_lastline.html caret_on_textarea_lastline-ref.html
 needs-focus pref(touchcaret.enabled,false) pref(selectioncaret.enabled,false) == input-text-onfocus-reframe.html input-text-onfocus-reframe-ref.html
 needs-focus pref(touchcaret.enabled,false) pref(selectioncaret.enabled,false) == input-text-notheme-onfocus-reframe.html input-text-notheme-onfocus-reframe-ref.html
-skip-if(B2G) needs-focus == caret_after_reframe.html caret_after_reframe-ref.html # B2G timed out waiting for reftest-wait to be removed
+skip-if(B2G||Mulet) needs-focus == caret_after_reframe.html caret_after_reframe-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
 == nobogusnode-1.html nobogusnode-ref.html
 == nobogusnode-2.html nobogusnode-ref.html
 == spellcheck-hyphen-valid.html spellcheck-hyphen-valid-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-hyphen-invalid.html spellcheck-hyphen-invalid-ref.html
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-hyphen-invalid.html spellcheck-hyphen-invalid-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == spellcheck-slash-valid.html spellcheck-slash-valid-ref.html
 == spellcheck-period-valid.html spellcheck-period-valid-ref.html
 == spellcheck-space-valid.html spellcheck-space-valid-ref.html
 == spellcheck-comma-valid.html spellcheck-comma-valid-ref.html
 == spellcheck-hyphen-multiple-valid.html spellcheck-hyphen-multiple-valid-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-hyphen-multiple-invalid.html spellcheck-hyphen-multiple-invalid-ref.html
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-hyphen-multiple-invalid.html spellcheck-hyphen-multiple-invalid-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == spellcheck-dotafterquote-valid.html spellcheck-dotafterquote-valid-ref.html
 == spellcheck-url-valid.html spellcheck-url-valid-ref.html
 == unneeded_scroll.html unneeded_scroll-ref.html
-skip-if(B2G) == caret_on_presshell_reinit.html caret_on_presshell_reinit-ref.html
-skip-if(B2G) == caret_on_presshell_reinit-2.html caret_on_presshell_reinit-ref.html
-skip-if(B2G) == 642800.html 642800-ref.html
+skip-if(B2G||Mulet) == caret_on_presshell_reinit.html caret_on_presshell_reinit-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == caret_on_presshell_reinit-2.html caret_on_presshell_reinit-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 642800.html 642800-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == selection_visibility_after_reframe.html selection_visibility_after_reframe-ref.html
 != selection_visibility_after_reframe-2.html selection_visibility_after_reframe-ref.html
 != selection_visibility_after_reframe-3.html selection_visibility_after_reframe-ref.html
 == 672709.html 672709-ref.html
 == 338427-1.html 338427-1-ref.html
-skip-if(Android||B2G) needs-focus == 674212-spellcheck.html 674212-spellcheck-ref.html
-skip-if(Android||B2G) needs-focus == 338427-2.html 338427-2-ref.html
-skip-if(Android||B2G) needs-focus == 338427-3.html 338427-3-ref.html
-skip-if(Android||B2G) needs-focus == 462758-grabbers-resizers.html 462758-grabbers-resizers-ref.html
+skip-if(Android||B2G||Mulet) needs-focus == 674212-spellcheck.html 674212-spellcheck-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(Android||B2G||Mulet) needs-focus == 338427-2.html 338427-2-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(Android||B2G||Mulet) needs-focus == 338427-3.html 338427-3-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(Android||B2G||Mulet) needs-focus == 462758-grabbers-resizers.html 462758-grabbers-resizers-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == readwrite-non-editable.html readwrite-non-editable-ref.html
 == readwrite-editable.html readwrite-editable-ref.html
 == readonly-non-editable.html readonly-non-editable-ref.html
 == readonly-editable.html readonly-editable-ref.html
 == dynamic-overflow-change.html dynamic-overflow-change-ref.html
 == 694880-1.html 694880-ref.html
 == 694880-2.html 694880-ref.html
 == 694880-3.html 694880-ref.html
 == 388980-1.html 388980-1-ref.html
 needs-focus == spellcheck-superscript-1.html spellcheck-superscript-1-ref.html
-skip-if(B2G) fails-if(Android) needs-focus != spellcheck-superscript-2.html spellcheck-superscript-2-ref.html # bug 783658
+skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-superscript-2.html spellcheck-superscript-2-ref.html # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
 needs-focus pref(selectioncaret.enabled,false) == 824080-1.html 824080-1-ref.html
 needs-focus pref(selectioncaret.enabled,false) == 824080-2.html 824080-2-ref.html
 needs-focus pref(selectioncaret.enabled,false) == 824080-3.html 824080-3-ref.html
 needs-focus != 824080-2.html 824080-3.html
 needs-focus pref(selectioncaret.enabled,false) == 824080-4.html 824080-4-ref.html
 needs-focus pref(selectioncaret.enabled,false) == 824080-5.html 824080-5-ref.html
 needs-focus != 824080-4.html 824080-5.html
 needs-focus == 824080-6.html 824080-6-ref.html
 needs-focus pref(selectioncaret.enabled,false) == 824080-7.html 824080-7-ref.html
 needs-focus != 824080-6.html 824080-7.html
 # Bug 674927: copy spellcheck-textarea tests to contenteditable
 == spellcheck-contenteditable-attr.html spellcheck-contenteditable-nofocus-ref.html
-fails-if(Android||B2G) needs-focus != spellcheck-contenteditable-attr.html spellcheck-contenteditable-ref.html # B2G no spellcheck underline
+fails-if(Android||B2G||Mulet) needs-focus != spellcheck-contenteditable-attr.html spellcheck-contenteditable-ref.html # B2G no spellcheck underline # Initial mulet triage: parity with B2G/B2G Desktop
 needs-focus == spellcheck-contenteditable-focused.html spellcheck-contenteditable-ref.html
 needs-focus == spellcheck-contenteditable-focused-reframe.html spellcheck-contenteditable-ref.html
 == spellcheck-contenteditable-nofocus.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-disabled.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-disabled-partial.html spellcheck-contenteditable-disabled-partial-ref.html
 == spellcheck-contenteditable-attr-inherit.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-attr-dynamic.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-attr-dynamic-inherit.html spellcheck-contenteditable-disabled-ref.html
--- a/editor/reftests/xul/reftest.list
+++ b/editor/reftests/xul/reftest.list
@@ -1,29 +1,29 @@
-fails-if(Android||B2G) skip-if(B2G&&browserIsRemote) == empty-1.xul empty-ref.xul # bug 783658
-skip-if(B2G&&browserIsRemote) != empty-2.xul empty-ref.xul
+fails-if(Android||B2G) skip-if((B2G&&browserIsRemote)||Mulet) == empty-1.xul empty-ref.xul # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != empty-2.xul empty-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
 # There is no way to simulate an autocomplete textbox in windows XP/Vista/7 default theme using CSS.
 # Therefore, the equlity tests below should be marked as failing.
-fails-if(Android||B2G) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012])/.test(http.oscpu)) skip-if(B2G&&browserIsRemote) == autocomplete-1.xul autocomplete-ref.xul # bug 783658
-fails-if(Android||B2G) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012])/.test(http.oscpu)) skip-if(B2G&&browserIsRemote) == emptyautocomplete-1.xul emptyautocomplete-ref.xul # bug 783658
-skip-if(B2G&&browserIsRemote) != emptymultiline-1.xul emptymultiline-ref.xul
-fails-if(Android||B2G) skip-if(B2G&&browserIsRemote) == emptymultiline-2.xul emptymultiline-ref.xul # bug 783658
-fails-if(Android||B2G) skip-if(B2G&&browserIsRemote) == emptytextbox-1.xul emptytextbox-ref.xul # bug 783658
-fails-if(Android||B2G) skip-if(B2G&&browserIsRemote) == emptytextbox-2.xul emptytextbox-ref.xul # bug 783658
-skip-if(B2G&&browserIsRemote) != emptytextbox-3.xul emptytextbox-ref.xul
-skip-if(B2G&&browserIsRemote) != emptytextbox-4.xul emptytextbox-ref.xul
-fails-if(Android||B2G) skip-if(B2G&&browserIsRemote) == emptytextbox-5.xul emptytextbox-ref.xul # bug 783658
+fails-if(Android||B2G) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012])/.test(http.oscpu)) skip-if((B2G&&browserIsRemote)||Mulet) == autocomplete-1.xul autocomplete-ref.xul # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android||B2G) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012])/.test(http.oscpu)) skip-if((B2G&&browserIsRemote)||Mulet) == emptyautocomplete-1.xul emptyautocomplete-ref.xul # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != emptymultiline-1.xul emptymultiline-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android||B2G) skip-if((B2G&&browserIsRemote)||Mulet) == emptymultiline-2.xul emptymultiline-ref.xul # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android||B2G) skip-if((B2G&&browserIsRemote)||Mulet) == emptytextbox-1.xul emptytextbox-ref.xul # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android||B2G) skip-if((B2G&&browserIsRemote)||Mulet) == emptytextbox-2.xul emptytextbox-ref.xul # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != emptytextbox-3.xul emptytextbox-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != emptytextbox-4.xul emptytextbox-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android||B2G) skip-if((B2G&&browserIsRemote)||Mulet) == emptytextbox-5.xul emptytextbox-ref.xul # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
 # There is no way to simulate a number textbox in windows XP/Vista/7 default theme using CSS.
 # Therefore, the equlity tests below should be marked as failing.
-skip-if(B2G&&browserIsRemote) != number-1.xul number-ref.xul
-skip-if(B2G&&browserIsRemote) != number-2.xul number-ref.xul
-fails-if(Android||B2G) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012])/.test(http.oscpu)) skip-if(B2G&&browserIsRemote) == number-3.xul number-ref.xul # bug 783658
-skip-if(B2G&&browserIsRemote) != number-4.xul number-ref.xul
-fails-if(Android||B2G) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012])/.test(http.oscpu)) skip-if(B2G&&browserIsRemote) == number-5.xul number-ref.xul # bug 783658
-fails-if(Android||B2G) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012])/.test(http.oscpu)) skip-if(B2G&&browserIsRemote) == numberwithvalue-1.xul numberwithvalue-ref.xul # bug 783658
-fails-if(Android||B2G) skip-if(B2G&&browserIsRemote) == passwd-1.xul passwd-ref.xul # bug 783658
-fails-if(Android||B2G) skip-if(B2G&&browserIsRemote) == passwd-2.xul passwd-ref.xul # bug 783658
-skip-if(B2G&&browserIsRemote) != passwd-3.xul passwd-ref.xul
-fails-if(Android||B2G) skip-if(B2G&&browserIsRemote) == plain-1.xul plain-ref.xul # bug 783658
-fails-if(Android||B2G) skip-if(B2G&&browserIsRemote) == textbox-1.xul textbox-ref.xul
-skip-if(B2G&&browserIsRemote) != textbox-disabled.xul textbox-ref.xul
+skip-if((B2G&&browserIsRemote)||Mulet) != number-1.xul number-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != number-2.xul number-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android||B2G) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012])/.test(http.oscpu)) skip-if((B2G&&browserIsRemote)||Mulet) == number-3.xul number-ref.xul # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != number-4.xul number-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android||B2G) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012])/.test(http.oscpu)) skip-if((B2G&&browserIsRemote)||Mulet) == number-5.xul number-ref.xul # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android||B2G) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012])/.test(http.oscpu)) skip-if((B2G&&browserIsRemote)||Mulet) == numberwithvalue-1.xul numberwithvalue-ref.xul # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android||B2G) skip-if((B2G&&browserIsRemote)||Mulet) == passwd-1.xul passwd-ref.xul # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android||B2G) skip-if((B2G&&browserIsRemote)||Mulet) == passwd-2.xul passwd-ref.xul # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != passwd-3.xul passwd-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android||B2G) skip-if((B2G&&browserIsRemote)||Mulet) == plain-1.xul plain-ref.xul # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android||B2G) skip-if((B2G&&browserIsRemote)||Mulet) == textbox-1.xul textbox-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != textbox-disabled.xul textbox-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
 # Read-only textboxes look like normal textboxes in windows Vista/7 default theme
-fails-if(windowsDefaultTheme&&/^Windows\x20NT\x206\.[012]/.test(http.oscpu)) skip-if(B2G&&browserIsRemote) != textbox-readonly.xul textbox-ref.xul
+fails-if(windowsDefaultTheme&&/^Windows\x20NT\x206\.[012]/.test(http.oscpu)) skip-if((B2G&&browserIsRemote)||Mulet) != textbox-readonly.xul textbox-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
--- a/gfx/layers/ipc/SharedBufferManagerParent.cpp
+++ b/gfx/layers/ipc/SharedBufferManagerParent.cpp
@@ -94,16 +94,18 @@ public:
       }
     }
     if (SharedBufferManagerParent::sManagerMonitor) {
       SharedBufferManagerParent::sManagerMonitor->Unlock();
     }
     return NS_OK;
   }
 
+protected:
+  ~GrallocReporter() {}
 };
 
 NS_IMPL_ISUPPORTS(GrallocReporter, nsIMemoryReporter)
 #endif
 
 void InitGralloc() {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 #ifdef MOZ_WIDGET_GONK
--- a/hal/gonk/GonkHal.cpp
+++ b/hal/gonk/GonkHal.cpp
@@ -292,16 +292,19 @@ public:
   NS_DECL_NSIOBSERVER
 
   // Run on the main thread, not the vibrator thread.
   void Vibrate(const nsTArray<uint32_t> &pattern);
   void CancelVibrate();
 
   static bool ShuttingDown() { return sShuttingDown; }
 
+protected:
+  ~VibratorRunnable() {}
+
 private:
   Monitor mMonitor;
 
   // The currently-playing pattern.
   nsTArray<uint32_t> mPattern;
 
   // The index we're at in the currently-playing pattern.  If mIndex >=
   // mPattern.Length(), then we're not currently playing anything.
@@ -466,17 +469,17 @@ public:
     }
 
     return NS_OK;
   }
 };
 
 } // anonymous namespace
 
-class BatteryObserver : public IUeventObserver
+class BatteryObserver MOZ_FINAL : public IUeventObserver
 {
 public:
   NS_INLINE_DECL_REFCOUNTING(BatteryObserver)
 
   BatteryObserver()
     :mUpdater(new BatteryUpdater())
   {
   }
@@ -490,16 +493,19 @@ public:
     const char *devpath = event->findParam("DEVPATH");
     if (strcmp(subsystem, "power_supply") == 0 &&
         strstr(devpath, "battery")) {
       // aEvent will be valid only in this method.
       NS_DispatchToMainThread(mUpdater);
     }
   }
 
+protected:
+  ~BatteryObserver() {}
+
 private:
   nsRefPtr<BatteryUpdater> mUpdater;
 };
 
 // sBatteryObserver is owned by the IO thread. Only the IO thread may
 // create or destroy it.
 static StaticRefPtr<BatteryObserver> sBatteryObserver;
 
@@ -1167,16 +1173,20 @@ public:
       mRegexes(nullptr)
   {
     // Enable timestamps in kernel's printk
     WriteToFile("/sys/module/printk/parameters/time", "Y");
   }
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
+
+protected:
+  ~OomVictimLogger() {}
+
 private:
   double mLastLineChecked;
   ScopedFreePtr<regex_t> mRegexes;
 };
 NS_IMPL_ISUPPORTS(OomVictimLogger, nsIObserver);
 
 NS_IMETHODIMP
 OomVictimLogger::Observe(
--- a/hal/gonk/GonkSwitch.cpp
+++ b/hal/gonk/GonkSwitch.cpp
@@ -56,20 +56,16 @@ public:
   SwitchHandler(const char* aDevPath, SwitchDevice aDevice)
     : mDevPath(aDevPath),
       mState(SWITCH_STATE_UNKNOWN),
       mDevice(aDevice)
   {
     GetInitialState();
   }
 
-  virtual ~SwitchHandler()
-  {
-  }
-
   bool CheckEvent(NetlinkEvent* aEvent)
   {
     if (strcmp(GetSubsystem(), aEvent->getSubsystem()) ||
         strcmp(mDevPath, aEvent->findParam("DEVPATH"))) {
         return false;
     }
     
     mState = ConvertState(GetStateString(aEvent));
@@ -81,16 +77,20 @@ public:
     return mState;
   }
 
   SwitchDevice GetType()
   {
     return mDevice;
   }
 protected:
+  virtual ~SwitchHandler()
+  {
+  }
+
   virtual const char* GetSubsystem()
   {
     return "switch";
   }
 
   virtual const char* GetStateString(NetlinkEvent* aEvent)
   {
     return aEvent->findParam("SWITCH_STATE");
--- a/js/xpconnect/src/ExportHelpers.cpp
+++ b/js/xpconnect/src/ExportHelpers.cpp
@@ -9,16 +9,19 @@
 #include "AccessCheck.h"
 #include "jsfriendapi.h"
 #include "jswrapper.h"
 #include "js/StructuredClone.h"
 #include "js/Proxy.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/File.h"
+#ifdef MOZ_NFC
+#include "mozilla/dom/MozNDEFRecord.h"
+#endif
 #include "nsGlobalWindow.h"
 #include "nsJSUtils.h"
 #include "nsIDOMFileList.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace JS;
 using namespace js;
@@ -33,17 +36,18 @@ IsReflector(JSObject *obj)
         return false;
     return IS_WN_REFLECTOR(obj) || dom::IsDOMObject(obj);
 }
 
 enum StackScopedCloneTags {
     SCTAG_BASE = JS_SCTAG_USER_MIN,
     SCTAG_REFLECTOR,
     SCTAG_BLOB,
-    SCTAG_FUNCTION
+    SCTAG_FUNCTION,
+    SCTAG_DOM_NFC_NDEF
 };
 
 class MOZ_STACK_CLASS StackScopedCloneData {
 public:
     StackScopedCloneData(JSContext *aCx, StackScopedCloneOptions *aOptions)
         : mOptions(aOptions)
         , mReflectors(aCx)
         , mFunctions(aCx)
@@ -116,16 +120,36 @@ StackScopedCloneRead(JSContext *cx, JSSt
             if (!GetOrCreateDOMReflector(cx, blob, &val)) {
                 return nullptr;
             }
         }
 
         return val.toObjectOrNull();
     }
 
+    if (tag == SCTAG_DOM_NFC_NDEF) {
+#ifdef MOZ_NFC
+      nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(cx));
+      if (!global) {
+        return nullptr;
+      }
+
+      // Prevent the return value from being trashed by a GC during ~nsRefPtr.
+      JS::Rooted<JSObject*> result(cx);
+      {
+        nsRefPtr<MozNDEFRecord> ndefRecord = new MozNDEFRecord(global);
+        result = ndefRecord->ReadStructuredClone(cx, reader) ?
+                 ndefRecord->WrapObject(cx) : nullptr;
+      }
+      return result;
+#else
+      return nullptr;
+#endif
+    }
+
     MOZ_ASSERT_UNREACHABLE("Encountered garbage in the clone stream!");
     return nullptr;
 }
 
 // The HTML5 structured cloning algorithm includes a few DOM objects, notably
 // FileList. That wouldn't in itself be a reason to support them here,
 // but we've historically supported them for Cu.cloneInto (where we didn't support
 // other reflectors), so we need to continue to do so in the wrapReflectors == false
@@ -187,16 +211,26 @@ StackScopedCloneWrite(JSContext *cx, JSS
             cloneData->mFunctions.append(obj);
             return JS_WriteUint32Pair(writer, SCTAG_FUNCTION, cloneData->mFunctions.length() - 1);
         } else {
             JS_ReportError(cx, "Permission denied to pass a Function via structured clone");
             return false;
         }
     }
 
+#ifdef MOZ_NFC
+    {
+      MozNDEFRecord* ndefRecord;
+      if (NS_SUCCEEDED(UNWRAP_OBJECT(MozNDEFRecord, obj, ndefRecord))) {
+        return JS_WriteUint32Pair(writer, SCTAG_DOM_NFC_NDEF, 0) &&
+               ndefRecord->WriteStructuredClone(cx, writer);
+      }
+    }
+#endif
+
     JS_ReportError(cx, "Encountered unsupported value type writing stack-scoped structured clone");
     return false;
 }
 
 static const JSStructuredCloneCallbacks gStackScopedCloneCallbacks = {
     StackScopedCloneRead,
     StackScopedCloneWrite,
     nullptr,
--- a/layout/reftests/abs-pos/reftest.list
+++ b/layout/reftests/abs-pos/reftest.list
@@ -4,25 +4,25 @@ fuzzy-if(/^Windows\x20NT\x206\.1/.test(h
 == fieldset-1.html fieldset-1-ref.html
 == table-1.html table-1-ref.html
 == table-2.html table-2-ref.html
 == table-3.html table-3-ref.html
 == table-caption-1.html table-internal-1-ref.html
 == table-caption-2.html table-internal-2-ref.html
 == table-caption-3.html table-internal-3-ref.html
 == table-caption-4.html table-internal-8-ref.html
-skip-if(B2G&&browserIsRemote) != table-caption-5.html table-print-1-ref.html # TODO: change to == when bug 967870 is fixed
+skip-if((B2G&&browserIsRemote)||Mulet) != table-caption-5.html table-print-1-ref.html # TODO: change to == when bug 967870 is fixed # Initial mulet triage: parity with B2G/B2G Desktop
 == table-cell-1.html table-internal-1-ref.html
 == table-cell-2.html table-internal-2-ref.html
 == table-cell-3.html table-internal-3-ref.html
 == table-cell-4.html table-internal-4-ref.html
 == table-cell-5.html table-internal-5-ref.html
 == table-cell-6.html table-internal-6-ref.html
 == table-cell-7.html table-internal-7-ref.html
-skip-if(B2G&&browserIsRemote) != table-cell-8.html table-print-1-ref.html # TODO: change to == when bug 967870 is fixed
+skip-if((B2G&&browserIsRemote)||Mulet) != table-cell-8.html table-print-1-ref.html # TODO: change to == when bug 967870 is fixed # Initial mulet triage: parity with B2G/B2G Desktop
 == table-row-1.html table-internal-1-ref.html
 == table-row-2.html table-internal-2-ref.html
 == table-row-3.html table-internal-3-ref.html
 == table-row-4.html table-internal-4-ref.html
 == table-row-5.html table-internal-5-ref.html
 == table-row-6.html table-internal-6-ref.html
 == table-row-7.html table-internal-7-ref.html
 == table-row-group-1.html table-internal-1-ref.html
@@ -44,17 +44,17 @@ skip-if(B2G&&browserIsRemote) != table-c
 == table-footer-group-3.html table-internal-3-ref.html
 == table-footer-group-4.html table-internal-4-ref.html
 == table-footer-group-5.html table-internal-5-ref.html
 == table-footer-group-6.html table-internal-6-ref.html
 == table-footer-group-7.html table-internal-7-ref.html
 == continuation-positioned-inline-1.html continuation-positioned-inline-ref.html
 == continuation-positioned-inline-2.html continuation-positioned-inline-ref.html
 == scrollframe-1.html scrollframe-1-ref.html
-skip-if(B2G) fuzzy-if(Android,9,185) == scrollframe-2.html scrollframe-2-ref.html #bug 756530
+skip-if(B2G||Mulet) fuzzy-if(Android,9,185) == scrollframe-2.html scrollframe-2-ref.html #bug 756530 # Initial mulet triage: parity with B2G/B2G Desktop
 fuzzy-if(gtk2Widget,1,8) == select-1.html select-1-ref.html
 fuzzy-if(gtk2Widget,1,8) == select-1-dynamic.html select-1-ref.html
 == select-2.html select-2-ref.html
 fuzzy-if(gtk2Widget,1,19) fuzzy-if(Android||B2G,17,726) == select-3.html select-3-ref.html
 == multi-column-1.html multi-column-1-ref.html
 == button-1.html button-1-ref.html
 == button-2.html button-2-ref.html
 == relative-row-animation-1.html relative-row-animation-1-ref.html
--- a/layout/reftests/backgrounds/reftest.list
+++ b/layout/reftests/backgrounds/reftest.list
@@ -1,43 +1,43 @@
 include gradient/reftest.list
 include vector/reftest.list
 
-skip-if(B2G) == layers-stacking-order.xhtml layers-stacking-order-ref.xhtml
-skip-if(B2G) == layers-layer-count-cascade-1.xhtml layers-layer-count-1-ref.xhtml
+skip-if(B2G||Mulet) == layers-stacking-order.xhtml layers-stacking-order-ref.xhtml # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == layers-layer-count-cascade-1.xhtml layers-layer-count-1-ref.xhtml # Initial mulet triage: parity with B2G/B2G Desktop
 == layers-layer-count-inheritance-1.xhtml layers-layer-count-1-ref.xhtml
 == layers-layer-count-cascade-2.xhtml layers-layer-count-2-ref.xhtml
 == layers-layer-count-inheritance-2.xhtml layers-layer-count-2-ref.xhtml
 fuzzy-if(Android,9,600000) == viewport-translucent-color-1.html viewport-translucent-color-ref.html
 == viewport-translucent-color-2.html viewport-translucent-color-ref.html
 == viewport-translucent-color-3.html viewport-translucent-color-ref.html
 != viewport-translucent-color-ref.html about:blank
 == iframe-translucent-color-1.html iframe-translucent-color-ref.html
 == translucent-color-1.html translucent-color-ref.html
 == translucent-color-2.html translucent-color-ref.html
 == translucent-color-3.html translucent-color-ref.html
 != translucent-color-ref.html about:blank
-skip-if(B2G) == root-element-display-none-1.html root-element-display-none-ref.html
+skip-if(B2G||Mulet) == root-element-display-none-1.html root-element-display-none-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == continuous-inline-1a.html continuous-inline-1ab-ref.html
 == continuous-inline-1b.html continuous-inline-1ab-ref.html
 == continuous-inline-1c.html continuous-inline-1cd-ref.html
 == continuous-inline-1d.html continuous-inline-1cd-ref.html
 == continuous-inline-2a.html continuous-inline-2-ref.html
 == continuous-inline-2b.html continuous-inline-2-ref.html
 == continuous-inline-3.html continuous-inline-3-ref.html
-skip-if(B2G) == continuous-inline-4a.html continuous-inline-4-ref.html
-skip-if(B2G) == continuous-inline-4b.html continuous-inline-4-ref.html
+skip-if(B2G||Mulet) == continuous-inline-4a.html continuous-inline-4-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == continuous-inline-4b.html continuous-inline-4-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == continuous-inline-5a.html continuous-inline-5-ref.html
 == continuous-inline-5b.html continuous-inline-5-ref.html
 == background-redraw-237766.html background-redraw-237766-ref.html
 
 == background-clip-1.html background-clip-1-ref.html
 == background-clip-2.html background-clip-2-ref.html
 
-skip-if(B2G) == background-position-1a.html background-position-1-ref.html
+skip-if(B2G||Mulet) == background-position-1a.html background-position-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == background-position-1b.html background-position-1-ref.html
 == background-position-1c.html background-position-1-ref.html
 == background-position-2a.html background-position-2-ref.html
 == background-position-2b.html background-position-2-ref.html
 == background-position-3a.html background-position-3-ref.html
 == background-position-3b.html background-position-3-ref.html
 == background-position-4a.html background-position-4-ref.html
 == background-position-4b.html background-position-4-ref.html
@@ -56,17 +56,17 @@ skip-if(B2G) == background-position-1a.h
 == background-size-length.html background-size-auto-length-ref.html
 == background-size-auto-percent.html background-size-auto-length-ref.html
 == background-size-percent-auto.html background-size-auto-length-ref.html
 == background-size-percent.html background-size-auto-length-ref.html
 == background-size-length-percent.html background-size-length-percent-ref.html
 == background-size-percent-length.html background-size-length-percent-ref.html
 == background-size-percent-percent.html background-size-percent-percent-ref.html
 == background-size-length-length.html background-size-length-length-ref.html
-skip-if(B2G) == background-size-percent-percent-stretch.html background-size-percent-percent-stretch-ref.html # bug 773482
+skip-if(B2G||Mulet) == background-size-percent-percent-stretch.html background-size-percent-percent-stretch-ref.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 
 == background-size-body-percent-percent.html background-size-body-percent-percent-ref.html
 == background-size-body-percent-percent-no-repeat.html background-size-body-percent-percent-ref.html
 == background-size-body-percent-percent-not-fixed.html background-size-body-percent-percent-ref.html
 == background-size-body-cover.html background-size-body-cover-ref.html
 == background-size-body-cover-no-repeat.html background-size-body-cover-ref.html
 fails-if(smallScreen&&Android) != background-size-body-cover-not-fixed.html background-size-body-cover-ref.html
 != background-size-body-cover-not-fixed.html background-size-body-single-not-fixed.html
@@ -82,17 +82,17 @@ fails-if(smallScreen&&Android) != backgr
 == background-size-zoom-no-repeat.html background-size-zoom-no-repeat-ref.html
 
 == background-size-contain-clip-padding.html background-size-contain-clip-padding-ref.html
 == background-size-contain-clip-border.html background-size-contain-clip-border-ref.html
 == background-size-contain-position-fifty-fifty.html background-size-contain-position-fifty-fifty-ref.html
 == background-size-contain-clip-padding-origin-border.html background-size-contain-clip-padding-origin-border-ref.html
 == background-size-contain-clip-padding-origin-border-padding.html background-size-contain-clip-padding-origin-border-padding-ref.html
 
-skip-if(B2G) == background-layers-1a.html background-layers-1-ref.html # bug 773482
+skip-if(B2G||Mulet) == background-layers-1a.html background-layers-1-ref.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 fuzzy-if(OSX,1,324) == background-layers-1b.html background-layers-1-ref.html
 
 # box-decoration-break's effect on backgrounds is touchy and hard to test due to stretching
 # artifacts and the difficulty of covering exact lines, so just make sure
 # background-size results in a different rendering when present.
 pref(layout.css.box-decoration-break.enabled,true) != background-size-cover-slice.html background-size-slice.html
 pref(layout.css.box-decoration-break.enabled,true) != background-size-cover-clone.html background-size-clone.html
 
@@ -116,22 +116,22 @@ random-if(OSX==1010) == background-size-
 # the image aren't the issue, because they're being obscured to avoid sampling
 # algorithm dependencies (at least assuming the sampling algorithm in use
 # doesn't sample too far astray from the boundaries).
 fails == background-size-zoom-repeat.html background-size-zoom-repeat-ref.html
 
 # -moz-default-background-color and -moz-default-color (bug 591341)
 == background-moz-default-background-color.html background-moz-default-background-color-ref.html
 
-random-if(B2G) == fixed-bg-with-transform-outside-viewport-1.html fixed-bg-with-transform-outside-viewport-ref.html
+random-if(B2G||Mulet) == fixed-bg-with-transform-outside-viewport-1.html fixed-bg-with-transform-outside-viewport-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 
 HTTP == root-background-1.html root-background-ref.html
 HTTP != root-background-1.html about:blank
 
-random-if(B2G) == really-big-background.html really-big-background-ref.html
+random-if(B2G||Mulet) == really-big-background.html really-big-background-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == body-background.html body-background-ref.html
 == table-background.html table-background-ref.html
 == table-background-print.html table-background-print-ref.html
 != div-background.html div-background-ref.html
 
 == background-repeat-1-ref.html background-repeat-1.html
 
 == multi-background-clip-content-border.html multi-background-clip-content-border-ref.html
--- a/layout/reftests/backgrounds/vector/reftest.list
+++ b/layout/reftests/backgrounds/vector/reftest.list
@@ -7,74 +7,74 @@ include empty/reftest.list
 #
 # for ORIENTATION in tall wide; do for SIZE in 32px-auto auto-32px auto contain cover; do for VIMAGE in *.svg; do cat template.html | sed -e "s/SIZE/$(echo $SIZE | sed -e 's/-/ /')/g" | sed -e "s/VIMAGE/$VIMAGE/g" | sed -e "s/TALLWIDE/$ORIENTATION/g" | sed -e "s/ORIENTATION/$(if [ "$ORIENTATION" = "tall" ]; then echo 'width: 256px; height: 768px'; else echo 'width: 768px; height: 256px'; fi)/g" > $ORIENTATION--$SIZE--$(echo $VIMAGE | sed -e 's/.svg//').html; echo "== $ORIENTATION--$SIZE--$(echo $VIMAGE | sed -e 's/.svg//').html ###" >> reftest.list; done; echo >> reftest.list; done; done
 #
 ################################################################################
 
 == tall--32px-auto--nonpercent-width-nonpercent-height.html ref-tall-lime32x64-aqua32x64.html
 == tall--32px-auto--nonpercent-width-nonpercent-height-viewbox.html ref-tall-lime32x64-aqua32x64.html
 == tall--32px-auto--nonpercent-width-omitted-height.html ref-tall-lime32x384-aqua32x384.html
-skip-if(B2G) == tall--32px-auto--nonpercent-width-omitted-height-viewbox.html ref-tall-lime32x256-aqua32x256.html # bug 773482
-skip-if(B2G) == tall--32px-auto--nonpercent-width-percent-height.html ref-tall-lime32x384-aqua32x384.html # bug 773482
+skip-if(B2G||Mulet) == tall--32px-auto--nonpercent-width-omitted-height-viewbox.html ref-tall-lime32x256-aqua32x256.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == tall--32px-auto--nonpercent-width-percent-height.html ref-tall-lime32x384-aqua32x384.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == tall--32px-auto--nonpercent-width-percent-height-viewbox.html ref-tall-lime32x256-aqua32x256.html
-skip-if(B2G) == tall--32px-auto--omitted-width-nonpercent-height.html ref-tall-lime32x16-aqua32x16.html # bug 773482
+skip-if(B2G||Mulet) == tall--32px-auto--omitted-width-nonpercent-height.html ref-tall-lime32x16-aqua32x16.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == tall--32px-auto--omitted-width-nonpercent-height-viewbox.html ref-tall-lime32x256-aqua32x256.html
 == tall--32px-auto--omitted-width-omitted-height.html ref-tall-lime32x384-aqua32x384.html
 == tall--32px-auto--omitted-width-omitted-height-viewbox.html ref-tall-lime32x256-aqua32x256.html
-skip-if(B2G) == tall--32px-auto--omitted-width-percent-height.html ref-tall-lime32x384-aqua32x384.html # bug 773482
-skip-if(B2G) == tall--32px-auto--omitted-width-percent-height-viewbox.html ref-tall-lime32x256-aqua32x256.html # bug 773482
+skip-if(B2G||Mulet) == tall--32px-auto--omitted-width-percent-height.html ref-tall-lime32x384-aqua32x384.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == tall--32px-auto--omitted-width-percent-height-viewbox.html ref-tall-lime32x256-aqua32x256.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == tall--32px-auto--percent-width-nonpercent-height.html ref-tall-lime32x16-aqua32x16.html
 == tall--32px-auto--percent-width-nonpercent-height-viewbox.html ref-tall-lime32x256-aqua32x256.html
 == tall--32px-auto--percent-width-omitted-height.html ref-tall-lime32x384-aqua32x384.html
-skip-if(B2G) == tall--32px-auto--percent-width-omitted-height-viewbox.html ref-tall-lime32x256-aqua32x256.html # bug 773482
+skip-if(B2G||Mulet) == tall--32px-auto--percent-width-omitted-height-viewbox.html ref-tall-lime32x256-aqua32x256.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == tall--32px-auto--percent-width-percent-height.html ref-tall-lime32x384-aqua32x384.html
-skip-if(B2G) == tall--32px-auto--percent-width-percent-height-viewbox.html ref-tall-lime32x256-aqua32x256.html # bug 773482
+skip-if(B2G||Mulet) == tall--32px-auto--percent-width-percent-height-viewbox.html ref-tall-lime32x256-aqua32x256.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 
 == tall--auto-32px--nonpercent-width-nonpercent-height.html ref-tall-lime8x16-aqua8x16.html
 == tall--auto-32px--nonpercent-width-nonpercent-height-viewbox.html ref-tall-lime8x16-aqua8x16.html
-skip-if(B2G) == tall--auto-32px--nonpercent-width-omitted-height.html ref-tall-lime8x16-aqua8x16.html # bug 773482
+skip-if(B2G||Mulet) == tall--auto-32px--nonpercent-width-omitted-height.html ref-tall-lime8x16-aqua8x16.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == tall--auto-32px--nonpercent-width-omitted-height-viewbox.html ref-tall-lime2x16-aqua2x16.html
-skip-if(B2G) == tall--auto-32px--nonpercent-width-percent-height.html ref-tall-lime8x16-aqua8x16.html
+skip-if(B2G||Mulet) == tall--auto-32px--nonpercent-width-percent-height.html ref-tall-lime8x16-aqua8x16.html # Initial mulet triage: parity with B2G/B2G Desktop
 == tall--auto-32px--nonpercent-width-percent-height-viewbox.html ref-tall-lime2x16-aqua2x16.html
 == tall--auto-32px--omitted-width-nonpercent-height.html ref-tall-lime256x16-aqua256x16.html
 == tall--auto-32px--omitted-width-nonpercent-height-viewbox.html ref-tall-lime2x16-aqua2x16.html
 == tall--auto-32px--omitted-width-omitted-height.html ref-tall-lime256x16-aqua256x16.html
 == tall--auto-32px--omitted-width-omitted-height-viewbox.html ref-tall-lime2x16-aqua2x16.html
 == tall--auto-32px--omitted-width-percent-height.html ref-tall-lime256x16-aqua256x16.html
 == tall--auto-32px--omitted-width-percent-height-viewbox.html ref-tall-lime2x16-aqua2x16.html
 == tall--auto-32px--percent-width-nonpercent-height.html ref-tall-lime256x16-aqua256x16.html
 == tall--auto-32px--percent-width-nonpercent-height-viewbox.html ref-tall-lime2x16-aqua2x16.html
 == tall--auto-32px--percent-width-omitted-height.html ref-tall-lime256x16-aqua256x16.html
 == tall--auto-32px--percent-width-omitted-height-viewbox.html ref-tall-lime2x16-aqua2x16.html
 == tall--auto-32px--percent-width-percent-height.html ref-tall-lime256x16-aqua256x16.html
 == tall--auto-32px--percent-width-percent-height-viewbox.html ref-tall-lime2x16-aqua2x16.html
 
-skip-if(B2G) == tall--auto--nonpercent-width-nonpercent-height.html ref-tall-lime8x16-aqua8x16.html # bug 773482
+skip-if(B2G||Mulet) == tall--auto--nonpercent-width-nonpercent-height.html ref-tall-lime8x16-aqua8x16.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == tall--auto--nonpercent-width-nonpercent-height-viewbox.html ref-tall-lime8x16-aqua8x16.html
 == tall--auto--nonpercent-width-omitted-height.html ref-tall-lime8x384-aqua8x384.html
 == tall--auto--nonpercent-width-omitted-height-viewbox.html ref-tall-lime8x64-aqua8x64.html
 == tall--auto--nonpercent-width-percent-height.html ref-tall-lime8x384-aqua8x384.html
 == tall--auto--nonpercent-width-percent-height-viewbox.html ref-tall-lime8x64-aqua8x64.html
 == tall--auto--omitted-width-nonpercent-height.html ref-tall-lime256x16-aqua256x16.html
 == tall--auto--omitted-width-nonpercent-height-viewbox.html ref-tall-lime2x16-aqua2x16.html
 == tall--auto--omitted-width-omitted-height.html ref-tall-lime256x384-aqua256x384.html
-skip-if(B2G) == tall--auto--omitted-width-omitted-height-viewbox.html ref-tall-lime48x384-aqua48x384.html # bug 773482
+skip-if(B2G||Mulet) == tall--auto--omitted-width-omitted-height-viewbox.html ref-tall-lime48x384-aqua48x384.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == tall--auto--omitted-width-percent-height.html ref-tall-lime256x384-aqua256x384.html
 == tall--auto--omitted-width-percent-height-viewbox.html ref-tall-lime48x384-aqua48x384.html
 == tall--auto--percent-width-nonpercent-height.html ref-tall-lime256x16-aqua256x16.html
 == tall--auto--percent-width-nonpercent-height-viewbox.html ref-tall-lime2x16-aqua2x16.html
 == tall--auto--percent-width-omitted-height.html ref-tall-lime256x384-aqua256x384.html
 == tall--auto--percent-width-omitted-height-viewbox.html ref-tall-lime48x384-aqua48x384.html
 == tall--auto--percent-width-percent-height.html ref-tall-lime256x384-aqua256x384.html
 == tall--auto--percent-width-percent-height-viewbox.html ref-tall-lime48x384-aqua48x384.html
 
 == tall--contain--nonpercent-width-nonpercent-height.html ref-tall-lime192x384-aqua192x384.html
 == tall--contain--nonpercent-width-nonpercent-height-viewbox.html ref-tall-lime192x384-aqua192x384.html
-skip-if(B2G) == tall--contain--nonpercent-width-omitted-height.html ref-tall-lime256x384-aqua256x384.html # bug 773482
-skip-if(B2G) == tall--contain--nonpercent-width-omitted-height-viewbox.html ref-tall-lime48x384-aqua48x384.html # bug 773482
+skip-if(B2G||Mulet) == tall--contain--nonpercent-width-omitted-height.html ref-tall-lime256x384-aqua256x384.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == tall--contain--nonpercent-width-omitted-height-viewbox.html ref-tall-lime48x384-aqua48x384.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == tall--contain--nonpercent-width-percent-height.html ref-tall-lime256x384-aqua256x384.html
 == tall--contain--nonpercent-width-percent-height-viewbox.html ref-tall-lime48x384-aqua48x384.html
 == tall--contain--omitted-width-nonpercent-height.html ref-tall-lime256x384-aqua256x384.html
 == tall--contain--omitted-width-nonpercent-height-viewbox.html ref-tall-lime48x384-aqua48x384.html
 == tall--contain--omitted-width-omitted-height.html ref-tall-lime256x384-aqua256x384.html
 == tall--contain--omitted-width-omitted-height-viewbox.html ref-tall-lime48x384-aqua48x384.html
 == tall--contain--omitted-width-percent-height.html ref-tall-lime256x384-aqua256x384.html
 == tall--contain--omitted-width-percent-height-viewbox.html ref-tall-lime48x384-aqua48x384.html
@@ -102,74 +102,74 @@ skip-if(B2G) == tall--contain--nonpercen
 == tall--cover--percent-width-nonpercent-height-viewbox.html ref-tall-lime256x768.html
 == tall--cover--percent-width-omitted-height.html ref-tall-lime256x384-aqua256x384.html
 == tall--cover--percent-width-omitted-height-viewbox.html ref-tall-lime256x768.html
 == tall--cover--percent-width-percent-height.html ref-tall-lime256x384-aqua256x384.html
 == tall--cover--percent-width-percent-height-viewbox.html ref-tall-lime256x768.html
 
 == wide--12px-auto--nonpercent-width-nonpercent-height.html ref-wide-lime12x24-aqua12x24.html
 == wide--12px-auto--nonpercent-width-nonpercent-height-viewbox.html ref-wide-lime12x24-aqua12x24.html
-skip-if(B2G) == wide--12px-auto--nonpercent-width-omitted-height.html ref-wide-lime12x128-aqua12x128.html
-skip-if(B2G) == wide--12px-auto--nonpercent-width-omitted-height-viewbox.html ref-wide-lime12x96-aqua12x96.html # bug 773482
+skip-if(B2G||Mulet) == wide--12px-auto--nonpercent-width-omitted-height.html ref-wide-lime12x128-aqua12x128.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == wide--12px-auto--nonpercent-width-omitted-height-viewbox.html ref-wide-lime12x96-aqua12x96.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == wide--12px-auto--nonpercent-width-percent-height.html ref-wide-lime12x128-aqua12x128.html
 == wide--12px-auto--nonpercent-width-percent-height-viewbox.html ref-wide-lime12x96-aqua12x96.html
-skip-if(B2G) == wide--12px-auto--omitted-width-nonpercent-height.html ref-wide-lime12x16-aqua12x16.html # bug 773482
-skip-if(B2G) == wide--12px-auto--omitted-width-nonpercent-height-viewbox.html ref-wide-lime12x96-aqua12x96.html # bug 773482
+skip-if(B2G||Mulet) == wide--12px-auto--omitted-width-nonpercent-height.html ref-wide-lime12x16-aqua12x16.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == wide--12px-auto--omitted-width-nonpercent-height-viewbox.html ref-wide-lime12x96-aqua12x96.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == wide--12px-auto--omitted-width-omitted-height.html ref-wide-lime12x128-aqua12x128.html
 == wide--12px-auto--omitted-width-omitted-height-viewbox.html ref-wide-lime12x96-aqua12x96.html
 == wide--12px-auto--omitted-width-percent-height.html ref-wide-lime12x128-aqua12x128.html
 == wide--12px-auto--omitted-width-percent-height-viewbox.html ref-wide-lime12x96-aqua12x96.html
 == wide--12px-auto--percent-width-nonpercent-height.html ref-wide-lime12x16-aqua12x16.html
 == wide--12px-auto--percent-width-nonpercent-height-viewbox.html ref-wide-lime12x96-aqua12x96.html
 == wide--12px-auto--percent-width-omitted-height.html ref-wide-lime12x128-aqua12x128.html
-skip-if(B2G) == wide--12px-auto--percent-width-omitted-height-viewbox.html ref-wide-lime12x96-aqua12x96.html # bug 773482
-skip-if(B2G) == wide--12px-auto--percent-width-percent-height.html ref-wide-lime12x128-aqua12x128.html # bug 773482
+skip-if(B2G||Mulet) == wide--12px-auto--percent-width-omitted-height-viewbox.html ref-wide-lime12x96-aqua12x96.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == wide--12px-auto--percent-width-percent-height.html ref-wide-lime12x128-aqua12x128.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == wide--12px-auto--percent-width-percent-height-viewbox.html ref-wide-lime12x96-aqua12x96.html
 
 == wide--auto-32px--nonpercent-width-nonpercent-height.html ref-wide-lime8x16-aqua8x16.html
 == wide--auto-32px--nonpercent-width-nonpercent-height-viewbox.html ref-wide-lime8x16-aqua8x16.html
 == wide--auto-32px--nonpercent-width-omitted-height.html ref-wide-lime8x16-aqua8x16.html
 == wide--auto-32px--nonpercent-width-omitted-height-viewbox.html ref-wide-lime2x16-aqua2x16.html
-skip-if(B2G) == wide--auto-32px--nonpercent-width-percent-height.html ref-wide-lime8x16-aqua8x16.html # bug 773482
+skip-if(B2G||Mulet) == wide--auto-32px--nonpercent-width-percent-height.html ref-wide-lime8x16-aqua8x16.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == wide--auto-32px--nonpercent-width-percent-height-viewbox.html ref-wide-lime2x16-aqua2x16.html
 == wide--auto-32px--omitted-width-nonpercent-height.html ref-wide-lime768x16-aqua768x16.html
 == wide--auto-32px--omitted-width-nonpercent-height-viewbox.html ref-wide-lime2x16-aqua2x16.html
 == wide--auto-32px--omitted-width-omitted-height.html ref-wide-lime768x16-aqua768x16.html
 == wide--auto-32px--omitted-width-omitted-height-viewbox.html ref-wide-lime2x16-aqua2x16.html
 == wide--auto-32px--omitted-width-percent-height.html ref-wide-lime768x16-aqua768x16.html
 == wide--auto-32px--omitted-width-percent-height-viewbox.html ref-wide-lime2x16-aqua2x16.html
 == wide--auto-32px--percent-width-nonpercent-height.html ref-wide-lime768x16-aqua768x16.html
 == wide--auto-32px--percent-width-nonpercent-height-viewbox.html ref-wide-lime2x16-aqua2x16.html
 == wide--auto-32px--percent-width-omitted-height.html ref-wide-lime768x16-aqua768x16.html
 == wide--auto-32px--percent-width-omitted-height-viewbox.html ref-wide-lime2x16-aqua2x16.html
 == wide--auto-32px--percent-width-percent-height.html ref-wide-lime768x16-aqua768x16.html
 == wide--auto-32px--percent-width-percent-height-viewbox.html ref-wide-lime2x16-aqua2x16.html
 
-skip-if(B2G) == wide--auto--nonpercent-width-nonpercent-height.html ref-wide-lime8x16-aqua8x16.html
-skip-if(B2G) == wide--auto--nonpercent-width-nonpercent-height-viewbox.html ref-wide-lime8x16-aqua8x16.html # bug 773482
+skip-if(B2G||Mulet) == wide--auto--nonpercent-width-nonpercent-height.html ref-wide-lime8x16-aqua8x16.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == wide--auto--nonpercent-width-nonpercent-height-viewbox.html ref-wide-lime8x16-aqua8x16.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == wide--auto--nonpercent-width-omitted-height.html ref-wide-lime8x128-aqua8x128.html
 == wide--auto--nonpercent-width-omitted-height-viewbox.html ref-wide-lime8x64-aqua8x64.html
 == wide--auto--nonpercent-width-percent-height.html ref-wide-lime8x128-aqua8x128.html
-skip-if(B2G) == wide--auto--nonpercent-width-percent-height-viewbox.html ref-wide-lime8x64-aqua8x64.html # bug 773482
+skip-if(B2G||Mulet) == wide--auto--nonpercent-width-percent-height-viewbox.html ref-wide-lime8x64-aqua8x64.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == wide--auto--omitted-width-nonpercent-height.html ref-wide-lime768x16-aqua768x16.html
 == wide--auto--omitted-width-nonpercent-height-viewbox.html ref-wide-lime2x16-aqua2x16.html
 == wide--auto--omitted-width-omitted-height.html ref-wide-lime768x128-aqua768x128.html
-skip-if(B2G) == wide--auto--omitted-width-omitted-height-viewbox.html ref-wide-lime16x128-aqua16x128.html # bug 773482
+skip-if(B2G||Mulet) == wide--auto--omitted-width-omitted-height-viewbox.html ref-wide-lime16x128-aqua16x128.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == wide--auto--omitted-width-percent-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--auto--omitted-width-percent-height-viewbox.html ref-wide-lime16x128-aqua16x128.html
 == wide--auto--percent-width-nonpercent-height.html ref-wide-lime768x16-aqua768x16.html
 == wide--auto--percent-width-nonpercent-height-viewbox.html ref-wide-lime2x16-aqua2x16.html
 == wide--auto--percent-width-omitted-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--auto--percent-width-omitted-height-viewbox.html ref-wide-lime16x128-aqua16x128.html
 == wide--auto--percent-width-percent-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--auto--percent-width-percent-height-viewbox.html ref-wide-lime16x128-aqua16x128.html
 
 == wide--contain--nonpercent-width-nonpercent-height.html ref-wide-lime64x128-aqua64x128.html
 == wide--contain--nonpercent-width-nonpercent-height-viewbox.html ref-wide-lime64x128-aqua64x128.html
-skip-if(B2G) == wide--contain--nonpercent-width-omitted-height.html ref-wide-lime768x128-aqua768x128.html # bug 773482
+skip-if(B2G||Mulet) == wide--contain--nonpercent-width-omitted-height.html ref-wide-lime768x128-aqua768x128.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == wide--contain--nonpercent-width-omitted-height-viewbox.html ref-wide-lime16x128-aqua16x128.html
 == wide--contain--nonpercent-width-percent-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--contain--nonpercent-width-percent-height-viewbox.html ref-wide-lime16x128-aqua16x128.html
 == wide--contain--omitted-width-nonpercent-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--contain--omitted-width-nonpercent-height-viewbox.html ref-wide-lime16x128-aqua16x128.html
 == wide--contain--omitted-width-omitted-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--contain--omitted-width-omitted-height-viewbox.html ref-wide-lime16x128-aqua16x128.html
 == wide--contain--omitted-width-percent-height.html ref-wide-lime768x128-aqua768x128.html
@@ -177,28 +177,28 @@ skip-if(B2G) == wide--contain--nonpercen
 == wide--contain--percent-width-nonpercent-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--contain--percent-width-nonpercent-height-viewbox.html ref-wide-lime16x128-aqua16x128.html
 == wide--contain--percent-width-omitted-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--contain--percent-width-omitted-height-viewbox.html ref-wide-lime16x128-aqua16x128.html
 == wide--contain--percent-width-percent-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--contain--percent-width-percent-height-viewbox.html ref-wide-lime16x128-aqua16x128.html
 
 == wide--cover--nonpercent-width-nonpercent-height.html ref-wide-lime768x256.html
-skip-if(B2G) == wide--cover--nonpercent-width-nonpercent-height-viewbox.html ref-wide-lime768x256.html # bug 773482
+skip-if(B2G||Mulet) == wide--cover--nonpercent-width-nonpercent-height-viewbox.html ref-wide-lime768x256.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == wide--cover--nonpercent-width-omitted-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--cover--nonpercent-width-omitted-height-viewbox.html ref-wide-lime768x256.html
 == wide--cover--nonpercent-width-percent-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--cover--nonpercent-width-percent-height-viewbox.html ref-wide-lime768x256.html
 == wide--cover--omitted-width-nonpercent-height.html ref-wide-lime768x128-aqua768x128.html
-skip-if(B2G) == wide--cover--omitted-width-nonpercent-height-viewbox.html ref-wide-lime768x256.html # bug 773482
+skip-if(B2G||Mulet) == wide--cover--omitted-width-nonpercent-height-viewbox.html ref-wide-lime768x256.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == wide--cover--omitted-width-omitted-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--cover--omitted-width-omitted-height-viewbox.html ref-wide-lime768x256.html
 == wide--cover--omitted-width-percent-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--cover--omitted-width-percent-height-viewbox.html ref-wide-lime768x256.html
 == wide--cover--percent-width-nonpercent-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--cover--percent-width-nonpercent-height-viewbox.html ref-wide-lime768x256.html
 == wide--cover--percent-width-omitted-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--cover--percent-width-omitted-height-viewbox.html ref-wide-lime768x256.html
 == wide--cover--percent-width-percent-height.html ref-wide-lime768x128-aqua768x128.html
 == wide--cover--percent-width-percent-height-viewbox.html ref-wide-lime768x256.html
 
 == diagonal-percentage-vector-background.html diagonal-percentage-vector-background-ref.html
-== no-aspect-ratio-wide.html no-aspect-ratio-normal.html
\ No newline at end of file
+== no-aspect-ratio-wide.html no-aspect-ratio-normal.html
--- a/layout/reftests/bidi/reftest.list
+++ b/layout/reftests/bidi/reftest.list
@@ -81,18 +81,18 @@ random-if(winWidget) == 267459-1.html 26
 random-if(winWidget) == 305643-1.html 305643-1-ref.html # depends on windows version, see bug 590101
 == 332655-1.html 332655-1-ref.html
 == 332655-2.html 332655-2-ref.html
 == 381279-1.html 381279-1-ref.html
 == 386339.html 386339-ref.html
 == 409375.html 409375-ref.html
 == 413542-1.html 413542-1-ref.html
 == 413542-2.html 413542-2-ref.html
-random-if(B2G&&browserIsRemote) == 413928-1.html 413928-1-ref.html
-random-if(B2G&&browserIsRemote) == 413928-2.html 413928-2-ref.html
+random-if((B2G&&browserIsRemote)||Mulet) == 413928-1.html 413928-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+random-if((B2G&&browserIsRemote)||Mulet) == 413928-2.html 413928-2-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == 425338-1a.html 425338-1-ref.html
 == 425338-1b.html 425338-1-ref.html
 == 489517-1.html 489517-1-ref.html
 == 489887-1.html 489887-1-ref.html
 == 492231-1.html 492231-1-ref.html
 == 496006-1.html 496006-1-ref.html
 == 503269-1.html 503269-1-ref.html
 == 503957-1.html 503957-1-ref.html
@@ -113,36 +113,36 @@ random-if(B2G&&browserIsRemote) == 41392
 == 613149-1b.html 613149-1-ref.html
 fuzzy-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)&&!layersGPUAccelerated&&!azureSkia,36,2) == 613149-2a.html 613149-2-ref.html
 fuzzy-if(Android,24,1) fuzzy-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)&&!layersGPUAccelerated&&!azureSkia,36,2) == 613149-2b.html 613149-2-ref.html
 == 613157-1.html 613157-1-ref.html
 fuzzy-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)&&!layersGPUAccelerated&&!azureSkia,255,6) == 613157-2.html 613157-2-ref.html
 == 662288-1.html 662288-1-ref.html
 == 670226-1.html 670226-1-ref.html
 == 676245-1.html 676245-1-ref.html
-skip-if(B2G) == 698291-1.html 698291-1-ref.html
+skip-if(B2G||Mulet) == 698291-1.html 698291-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == 698706-1.html 698706-1-ref.html
 == 704837-1.html 704837-1-ref.html
 == 712600-1.html 712600-1-ref.html
 == 712600-2.html 712600-2-ref.html
 == 712600-2-dyn.html 712600-2-ref.html
 == 712600-3.html 712600-3-ref.html
 == 718236-1.html 718236-1-ref.html
 == 718236-2.html 718236-2-ref.html
 == 718236-3.html 718236-3-ref.html
-skip-if(B2G) == 726420-1.html 726420-1-ref.html
+skip-if(B2G||Mulet) == 726420-1.html 726420-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == 726460-1.html 726460-1-ref.html
 == 729047-1.html 729047-1-ref.html
 == 730562-1.html 730562-1-ref.html
 == 746987-1.html 746987-1-ref.html
 == 746987-2.html 746987-2-ref.html
 == 746987-3.html 746987-3-ref.html
 == 746987-4.html 746987-4-ref.html
 == 779003-1.html 779003-1-ref.html
 == 779003-1-dynamic.html 779003-1-ref.html
 == 847242-1.html 847242-1-ref.html
-skip-if(B2G&&browserIsRemote) == 869833-1.xul 869833-1-ref.xul
+skip-if((B2G&&browserIsRemote)||Mulet) == 869833-1.xul 869833-1-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
 == 922530-1.html 922530-1-ref.html
 == 922550-1.html 922550-1-ref.html
 == 1067268-1.html 1067268-1-ref.html
 == 1069941-inline-bidi-border-1.html 1069941-inline-bidi-border-1-ref.html
 == 1069941-inline-bidi-margin-1.html 1069941-inline-bidi-margin-1-ref.html
 
--- a/layout/reftests/border-radius/reftest.list
+++ b/layout/reftests/border-radius/reftest.list
@@ -62,28 +62,28 @@ fuzzy-if(true,1,33) fuzzy-if(cocoaWidget
 
 # Inheritance
 == inherit-1.html inherit-1-ref.html # border-radius shouldn't inherit
 
 # Table elements
 == table-collapse-1.html table-collapse-1-ref.html # border-radius is ignored on internal table elements
 # when border-collapse: collapse
 
-fuzzy-if(azureQuartz,1,3) skip-if(B2G) == invalidate-1a.html invalidate-1-ref.html
-fuzzy-if(azureQuartz,1,3) skip-if(B2G) == invalidate-1b.html invalidate-1-ref.html
+fuzzy-if(azureQuartz,1,3) skip-if(B2G||Mulet) == invalidate-1a.html invalidate-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+fuzzy-if(azureQuartz,1,3) skip-if(B2G||Mulet) == invalidate-1b.html invalidate-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 
 # test that border-radius is reduced for scrollbars
-skip-if(B2G) fails-if(Android) == scrollbar-clamping-1.html scrollbar-clamping-1-ref.html
-skip-if(B2G) fails-if(Android) == scrollbar-clamping-2.html scrollbar-clamping-2-ref.html
+skip-if(B2G||Mulet) fails-if(Android) == scrollbar-clamping-1.html scrollbar-clamping-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) fails-if(Android) == scrollbar-clamping-2.html scrollbar-clamping-2-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 
 # Test for bad corner joins.
 fuzzy-if(true,1,1) == corner-joins-1.xhtml corner-joins-1-ref.xhtml
-skip-if(B2G) random-if(winWidget) HTTP(..) == corner-joins-2.xhtml corner-joins-2-ref.xhtml
+skip-if(B2G||Mulet) random-if(winWidget) HTTP(..) == corner-joins-2.xhtml corner-joins-2-ref.xhtml # Initial mulet triage: parity with B2G/B2G Desktop
 
-skip-if(B2G) fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,20) fuzzy-if(Android&&browserIsRemote,7,146) fuzzy-if(Android&&!browserIsRemote,166,400) fails-if(Android&&AndroidVersion==15) == scroll-1.html scroll-1-ref.html # see bug 732535 #Bug 959166
+skip-if(B2G||Mulet) fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,20) fuzzy-if(Android&&browserIsRemote,7,146) fuzzy-if(Android&&!browserIsRemote,166,400) fails-if(Android&&AndroidVersion==15) == scroll-1.html scroll-1-ref.html # see bug 732535 #Bug 959166 # Initial mulet triage: parity with B2G/B2G Desktop
 
 == transforms-1.html transforms-1-ref.html
 
 == zero-radius-clip-1.html zero-radius-clip-ref.html
 
 == iframe-1.html iframe-1-ref.html
 
 # Test for antialiasing gaps between background and border
--- a/layout/reftests/box-ordinal/reftest.list
+++ b/layout/reftests/box-ordinal/reftest.list
@@ -1,7 +1,7 @@
 == box-ordinal-with-out-of-flow-1.html box-ordinal-with-out-of-flow-1-ref.html
-skip-if(B2G&&browserIsRemote) == dynamic-1-remove-to-none-grouped.xul dynamic-1-ref.xul
-skip-if(B2G&&browserIsRemote) == dynamic-1-add-to-one-grouped.xul dynamic-1-ref.xul
-skip-if(B2G&&browserIsRemote) == dynamic-1-remove-to-one-grouped-1.xul dynamic-1-ref.xul
-fails skip-if(B2G&&browserIsRemote) == dynamic-1-remove-to-one-grouped-2.xul dynamic-1-ref.xul # bug 575500
-skip-if(B2G&&browserIsRemote) == dynamic-1-add-to-two-grouped-1.xul dynamic-1-ref.xul
-skip-if(B2G&&browserIsRemote) == dynamic-1-add-to-two-grouped-2.xul dynamic-1-ref.xul
+skip-if((B2G&&browserIsRemote)||Mulet) == dynamic-1-remove-to-none-grouped.xul dynamic-1-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == dynamic-1-add-to-one-grouped.xul dynamic-1-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == dynamic-1-remove-to-one-grouped-1.xul dynamic-1-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
+fails skip-if((B2G&&browserIsRemote)||Mulet) == dynamic-1-remove-to-one-grouped-2.xul dynamic-1-ref.xul # bug 575500 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == dynamic-1-add-to-two-grouped-1.xul dynamic-1-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == dynamic-1-add-to-two-grouped-2.xul dynamic-1-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
--- a/layout/reftests/box-properties/reftest.list
+++ b/layout/reftests/box-properties/reftest.list
@@ -14,17 +14,17 @@
 == width-special-values-cell-fixed.html width-special-values-cell-fixed-ref.html
 == box-sizing-1.html box-sizing-1-ref.html
 == box-sizing-2.html box-sizing-2-ref.html
 == box-sizing-3.html box-sizing-1-ref.html
 == box-sizing-4.html box-sizing-4-ref.html
 == box-sizing-minmax-height.html box-sizing-minmax-height-ref.html
 == box-sizing-minmax-width.html box-sizing-minmax-width-ref.html
 == box-sizing-mozbox-minmax-height.html box-sizing-mozbox-minmax-height-ref.html
-skip-if(B2G) == abspos-non-replaced-width-offset-margin.html abspos-non-replaced-width-offset-margin-ref.html
-skip-if(B2G) == abspos-replaced-width-offset-margin.html abspos-replaced-width-offset-margin-ref.html
-skip-if(B2G) HTTP(..) == CSS21-t100301.xhtml CSS21-t100301-ref.xhtml
-random-if(B2G) == CSS21-t100303.xhtml CSS21-t100303-ref.xhtml
-random-if(B2G) == CSS21-t100303-simple.xhtml CSS21-t100303-ref.xhtml
-random-if(B2G) == CSS21-t100801-vertical-align.xhtml CSS21-t100801-vertical-align-ref.xhtml
+skip-if(B2G||Mulet) == abspos-non-replaced-width-offset-margin.html abspos-non-replaced-width-offset-margin-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == abspos-replaced-width-offset-margin.html abspos-replaced-width-offset-margin-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) HTTP(..) == CSS21-t100301.xhtml CSS21-t100301-ref.xhtml # Initial mulet triage: parity with B2G/B2G Desktop
+random-if(B2G||Mulet) == CSS21-t100303.xhtml CSS21-t100303-ref.xhtml # Initial mulet triage: parity with B2G/B2G Desktop
+random-if(B2G||Mulet) == CSS21-t100303-simple.xhtml CSS21-t100303-ref.xhtml # Initial mulet triage: parity with B2G/B2G Desktop
+random-if(B2G||Mulet) == CSS21-t100801-vertical-align.xhtml CSS21-t100801-vertical-align-ref.xhtml # Initial mulet triage: parity with B2G/B2G Desktop
 == clip-auto.html clip-auto-ref.html
 == clip-rect-auto.html clip-rect-auto-ref.html
 == width-rounding.html width-rounding-ref.html
--- a/layout/reftests/box-shadow/reftest.list
+++ b/layout/reftests/box-shadow/reftest.list
@@ -4,28 +4,28 @@
 random == boxshadow-blur-2.html boxshadow-blur-2-ref.html # fixedpoint division in blur code makes this fail
 random != boxshadow-blur-2.html boxshadow-blur-2-notref.html # fixedpoint division in blur code makes this fail
 == boxshadow-multiple.html boxshadow-multiple-ref.html
 == boxshadow-spread.html boxshadow-spread-ref.html
 == tableboxshadow-basic.html tableboxshadow-basic-ref.html
 == tableboxshadow-trshadow.html tableboxshadow-trshadow-ref.html
 == tableboxshadow-tdshadow.html tableboxshadow-tdshadow-ref.html
 == boxshadow-rounding.html boxshadow-rounding-ref.html
-fails-if(Android||B2G) == boxshadow-button.html boxshadow-button-ref.html
-fails-if(Android||B2G) == boxshadow-fileupload.html boxshadow-fileupload-ref.html
+fails-if(Android||B2G||Mulet) == boxshadow-button.html boxshadow-button-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android||B2G||Mulet) == boxshadow-fileupload.html boxshadow-fileupload-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == boxshadow-inner-basic.html boxshadow-inner-basic-ref.svg
 random-if(layersGPUAccelerated) == boxshadow-mixed.html boxshadow-mixed-ref.html
 random-if(d2d) fuzzy-if(B2G,12,18) == boxshadow-rounded-spread.html boxshadow-rounded-spread-ref.html
-skip-if(B2G&&browserIsRemote) HTTP(..) == boxshadow-dynamic.xul boxshadow-dynamic-ref.xul
+skip-if((B2G&&browserIsRemote)||Mulet) HTTP(..) == boxshadow-dynamic.xul boxshadow-dynamic-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
 random-if(d2d) == boxshadow-onecorner.html boxshadow-onecorner-ref.html
 random-if(d2d) == boxshadow-twocorners.html boxshadow-twocorners-ref.html
 random-if(d2d) == boxshadow-threecorners.html boxshadow-threecorners-ref.html
 == boxshadow-skiprect.html boxshadow-skiprect-ref.html
 == boxshadow-opacity.html boxshadow-opacity-ref.html
 == boxshadow-color-rounding.html boxshadow-color-rounding-ref.html
 
 == overflow-not-scrollable-1.html overflow-not-scrollable-1-ref.html
 == overflow-not-scrollable-1.html overflow-not-scrollable-1-ref2.html
 == overflow-not-scrollable-2.html overflow-not-scrollable-2-ref.html
-fails-if(B2G) == 611574-1.html 611574-1-ref.html
-fails-if(B2G) == 611574-2.html 611574-2-ref.html
+fails-if(B2G||Mulet) == 611574-1.html 611574-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(B2G||Mulet) == 611574-2.html 611574-2-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 fuzzy-if(winWidget,5,30) == fieldset.html fieldset-ref.html # minor anti-aliasing problem on Windows
 fuzzy-if(winWidget,5,30) == fieldset-inset.html fieldset-inset-ref.html # minor anti-aliasing problem on Windows
--- a/layout/reftests/box/reftest.list
+++ b/layout/reftests/box/reftest.list
@@ -1,11 +1,11 @@
 == flexbox-abspos-container-1a.html flexbox-abspos-container-1-ref.html
 == flexbox-abspos-container-1b.html flexbox-abspos-container-1-ref.html
 == flexbox-abspos-container-1c.html flexbox-abspos-container-1-ref.html
 == flexbox-abspos-container-1d.html flexbox-abspos-container-1-ref.html
 == flexbox-abspos-container-2.html  flexbox-abspos-container-2-ref.html
 == flexbox-attributes-no-box-horizontal.xhtml flexbox-attributes-no-box-horizontal-ref.xhtml
 == flexbox-attributes-no-box-vertical.xhtml flexbox-attributes-no-box-vertical-ref.xhtml
-skip-if(B2G) == flexbox-attributes-no-input-horizontal.xhtml flexbox-attributes-no-input-horizontal-ref.xhtml
-skip-if(B2G) == flexbox-attributes-no-input-vertical.xhtml flexbox-attributes-no-input-vertical-ref.xhtml
+skip-if(B2G||Mulet) == flexbox-attributes-no-input-horizontal.xhtml flexbox-attributes-no-input-horizontal-ref.xhtml # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == flexbox-attributes-no-input-vertical.xhtml flexbox-attributes-no-input-vertical-ref.xhtml # Initial mulet triage: parity with B2G/B2G Desktop
 == flexbox-child-is-abspos-container-1.html flexbox-child-is-abspos-container-1-ref.html
 == flexbox-child-is-abspos-container-2.html flexbox-child-is-abspos-container-2-ref.html
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -49,19 +49,19 @@ asserts(2) skip-if(!cocoaWidget) HTTP(..
 fails == 25888-1l-block.html 25888-1l-ref.html # Bug 25888
 fails != 25888-1l-block.html 25888-1l-notref.html # Bug 25888
 fails == 25888-1r-block.html 25888-1r-ref.html # Bug 25888
 fails != 25888-1r-block.html 25888-1r-notref.html # Bug 25888
 fails == 25888-2l-block.html 25888-2l-ref.html # Bug 25888
 fails == 25888-2r-block.html 25888-2r-ref.html # Bug 25888
 fails == 25888-3l-block.html 25888-3l-ref.html # Bug 25888
 fails == 25888-3r-block.html 25888-3r-ref.html # Bug 25888
-skip-if(B2G) == 28811-1a.html 28811-1-ref.html
+skip-if(B2G||Mulet) == 28811-1a.html 28811-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 fuzzy-if(gtk2Widget,6,26200) == 28811-1b.html 28811-1-ref.html  # Bug 1128229
-skip-if(B2G) == 28811-2a.html 28811-2-ref.html
+skip-if(B2G||Mulet) == 28811-2a.html 28811-2-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 fuzzy-if(gtk2Widget,6,26200) == 28811-2b.html 28811-2-ref.html  # Bug 1128229
 == 40596-1a.html 40596-1-ref.html
 != 40596-1b.html 40596-1-ref.html
 == 40596-1c.html 40596-1-ref.html
 != 40596-1d.html 40596-1-ref.html
 == 40596-1e.html 40596-1-ref.html
 != 40596-1f.html 40596-1-ref.html
 == 40596-1g.html 40596-1-ref.html
@@ -152,44 +152,44 @@ random == 99850-1b.html 99850-1-ref.html
 == 179596-2.html 179596-2-ref.html
 == 179596-2.html 179596-2-ref2.html
 == 179596-2.html 179596-2-ref3.html
 == 180085-1.html 180085-1-ref.html
 == 180085-2.html 180085-2-ref.html
 == 185388-1.html 185388-1-ref.html
 == 186317-1.html 186317-1-ref.html
 == 192902-1.html 192902-ref.html
-skip-if(B2G) == 192767-01.xul 192767-11.xul
-skip-if(B2G) == 192767-02.xul 192767-12.xul
-skip-if(B2G) == 192767-03.xul 192767-13.xul
-skip-if(B2G) == 192767-04.xul 192767-14.xul
-skip-if(B2G) == 192767-05.xul 192767-15.xul
-skip-if(B2G) == 192767-06.xul 192767-16.xul
-skip-if(B2G) == 192767-07.xul 192767-17.xul
-skip-if(B2G) == 192767-21.xul 192767-31.xul
-skip-if(B2G) == 192767-22.xul 192767-32.xul
-skip-if(B2G) == 192767-23.xul 192767-33.xul
-skip-if(B2G) == 192767-24.xul 192767-34.xul
-skip-if(B2G) == 192767-25.xul 192767-35.xul
-skip-if(B2G) == 192767-26.xul 192767-36.xul
-skip-if(B2G) == 192767-27.xul 192767-37.xul
-skip-if(B2G&&browserIsRemote) != 192767-01.xul 192767-21.xul # bug 974780
-skip-if(B2G&&browserIsRemote) != 192767-02.xul 192767-22.xul # bug 974780
-fails-if(Android) skip-if(B2G) != 192767-03.xul 192767-23.xul
-skip-if(B2G&&browserIsRemote) != 192767-04.xul 192767-24.xul # bug 974780
-skip-if(B2G&&browserIsRemote) != 192767-05.xul 192767-25.xul # bug 974780
-fails-if(Android) skip-if(B2G) != 192767-06.xul 192767-26.xul
-fails-if(Android) skip-if(B2G) != 192767-07.xul 192767-27.xul
-skip-if(B2G&&browserIsRemote) != 192767-11.xul 192767-31.xul # bug 974780
-skip-if(B2G&&browserIsRemote) != 192767-12.xul 192767-32.xul # bug 974780
-fails-if(Android) skip-if(B2G) != 192767-13.xul 192767-33.xul
-skip-if(B2G&&browserIsRemote) != 192767-14.xul 192767-34.xul # bug 974780
-skip-if(B2G&&browserIsRemote) != 192767-15.xul 192767-35.xul # bug 974780
-fails-if(Android) skip-if(B2G) != 192767-16.xul 192767-36.xul
-fails-if(Android) skip-if(B2G) != 192767-17.xul 192767-37.xul
+skip-if(B2G||Mulet) == 192767-01.xul 192767-11.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 192767-02.xul 192767-12.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 192767-03.xul 192767-13.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 192767-04.xul 192767-14.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 192767-05.xul 192767-15.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 192767-06.xul 192767-16.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 192767-07.xul 192767-17.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 192767-21.xul 192767-31.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 192767-22.xul 192767-32.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 192767-23.xul 192767-33.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 192767-24.xul 192767-34.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 192767-25.xul 192767-35.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 192767-26.xul 192767-36.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 192767-27.xul 192767-37.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != 192767-01.xul 192767-21.xul # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != 192767-02.xul 192767-22.xul # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android) skip-if(B2G||Mulet) != 192767-03.xul 192767-23.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != 192767-04.xul 192767-24.xul # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != 192767-05.xul 192767-25.xul # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android) skip-if(B2G||Mulet) != 192767-06.xul 192767-26.xul # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android) skip-if(B2G||Mulet) != 192767-07.xul 192767-27.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != 192767-11.xul 192767-31.xul # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != 192767-12.xul 192767-32.xul # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android) skip-if(B2G||Mulet) != 192767-13.xul 192767-33.xul # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != 192767-14.xul 192767-34.xul # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) != 192767-15.xul 192767-35.xul # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android) skip-if(B2G||Mulet) != 192767-16.xul 192767-36.xul # Initial mulet triage: parity with B2G/B2G Desktop
+fails-if(Android) skip-if(B2G||Mulet) != 192767-17.xul 192767-37.xul # Initial mulet triage: parity with B2G/B2G Desktop
 != 200774-1.html about:blank
 == 201215-1.html 201215-1-ref.html
 == 201293-1a.html 201293-1-ref.html
 == 201293-1b.html 201293-1-ref.html
 == 201293-1c.html 201293-1-ref.html
 == 201293-1d.html 201293-1-ref.html
 == 203727.html 203727-ref.html
 == 206516-1.html 206516-1-ref.html
@@ -201,24 +201,24 @@ fails-if(Android) skip-if(B2G) != 192767
 == 210876-1.html 210876-1-ref.html
 == 211931-1.html 211931-1-ref.html
 == 212563-1.html 212563-1-ref.html
 == 212563-2.html 212563-2-ref.html
 == 213834-1.html 213834-1-ref.html
 == 214077-1a.html 214077-1-ref.html
 == 214077-1b.html 214077-1-ref.html
 == 218473-1.html 218473-1-ref.html
-skip-if(B2G&&browserIsRemote) == 220165-1.svg 220165-1-ref.svg # bug 974780
+skip-if((B2G&&browserIsRemote)||Mulet) == 220165-1.svg 220165-1-ref.svg # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop
 == 223809-1.html 223809-1-ref.html
 == 228856-1.html 228856-1-ref.html
 == 228856-2.html 228856-2-ref.html
 == 229591-1.html 229591-1-ref.html
 # == 231823-1.html 231823-1-ref.html
 == 232990-1a.xhtml 232990-1-ref.xhtml
-skip-if(B2G&&browserIsRemote) == 232990-1b.xhtml 232990-1-ref.xhtml
+skip-if((B2G&&browserIsRemote)||Mulet) == 232990-1b.xhtml 232990-1-ref.xhtml # Initial mulet triage: parity with B2G/B2G Desktop
 == 233094-1.html 233094-1-ref.html
 == 233094-2a.html 233094-2-ref.html
 == 233094-2b.html 233094-2-ref.html
 == 233094-2c.html 233094-2-ref.html
 == 234686-1.html 234686-ref.html
 == 234686-2.html 234686-ref.html
 == 234686-3.html 234686-ref.html
 == 234686-4.html 234686-ref.html
@@ -232,119 +232,119 @@ skip-if(B2G&&browserIsRemote) == 232990-
 == 234686-12.html 234686-ref.html
 == 234686-13.html 234686-ref.html
 == 234686-14.html 234686-ref.html
 == 234686-15.html 234686-ref.html
 == 234686-16.html 234686-ref.html
 == 234686-17.html 234686-ref.html
 == 234686-18.html 234686-ref.html
 == 234686-19.html 234686-ref.html
-skip-if(B2G) == 234964-1.html 234964-1-ref.html
+skip-if(B2G||Mulet) == 234964-1.html 234964-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == 234964-2.html 234964-2-ref.html
 == 235593-1.html 235593-1-ref.html
 == 236539-1.html 236539-1-ref.html
 == 240029-1.html 240029-1-ref.html
 == 240470-1.html 240470-1-ref.html
-skip-if(B2G) == 240933-1.html 240933-1-ref.html
-skip-if(Android||B2G) == 240933-2.html 240933-2-ref.html
+skip-if(B2G||Mulet) == 240933-1.html 240933-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(Android||B2G||Mulet) == 240933-2.html 240933-2-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == 243266-1.html 243266-1-ref.html
 == 243302-1.html 243302-1-ref.html
-skip-if(B2G) == 243519-1.html 243519-1-ref.html
+skip-if(B2G||Mulet) == 243519-1.html 243519-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == 243519-2.html 243519-2-ref.html
 == 243519-3.html 243519-3-ref.html
 == 243519-4a.html 243519-4-ref.html
 == 243519-4b.html 243519-4-ref.html
 == 243519-4c.html 243519-4-ref.html
 == 243519-4d.html 243519-4-ref.html
 == 243519-4e.html 243519-4-ref.html
 == 243519-4f.html 243519-4-ref.html
 == 243519-5a.html 243519-5-ref.html
 == 243519-5b.html 243519-5-ref.html
 == 243519-5c.html 243519-5-ref.html
 == 243519-5d.html 243519-5-ref.html
 == 243519-6.html 243519-6-ref.html
-skip-if(B2G) == 243519-7.html 243519-7-ref.html
+skip-if(B2G||Mulet) == 243519-7.html 243519-7-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == 243519-8.svg 243519-8-ref.svg
 == 243519-9a.html 243519-9-ref.html
 == 243519-9b.html 243519-9-ref.html
 == 243519-9c.html 243519-9-ref.html
 == 243519-9d.html 243519-9-ref.html
 == 243519-9e.html 243519-9-ref.html
 == 243519-9f.html 243519-9-ref.html
 == 244135-1.html 244135-1-ref.html
 == 244135-2.html 244135-2-ref.html
 == 244932-1.html 244932-1-ref.html
 == 246669-1.html 246669-1-ref.html
-skip-if(B2G) == 249141.xul 249141-ref.xul
+skip-if(B2G||Mulet) == 249141.xul 249141-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
 == 249982-1.html 249982-1-ref.html
 == 252920-1.html 252920-1-ref.html
 == 253701-1.html 253701-1-ref.html
 == 255820-1.html 255820-1-ref.html
 == 260406-1.html 260406-1-ref.html
-skip-if(B2G&&browserIsRemote) == 261826-1.xul 261826-1-ref.xul # bug 974780
+skip-if((B2G&&browserIsRemote)||Mulet) == 261826-1.xul 261826-1-ref.xul # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop
 == 262151-1.html 262151-1-ref.html
 == 262998-1.html 262998-1-ref.html
 == 267353-1.html 267353-1-ref.html
 == 269908-1.html 269908-1-ref.html
 == 269908-2.html 269908-2-ref.html
 == 269908-3.html 269908-3-ref.html
 == 269908-4.html 269908-4-ref.html
 == 269908-5.html 269908-5-ref.html
 == 271747-1a.html 271747-1-ref.html
 == 271747-1b.html 271747-1-ref.html
-skip-if(B2G&&browserIsRemote) == 272646-1.xul 272646-1-ref.xul # bug 974780
-skip-if(B2G&&browserIsRemote) == 272646-2a.xul 272646-2-ref.xul # bug 974780
-skip-if(B2G&&browserIsRemote) == 272646-2b.xul 272646-2-ref.xul # bug 974780
-skip-if(B2G&&browserIsRemote) == 272646-2c.xul 272646-2-ref.xul # bug 974780
-skip-if(B2G) == 273681-1.html 273681-1-ref.html
+skip-if((B2G&&browserIsRemote)||Mulet) == 272646-1.xul 272646-1-ref.xul # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == 272646-2a.xul 272646-2-ref.xul # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == 272646-2b.xul 272646-2-ref.xul # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if((B2G&&browserIsRemote)||Mulet) == 272646-2c.xul 272646-2-ref.xul # bug 974780 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) == 273681-1.html 273681-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == 278266-1a.html 278266-1-ref.html
 == 278266-1b.html 278266-1-ref.html
 == 280708-1a.html 280708-1-ref.html
 == 280708-1b.html 280708-1-ref.html
 == 281241-1.html 281241-1-ref.html
 == 281241-2.xhtml 281241-1-ref.html
 == 283686-1.html about:blank
 == 283686-2.html 283686-2-ref.html
 == 283686-3.html about:blank
 == 289384-1.xhtml 289384-ref.xhtml
-random-if(d2d) fuzzy-if(Android&&AndroidVersion>=15,8,1439) skip-if(B2G&&browserIsRemote) HTTP == 289480.html#top 289480-ref.html # basically-verbatim acid2 test, HTTP for a 404 page -- bug 578114 for the d2d failures
+random-if(d2d) fuzzy-if(Android&&AndroidVersion>=15,8,1439) skip-if((B2G&&browserIsRemote)||Mulet) HTTP == 289480.html#top 289480-ref.html # basically-verbatim acid2 test, HTTP for a 404 page -- bug 578114 for the d2d failures # Initial mulet triage: parity with B2G/B2G Desktop
 == 290129-1.html 290129-1-ref.html
-skip-if(B2G) == 291078-1.html 291078-1-ref.html
+skip-if(B2G||Mulet) == 291078-1.html 291078-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == 291078-2.html 291078-2-ref.html
 == 291262-1.html 291262-1-ref.html
 == 294306-1.html 294306-1a-ref.html
 != 294306-1.html 294306-1b-ref.html
 == 296361-1.html 296361-ref.html