Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 04 Nov 2014 13:30:53 +0100
changeset 240340 6066f395207ab227495e47504ef67d5ec97a39d2
parent 240339 2b4400abcbecc93798b316b61258a6045931e574 (current diff)
parent 240278 cadcd47db610f1c6af1ad7c0d692d5511187c782 (diff)
child 240341 1dd40ce6fc5a3774ecf69536b9b240135ffd6f2f
push id660
push userraliiev@mozilla.com
push dateWed, 18 Feb 2015 20:30:48 +0000
treeherdermozilla-release@49e493494178 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
configure.in
dom/telephony/test/marionette/test_incomingcall_phonestate_speaker.js
mobile/android/confvars.sh
--- 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="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3c50520982560ccba301474d1ac43706138fc851"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <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="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
--- 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="70eb0cb0977d6295e7da8896f9efb9f3ca1c13ea">
     <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="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3c50520982560ccba301474d1ac43706138fc851"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c058843242068d0df7c107e09da31b53d2e08fa6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
   <!-- 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="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3c50520982560ccba301474d1ac43706138fc851"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
   <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="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3c50520982560ccba301474d1ac43706138fc851"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <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="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
--- 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="70eb0cb0977d6295e7da8896f9efb9f3ca1c13ea">
     <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="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3c50520982560ccba301474d1ac43706138fc851"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c058843242068d0df7c107e09da31b53d2e08fa6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
   <!-- 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="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3c50520982560ccba301474d1ac43706138fc851"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <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="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
--- 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="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3c50520982560ccba301474d1ac43706138fc851"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
   <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"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "6bf7a21e0dbf766076bfd1c15e59dea7f25a4c13", 
+    "revision": "3b61e1b2ec16c213787b35f0af066933611bf547", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="70eb0cb0977d6295e7da8896f9efb9f3ca1c13ea">
     <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="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3c50520982560ccba301474d1ac43706138fc851"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="70eb0cb0977d6295e7da8896f9efb9f3ca1c13ea">
     <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="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3c50520982560ccba301474d1ac43706138fc851"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/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="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3c50520982560ccba301474d1ac43706138fc851"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
   <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/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="70eb0cb0977d6295e7da8896f9efb9f3ca1c13ea">
     <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="73eeaa23172c26e732972c318ea6aab20e0e1cae"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3c50520982560ccba301474d1ac43706138fc851"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -177,16 +177,19 @@ pref("app.update.mode", 1);
 // add-on incompatibilities are ignored by updates in Metro.
 pref("app.update.metro.enabled", true);
 #endif
 #endif
 
 // If set to true, the Update Service will present no UI for any event.
 pref("app.update.silent", false);
 
+// If set to true, the hamburger button will show badges for update events.
+pref("app.update.badge", false);
+
 // If set to true, the Update Service will apply updates in the background
 // when it finishes downloading them.
 pref("app.update.staging.enabled", true);
 
 // Update service URL:
 #ifndef RELEASE_BUILD
 pref("app.update.url", "https://aus4.mozilla.org/update/3/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
 #else
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1319,16 +1319,18 @@ var gBrowserInit = {
 
     LoopUI.init();
 
     gBrowserThumbnails.init();
 
     // Add Devtools menuitems and listeners
     gDevToolsBrowser.registerBrowserWindow(window);
 
+    gMenuButtonUpdateBadge.init();
+
     window.addEventListener("mousemove", MousePosTracker, false);
     window.addEventListener("dragover", MousePosTracker, false);
 
     gNavToolbox.addEventListener("customizationstarting", CustomizationHandler);
     gNavToolbox.addEventListener("customizationchange", CustomizationHandler);
     gNavToolbox.addEventListener("customizationending", CustomizationHandler);
 
     // End startup crash tracking after a delay to catch crashes while restoring
@@ -1465,16 +1467,18 @@ var gBrowserInit = {
     TabsInTitlebar.uninit();
 
     ToolbarIconColor.uninit();
 
     BrowserOnClick.uninit();
 
     DevEdition.uninit();
 
+    gMenuButtonUpdateBadge.uninit();
+
     var enumerator = Services.wm.getEnumerator(null);
     enumerator.getNext();
     if (!enumerator.hasMoreElements()) {
       document.persist("sidebar-box", "sidebarcommand");
       document.persist("sidebar-box", "width");
       document.persist("sidebar-box", "src");
       document.persist("sidebar-title", "value");
     }
@@ -2434,16 +2438,128 @@ function SetPageProxyState(aState)
 }
 
 function PageProxyClickHandler(aEvent)
 {
   if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste"))
     middleMousePaste(aEvent);
 }
 
+// Setup the hamburger button badges for updates, if enabled.
+let gMenuButtonUpdateBadge = {
+  enabled: false,
+
+  init: function () {
+    try {
+      this.enabled = Services.prefs.getBoolPref("app.update.badge");
+    } catch (e) {}
+    if (this.enabled) {
+      PanelUI.menuButton.classList.add("badged-button");
+      Services.obs.addObserver(this, "update-staged", false);
+    }
+  },
+
+  uninit: function () {
+    if (this.enabled) {
+      Services.obs.removeObserver(this, "update-staged");
+      PanelUI.panel.removeEventListener("popupshowing", this, true);
+      this.enabled = false;
+    }
+  },
+
+  onMenuPanelCommand: function(event) {
+    if (event.originalTarget.getAttribute("update-status") === "succeeded") {
+      // restart the app
+      let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+                       .createInstance(Ci.nsISupportsPRBool);
+      Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+      if (!cancelQuit.data) {
+        Services.startup.quit(Services.startup.eAttemptQuit | Services.startup.eRestart);
+      }
+    } else {
+      // open the page for manual update
+      let url = Services.urlFormatter.formatURLPref("app.update.url.manual");
+      openUILinkIn(url, "tab");
+    }
+  },
+
+  observe: function (subject, topic, status) {
+    const STATE_DOWNLOADING     = "downloading";
+    const STATE_PENDING         = "pending";
+    const STATE_PENDING_SVC     = "pending-service";
+    const STATE_APPLIED         = "applied";
+    const STATE_APPLIED_SVC     = "applied-service";
+    const STATE_FAILED          = "failed";
+
+    let updateButton = document.getElementById("PanelUI-update-status");
+
+    let updateButtonText;
+    let stringId;
+
+    // Update the UI when the background updater is finished.
+    switch (status) {
+      case STATE_APPLIED:
+      case STATE_APPLIED_SVC:
+      case STATE_PENDING:
+      case STATE_PENDING_SVC:
+        // If the update is successfully applied, or if the updater has fallen back
+        // to non-staged updates, add a badge to the hamburger menu to indicate an
+        // update will be applied once the browser restarts.
+        let badge = document.getAnonymousElementByAttribute(PanelUI.menuButton,
+                                                            "class",
+                                                            "toolbarbutton-badge");
+        badge.style.backgroundColor = 'green';
+        PanelUI.menuButton.setAttribute("badge", "\u2605");
+
+        let brandBundle = document.getElementById("bundle_brand");
+        let brandShortName = brandBundle.getString("brandShortName");
+        stringId = "appmenu.restartNeeded.description";
+        updateButtonText = gNavigatorBundle.getFormattedString(stringId,
+                                                               [brandShortName]);
+
+        updateButton.label = updateButtonText;
+        updateButton.hidden = false;
+        updateButton.setAttribute("update-status", "succeeded");
+
+        PanelUI.panel.addEventListener("popupshowing", this, true);
+
+        break;
+      case STATE_FAILED:
+        // Background update has failed, let's show the UI responsible for
+        // prompting the user to update manually.
+        PanelUI.menuButton.setAttribute("badge", "!");
+
+        stringId = "appmenu.updateFailed.description";
+        updateButtonText = gNavigatorBundle.getString(stringId);
+
+        updateButton.label = updateButtonText;
+        updateButton.hidden = false;
+        updateButton.setAttribute("update-status", "failed");
+
+        PanelUI.panel.addEventListener("popupshowing", this, true);
+
+        break;
+      case STATE_DOWNLOADING:
+        // We've fallen back to downloading the full update because the partial
+        // update failed to get staged in the background. Therefore we need to keep
+        // our observer.
+        return;
+    }
+    this.uninit();
+  },
+
+  handleEvent: function(e) {
+    if (e.type === "popupshowing") {
+      PanelUI.menuButton.removeAttribute("badge");
+      PanelUI.panel.removeEventListener("popupshowing", this, true);
+    }
+  }
+};
+
 /**
  * Handle command events bubbling up from error page content
  * or from about:newtab or from remote error pages that invoke
  * us via async messaging.
  */
 let BrowserOnClick = {
   init: function () {
     let mm = window.messageManager;
--- a/browser/base/content/newtab/search.js
+++ b/browser/base/content/newtab/search.js
@@ -208,22 +208,21 @@ let gSearch = {
     else if (engine.iconBuffer) {
       uri = this._getFaviconURIFromBuffer(engine.iconBuffer);
       type = "favicon";
     }
     this._nodes.logo.setAttribute("type", type);
 
     if (uri) {
       this._nodes.logo.style.backgroundImage = "url(" + uri + ")";
-      this._nodes.text.placeholder = "";
     }
     else {
       this._nodes.logo.style.backgroundImage = "";
-      this._nodes.text.placeholder = engine.name;
     }
+    this._nodes.text.placeholder = engine.placeholder;
 
     // Set up the suggestion controller.
     if (!this._suggestionController) {
       let parent = document.getElementById("newtab-scrollbox");
       this._suggestionController =
         new SearchSuggestionUIController(this._nodes.text, parent,
                                          () => this.search());
     }
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -135,104 +135,101 @@ skip-if = e10s
 skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
 [browser_bug329212.js]
 skip-if = e10s
 [browser_bug331772_xul_tooltiptext_in_html.js]
 skip-if = e10s
 [browser_bug356571.js]
 [browser_bug380960.js]
 [browser_bug386835.js]
-skip-if = e10s # Bug 691614 - no e10s zoom support yet
+skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug405137.js]
 [browser_bug406216.js]
 [browser_bug409481.js]
 [browser_bug409624.js]
 skip-if = e10s
 [browser_bug413915.js]
 [browser_bug416661.js]
-skip-if = e10s # Bug 691614 - no e10s zoom support yet
+skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug417483.js]
 skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
 [browser_bug419612.js]
-skip-if = e10s # Bug 691614 - no e10s zoom support yet
+skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug422590.js]
 [browser_bug846489.js]
 [browser_bug423833.js]
 skip-if = true # bug 428712
 [browser_bug424101.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content
 [browser_bug427559.js]
 skip-if = e10s # Bug ?????? - "content window is focused - Got [object ChromeWindow], expected [object XrayWrapper [object Window]]"
 [browser_bug431826.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (eg, var expertDiv = gBrowser.contentDocument.getElementById("expertContent");)
 [browser_bug432599.js]
 [browser_bug435035.js]
 [browser_bug435325.js]
 skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content
 [browser_bug441778.js]
-skip-if = buildapp == 'mulet' || e10s # Bug 691614 - no e10s zoom support yet
+skip-if = buildapp == 'mulet' || e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug455852.js]
 skip-if = e10s
 [browser_bug460146.js]
 skip-if = e10s # Bug 866413 - PageInfo doesn't work in e10s
 [browser_bug462289.js]
 skip-if = toolkit == "cocoa" || e10s # Bug ?????? - not sure why this is timing out and crashing!!
 [browser_bug462673.js]
-skip-if = e10s # Bug 924260 - "Window is closed"
+skip-if = e10s # Bug 1093404 - test expects sync window opening from content and is disappointed in that expectation
 [browser_bug477014.js]
 skip-if = e10s # Bug 1093206 - need to re-enable tests relying on swapFrameLoaders et al for e10s
 [browser_bug479408.js]
-skip-if = buildapp == 'mulet' || e10s # Bug 918663 - DOMLinkAdded events don't make their way to chrome
+skip-if = buildapp == 'mulet'
 [browser_bug481560.js]
 skip-if = e10s # Bug ????? - This bug attached an event listener directly to the content
 [browser_bug484315.js]
 skip-if = e10s
 [browser_bug491431.js]
 skip-if = buildapp == 'mulet'
 [browser_bug495058.js]
 skip-if = e10s # Bug 1093206 - need to re-enable tests relying on swapFrameLoaders et al (and thus replaceTabWithWindow) for e10s
 [browser_bug517902.js]
 skip-if = e10s # Bug 866413 - PageInfo doesn't work in e10s
 [browser_bug519216.js]
 skip-if = e10s # Bug ?????? - some weird timing issue with progress listeners that fails intermittently
 [browser_bug520538.js]
 [browser_bug521216.js]
-skip-if = e10s # Bug 918663 - DOMLinkAdded events don't make their way to chrome
 [browser_bug533232.js]
 [browser_bug537013.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1093206 - need to re-enable tests relying on swapFrameLoaders et al for e10s (test calls replaceTabWithWindow)
 [browser_bug537474.js]
 skip-if = e10s # Bug ?????? - test doesn't wait for document to be created before it checks it
 [browser_bug550565.js]
-skip-if = e10s # Bug 918663 - DOMLinkAdded events don't make their way to chrome (which is how gBrowser.getIcon works)
 [browser_bug553455.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?
 [browser_bug555224.js]
-skip-if = e10s # Bug 691614 - no e10s zoom support yet
+skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug555767.js]
-skip-if = e10s # Bug 916974 - Session history doesn't work in e10s
+skip-if = e10s # Bug 1093373 - relies on browser.sessionHistory
 [browser_bug556061.js]
 [browser_bug559991.js]
-skip-if = e10s # Bug 691614 - no e10s zoom support yet
 [browser_bug561623.js]
 skip-if = e10s
 [browser_bug561636.js]
 skip-if = e10s # Bug 691601 - no form submit observers
 [browser_bug562649.js]
 skip-if = e10s # Bug 940195 - XULBrowserWindow.isBusy is false as a remote tab starts loading
 [browser_bug563588.js]
 [browser_bug565575.js]
 skip-if = e10s
 [browser_bug565667.js]
 run-if = toolkit == "cocoa"
 [browser_bug567306.js]
 skip-if = e10s
 [browser_bug575561.js]
 [browser_bug575830.js]
-skip-if = e10s # Bug 691614 - no e10s zoom support yet
+skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug577121.js]
 [browser_bug578534.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content
 [browser_bug579872.js]
 [browser_bug580638.js]
 [browser_bug580956.js]
 [browser_bug581242.js]
 skip-if = e10s # Bug 930863 - pageshow issues ("TypeError: charset is undefined" in pageshow listener, as document is null)
@@ -255,58 +252,58 @@ skip-if = e10s # Bug 691601 - no form su
 skip-if = e10s # Bug 516755 - SessionStore disabled for e10s (calls duplicateTabIn, which uses SessionStore)
 [browser_bug623155.js]
 skip-if = e10s # Bug ?????? - URLBar issues (apparently issues with redirection)
 [browser_bug623893.js]
 [browser_bug624734.js]
 [browser_bug633691.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (eg, var expertDiv = gBrowser.contentDocument.getElementById("expertContent");)
 [browser_bug647886.js]
-skip-if = buildapp == 'mulet' || e10s # Bug 916974 - Session history doesn't work in e10s
+skip-if = buildapp == 'mulet' || e10s # Bug 1093373 - Relies on browser.sessionHistory
 [browser_bug655584.js]
 skip-if = e10s
 [browser_bug664672.js]
 [browser_bug676619.js]
 skip-if = buildapp == 'mulet' || os == "mac" || e10s # mac: Intermittent failures, bug 925225; e10s: Bug ?????? - test directly manipulates content (event.target.location)
 [browser_bug678392.js]
 skip-if = e10s # Bug ?????? - Obscure non-windows failures ("Snapshot array has correct length of 1 after loading one page. - Got 0, expected 1" and more)
 [browser_bug710878.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (doc.querySelector)
 [browser_bug719271.js]
-skip-if = e10s # Bug 691614 - no e10s zoom support yet
+skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug724239.js]
 skip-if = e10s # Bug 1077738
 [browser_bug734076.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content
 [browser_bug735471.js]
 [browser_bug749738.js]
 skip-if = e10s # Bug 921935 - focusmanager issues with e10s
 [browser_bug763468_perwindowpb.js]
 skip-if = e10s
 [browser_bug767836_perwindowpb.js]
 skip-if = e10s # Bug ?????? - test reports a leaked nsGlobalWindow with e10s enabled.
 [browser_bug771331.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content
 [browser_bug783614.js]
 [browser_bug816527.js]
-skip-if = e10s # Bug 916974 - Session history doesn't work in e10s
+skip-if = e10s # Bug 1093373 - relies on browser.sessionHistory
 [browser_bug817947.js]
 [browser_bug822367.js]
 [browser_bug832435.js]
 [browser_bug839103.js]
 [browser_bug880101.js]
 [browser_bug882977.js]
 [browser_bug902156.js]
 skip-if = e10s
 [browser_bug906190.js]
 skip-if = buildapp == "mulet" || e10s # Bug ?????? - test directly manipulates content (strange - gets an element from a child which it tries to treat as a string, but that fails)
 [browser_bug970746.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (directly gets elements from the content)
 [browser_bug1015721.js]
-skip-if = os == 'win' || e10s # Bug 1056146 - FullZoomHelper uses promiseTabLoadEvent() which isn't e10s friendly
+skip-if = os == 'win' || e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug1064280_changeUrlInPinnedTab.js]
 [browser_canonizeURL.js]
 skip-if = e10s # Bug ?????? - [JavaScript Error: "Error in AboutHome.sendAboutHomeData TypeError: target.messageManager is undefined" {file: "resource:///modules/AboutHome.jsm" line: 208}]
 [browser_contentAreaClick.js]
 skip-if = e10s
 [browser_contextSearchTabPosition.js]
 skip-if = os == "mac" || e10s # bug 967013, bug 926729
 [browser_ctrlTab.js]
@@ -316,17 +313,16 @@ skip-if = e10s
 [browser_datareporting_notification.js]
 run-if = datareporting
 [browser_devedition.js]
 [browser_devices_get_user_media.js]
 skip-if = buildapp == 'mulet' || (os == "linux" && debug) || e10s # linux: bug 976544; e10s: bug 1071623
 [browser_devices_get_user_media_about_urls.js]
 skip-if = e10s # Bug 1071623
 [browser_discovery.js]
-skip-if = e10s # Bug 918663 - DOMLinkAdded events don't make their way to chrome
 [browser_double_close_tab.js]
 skip-if = e10s
 [browser_duplicateIDs.js]
 [browser_drag.js]
 skip-if = true # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
 [browser_favicon_change.js]
 skip-if = e10s
 [browser_favicon_change_not_in_document.js]
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -11,16 +11,20 @@
        noautofocus="true">
   <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">
     <panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
       <vbox id="PanelUI-contents-scroller">
         <vbox id="PanelUI-contents" class="panelUI-grid"/>
       </vbox>
 
       <footer id="PanelUI-footer">
+        <toolbarbutton id="PanelUI-update-status"
+                       oncommand="gMenuButtonUpdateBadge.onMenuPanelCommand(event);"
+                       wrap="true"
+                       hidden="true"/>
         <toolbarbutton id="PanelUI-fxa-status"
                        defaultlabel="&fxaSignIn.label;"
                        errorlabel="&fxaSignInError.label;"
                        oncommand="gFxAccounts.onMenuPanelCommand(event);"
                        hidden="true"/>
 
         <hbox id="PanelUI-footer-inner">
           <toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
--- a/browser/components/loop/standalone/content/index.html
+++ b/browser/components/loop/standalone/content/index.html
@@ -22,16 +22,17 @@
     <script>
       window.OTProperties = {
         cdnURL: 'shared/libs/',
       };
       window.OTProperties.assetURL = window.OTProperties.cdnURL + 'sdk-content/';
       window.OTProperties.configURL = window.OTProperties.assetURL + 'js/dynamic_config.min.js';
       window.OTProperties.cssURL = window.OTProperties.assetURL + 'css/ot.css';
     </script>
+    <script type="text/javascript" src="js/multiplexGum.js"></script>
     <script type="text/javascript" src="shared/libs/sdk.js"></script>
     <script type="text/javascript" src="libs/l10n-gaia-02ca67948fe8.js"></script>
     <script type="text/javascript" src="shared/libs/react-0.11.2.js"></script>
     <script type="text/javascript" src="shared/libs/jquery-2.1.0.js"></script>
     <script type="text/javascript" src="shared/libs/lodash-2.4.1.js"></script>
     <script type="text/javascript" src="shared/libs/backbone-1.1.2.js"></script>
 
     <!-- app scripts -->
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/standalone/content/js/multiplexGum.js
@@ -0,0 +1,150 @@
+/* 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/. */
+
+
+var loop = loop || {};
+
+/**
+ * Monkeypatch getUserMedia in a way that prevents additional camera and
+ * microphone prompts, at the cost of ignoring all constraints other than
+ * the first set passed in.
+ *
+ * The first call to navigator.getUserMedia (also now aliased to
+ * multiplexGum.getPermsAndCacheMedia to allow for explicit calling code)
+ * will cause the underlying gUM implementation to be called.
+ *
+ * While permission is pending, subsequent calls will result in the callbacks
+ * being queued. Once the call succeeds or fails, all queued success or
+ * failure callbacks will be invoked.  Subsequent calls to either function will
+ * cause the success or failure callback to be invoked immediately.
+ */
+loop.standaloneMedia = (function() {
+  "use strict";
+
+  function patchSymbolIfExtant(objectName, propertyName, replacement) {
+    var object;
+    if (window[objectName]) {
+      object = window[objectName];
+    }
+    if (object && object[propertyName]) {
+      object[propertyName] = replacement;
+    }
+  }
+
+  // originalGum _must_ be on navigator; otherwise things blow up
+  navigator.originalGum = navigator.getUserMedia ||
+                          navigator.mozGetUserMedia ||
+                          navigator.webkitGetUserMedia ||
+                          (window["TBPlugin"] && TBPlugin.getUserMedia);
+
+  function _MultiplexGum() {
+    this.reset();
+  }
+
+  _MultiplexGum.prototype = {
+    /**
+     * @see The docs at the top of this file for overall semantics,
+     * & http://developer.mozilla.org/en-US/docs/NavigatorUserMedia.getUserMedia
+     * for params, since this is intended to be purely a passthrough to gUM.
+     */
+    getPermsAndCacheMedia: function(constraints, onSuccess, onError) {
+      function handleResult(callbacks, param) {
+        // Operate on a copy of the array in case any of the callbacks
+        // calls reset, which would cause an infinite-recursion.
+        this.userMedia.successCallbacks = [];
+        this.userMedia.errorCallbacks = [];
+        callbacks.forEach(function(cb) {
+          if (typeof cb == "function") {
+            cb(param);
+          }
+        })
+      }
+      function handleSuccess(localStream) {
+        this.userMedia.pending = false;
+        this.userMedia.localStream = localStream;
+        this.userMedia.error = null;
+        handleResult.call(this, this.userMedia.successCallbacks.slice(0), localStream);
+      }
+
+      function handleError(error) {
+        this.userMedia.pending = false;
+        this.userMedia.error = error;
+        handleResult.call(this, this.userMedia.errorCallbacks.slice(0), error);
+        this.error = null;
+      }
+
+      if (this.userMedia.localStream &&
+          this.userMedia.localStream.ended) {
+        this.userMedia.localStream = null;
+      }
+
+      this.userMedia.errorCallbacks.push(onError);
+      this.userMedia.successCallbacks.push(onSuccess);
+
+      if (this.userMedia.localStream) {
+        handleSuccess.call(this, this.userMedia.localStream);
+        return;
+      } else if (this.userMedia.error) {
+        handleError.call(this, this.userMedia.error);
+        return;
+      }
+
+      if (this.userMedia.pending) {
+        return;
+      }
+      this.userMedia.pending = true;
+
+      navigator.originalGum(constraints, handleSuccess.bind(this),
+        handleError.bind(this));
+    },
+
+    /**
+     * Reset the cached permissions, callbacks, and media to their default
+     * state and call any error callbacks to let any waiting callers know
+     * not to ever expect any more callbacks.  We use "PERMISSION_DENIED",
+     * for lack of a better, more specific gUM code that callers are likely
+     * to be prepared to handle.
+     */
+    reset: function() {
+      // When called from the ctor, userMedia is not created yet.
+      if (this.userMedia) {
+        this.userMedia.errorCallbacks.forEach(function(cb) {
+          if (typeof cb == "function") {
+            cb("PERMISSION_DENIED");
+          }
+        });
+        if (this.userMedia.localStream &&
+            typeof this.userMedia.localStream.stop == "function") {
+          this.userMedia.localStream.stop();
+        }
+      }
+      this.userMedia = {
+        error: null,
+        localStream: null,
+        pending: false,
+        errorCallbacks: [],
+        successCallbacks: [],
+      };
+    }
+  };
+
+  var singletonMultiplexGum = new _MultiplexGum();
+  function myGetUserMedia() {
+    // This function is needed to pull in the instance
+    // of the singleton for tests to overwrite the used instance.
+    singletonMultiplexGum.getPermsAndCacheMedia.apply(singletonMultiplexGum, arguments);
+  };
+  patchSymbolIfExtant("navigator", "mozGetUserMedia", myGetUserMedia);
+  patchSymbolIfExtant("navigator", "webkitGetUserMedia", myGetUserMedia);
+  patchSymbolIfExtant("navigator", "getUserMedia", myGetUserMedia);
+  patchSymbolIfExtant("TBPlugin", "getUserMedia", myGetUserMedia);
+
+  return {
+    multiplexGum: singletonMultiplexGum,
+    _MultiplexGum: _MultiplexGum,
+    setSingleton: function(singleton) {
+      singletonMultiplexGum = singleton;
+    },
+  };
+})();
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -14,21 +14,24 @@ loop.webapp = (function($, _, OT, mozL10
   loop.config = loop.config || {};
   loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
 
   var sharedMixins = loop.shared.mixins;
   var sharedModels = loop.shared.models;
   var sharedViews = loop.shared.views;
   var sharedUtils = loop.shared.utils;
 
+  var multiplexGum = loop.standaloneMedia.multiplexGum;
+
   /**
    * Homepage view.
    */
   var HomeView = React.createClass({displayName: 'HomeView',
     render: function() {
+      loop.standaloneMedia.multiplexGum.reset();
       return (
         React.DOM.p(null, mozL10n.get("welcome", {clientShortname: mozL10n.get("clientShortname2")}))
       );
     }
   });
 
   /**
    * Unsupported Browsers view.
@@ -282,16 +285,17 @@ loop.webapp = (function($, _, OT, mozL10
     },
 
     _handleRingingProgress: function() {
       this.play("ringing", {loop: true});
       this.setState({callState: "ringing"});
     },
 
     _cancelOutgoingCall: function() {
+      loop.standaloneMedia.multiplexGum.reset();
       this.props.websocket.cancel();
     },
 
     render: function() {
       var callStateStringEntityName = "call_progress_" + this.state.callState + "_description";
       var callState = mozL10n.get(callStateStringEntityName);
       document.title = mozL10n.get("standalone_title_with_status",
                                    {clientShortname: mozL10n.get("clientShortname2"),
@@ -443,18 +447,25 @@ loop.webapp = (function($, _, OT, mozL10
      * Takes in a call type parameter "audio" or "audio-video" and returns
      * a function that initiates the call. React click handler requires a function
      * to be called when that event happenes.
      *
      * @param {string} User call type choice "audio" or "audio-video"
      */
     startCall: function(callType) {
       return function() {
-        this.props.conversation.setupOutgoingCall(callType);
-        this.setState({disableCallButton: true});
+        multiplexGum.getPermsAndCacheMedia({audio:true, video:true},
+          function(localStream) {
+            this.props.conversation.setupOutgoingCall(callType);
+            this.setState({disableCallButton: true});
+          }.bind(this),
+          function(errorCode) {
+            multiplexGum.reset();
+          }.bind(this)
+        );
       }.bind(this);
     },
 
     _setConversationTimestamp: function(err, callUrlInfo) {
       if (err) {
         this.props.notifications.errorL10n("unable_retrieve_call_info");
       } else {
         this.setState({
--- a/browser/components/loop/standalone/content/js/webapp.jsx
+++ b/browser/components/loop/standalone/content/js/webapp.jsx
@@ -14,21 +14,24 @@ loop.webapp = (function($, _, OT, mozL10
   loop.config = loop.config || {};
   loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
 
   var sharedMixins = loop.shared.mixins;
   var sharedModels = loop.shared.models;
   var sharedViews = loop.shared.views;
   var sharedUtils = loop.shared.utils;
 
+  var multiplexGum = loop.standaloneMedia.multiplexGum;
+
   /**
    * Homepage view.
    */
   var HomeView = React.createClass({
     render: function() {
+      loop.standaloneMedia.multiplexGum.reset();
       return (
         <p>{mozL10n.get("welcome", {clientShortname: mozL10n.get("clientShortname2")})}</p>
       );
     }
   });
 
   /**
    * Unsupported Browsers view.
@@ -282,16 +285,17 @@ loop.webapp = (function($, _, OT, mozL10
     },
 
     _handleRingingProgress: function() {
       this.play("ringing", {loop: true});
       this.setState({callState: "ringing"});
     },
 
     _cancelOutgoingCall: function() {
+      loop.standaloneMedia.multiplexGum.reset();
       this.props.websocket.cancel();
     },
 
     render: function() {
       var callStateStringEntityName = "call_progress_" + this.state.callState + "_description";
       var callState = mozL10n.get(callStateStringEntityName);
       document.title = mozL10n.get("standalone_title_with_status",
                                    {clientShortname: mozL10n.get("clientShortname2"),
@@ -443,18 +447,25 @@ loop.webapp = (function($, _, OT, mozL10
      * Takes in a call type parameter "audio" or "audio-video" and returns
      * a function that initiates the call. React click handler requires a function
      * to be called when that event happenes.
      *
      * @param {string} User call type choice "audio" or "audio-video"
      */
     startCall: function(callType) {
       return function() {
-        this.props.conversation.setupOutgoingCall(callType);
-        this.setState({disableCallButton: true});
+        multiplexGum.getPermsAndCacheMedia({audio:true, video:true},
+          function(localStream) {
+            this.props.conversation.setupOutgoingCall(callType);
+            this.setState({disableCallButton: true});
+          }.bind(this),
+          function(errorCode) {
+            multiplexGum.reset();
+          }.bind(this)
+        );
       }.bind(this);
     },
 
     _setConversationTimestamp: function(err, callUrlInfo) {
       if (err) {
         this.props.notifications.errorL10n("unable_retrieve_call_info");
       } else {
         this.setState({
--- a/browser/components/loop/test/standalone/index.html
+++ b/browser/components/loop/test/standalone/index.html
@@ -32,20 +32,22 @@
   </script>
   <!-- App scripts -->
   <script src="../../content/shared/js/utils.js"></script>
   <script src="../../content/shared/js/models.js"></script>
   <script src="../../content/shared/js/mixins.js"></script>
   <script src="../../content/shared/js/views.js"></script>
   <script src="../../content/shared/js/websocket.js"></script>
   <script src="../../content/shared/js/feedbackApiClient.js"></script>
+  <script src="../../standalone/content/js/multiplexGum.js"></script>
   <script src="../../standalone/content/js/standaloneClient.js"></script>
   <script src="../../standalone/content/js/webapp.js"></script>
  <!-- Test scripts -->
   <script src="standalone_client_test.js"></script>
   <script src="webapp_test.js"></script>
+  <script src="multiplexGum_test.js"></script>
   <script>
     mocha.run(function () {
       $("#mocha").append("<p id='complete'>Complete.</p>");
     });
 </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/standalone/multiplexGum_test.js
@@ -0,0 +1,370 @@
+/* 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/. */
+
+/*global loop, sinon, it, beforeEach, afterEach, describe*/
+
+var expect = chai.expect;
+
+describe("loop.standaloneMedia._MultiplexGum", function() {
+  "use strict";
+
+  var defaultGum =
+    navigator.getUserMedia ||
+    navigator.mozGetUserMedia ||
+    navigator.webkitGetUserMedia ||
+    (window["TBPlugin"] && TBPlugin.getUserMedia);
+
+  var sandbox;
+  var multiplexGum;
+
+  beforeEach(function() {
+    sandbox = sinon.sandbox.create();
+    multiplexGum = new loop.standaloneMedia._MultiplexGum();
+    loop.standaloneMedia.setSingleton(multiplexGum);
+  });
+
+  afterEach(function() {
+    sandbox.restore();
+  });
+
+  describe("#constructor", function() {
+    it("pending should default to false", function() {
+      expect(multiplexGum.userMedia.pending).to.equal(false);
+    });
+  });
+
+  describe("default getUserMedia", function() {
+    it("should call getPermsAndCacheMedia", function() {
+      var fakeOptions = {audio: true, video: true};
+      var successCB = function() {};
+      var errorCB = function() {};
+      sandbox.stub(navigator, "originalGum");
+      sandbox.stub(loop.standaloneMedia._MultiplexGum.prototype,
+        "getPermsAndCacheMedia");
+      multiplexGum = new loop.standaloneMedia._MultiplexGum();
+
+      defaultGum(fakeOptions, successCB, errorCB);
+
+      sinon.assert.calledOnce(multiplexGum.getPermsAndCacheMedia);
+      sinon.assert.calledWithExactly(multiplexGum.getPermsAndCacheMedia,
+        fakeOptions, successCB, errorCB);
+    });
+  });
+
+  describe("#getPermsAndCacheMedia", function() {
+    beforeEach(function() {
+      sandbox.stub(navigator, "originalGum");
+    });
+
+    it("should change pending to true", function() {
+      multiplexGum.getPermsAndCacheMedia();
+
+      expect(multiplexGum.userMedia.pending).to.equal(true);
+    });
+
+    it("should call originalGum", function() {
+      multiplexGum.getPermsAndCacheMedia();
+
+      sinon.assert.calledOnce(navigator.originalGum);
+    });
+
+    it("should reset the pending state when the error callback is called",
+      function(done) {
+        var fakeError = new Error();
+        navigator.originalGum.callsArgWith(2, fakeError);
+
+        multiplexGum.getPermsAndCacheMedia(null, null, function onError(error) {
+          expect(multiplexGum.userMedia.pending).to.equal(false);
+          done();
+        });
+      });
+
+    it("should reset the pending state when the success callback is called",
+      function(done) {
+        var fakeLocalStream = {};
+        navigator.originalGum.callsArgWith(1, fakeLocalStream);
+
+        multiplexGum.getPermsAndCacheMedia(null,
+          function onSuccess(localStream) {
+            expect(multiplexGum.userMedia.pending).to.equal(false);
+            done();
+          }, null);
+      });
+
+    it("should call the error callback when originalGum calls back an error",
+      function(done) {
+        var fakeError = new Error();
+        navigator.originalGum.callsArgWith(2, fakeError);
+
+        multiplexGum.getPermsAndCacheMedia(null, null, function onError(error) {
+          expect(error).to.eql(fakeError);
+          done();
+        });
+      });
+
+    it("should propagate the success callback when originalGum succeeds",
+      function(done) {
+        var fakeLocalStream = {};
+        navigator.originalGum.callsArgWith(1, fakeLocalStream);
+
+        multiplexGum.getPermsAndCacheMedia(null,
+          function onSuccess(localStream) {
+            expect(localStream).to.eql(fakeLocalStream);
+            done();
+          }, null);
+      });
+
+    it("should call the success callback when the stream is cached",
+      function(done) {
+        var fakeLocalStream = {};
+        multiplexGum.userMedia.localStream = fakeLocalStream;
+        sinon.assert.notCalled(navigator.originalGum);
+
+        multiplexGum.getPermsAndCacheMedia(null,
+          function onSuccess(localStream) {
+            expect(localStream).to.eql(fakeLocalStream);
+            done();
+          }, null);
+      });
+
+    it("should call the error callback when an error is cached",
+      function(done) {
+        var fakeError = new Error();
+        multiplexGum.userMedia.error = fakeError;
+        sinon.assert.notCalled(navigator.originalGum);
+
+        multiplexGum.getPermsAndCacheMedia(null, null, function onError(error) {
+          expect(error).to.eql(fakeError);
+          done();
+        });
+      });
+
+    it("should clear the error when success is called back", function(done) {
+      var fakeError = new Error();
+      var fakeLocalStream = {};
+      multiplexGum.userMedia.localStream = fakeLocalStream;
+      multiplexGum.userMedia.error = fakeError;
+
+      multiplexGum.getPermsAndCacheMedia(null, function onSuccess(localStream) {
+        expect(multiplexGum.userMedia.error).to.not.eql(fakeError);
+        expect(localStream).to.eql(fakeLocalStream);
+        done();
+      }, null);
+    });
+
+    it("should call all success callbacks when success is achieved",
+      function(done) {
+        var fakeLocalStream = {};
+        var calls = 0;
+        // Async is needed so that the callbacks can be queued up.
+        navigator.originalGum.callsArgWithAsync(1, fakeLocalStream);
+
+        multiplexGum.getPermsAndCacheMedia(null, function onSuccess(localStream) {
+          calls += 1;
+          expect(localStream).to.eql(fakeLocalStream);
+        }, null);
+
+        expect(multiplexGum.userMedia).to.have.property('pending', true);
+
+        multiplexGum.getPermsAndCacheMedia(null, function onSuccess(localStream) {
+          calls += 10;
+          expect(localStream).to.eql(fakeLocalStream);
+          expect(calls).to.equal(11);
+          done();
+        }, null);
+      });
+
+    it("should call all error callbacks when error is encountered",
+      function(done) {
+        var fakeError = new Error();
+        var calls = 0;
+        // Async is needed so that the callbacks can be queued up.
+        navigator.originalGum.callsArgWithAsync(2, fakeError);
+
+        multiplexGum.getPermsAndCacheMedia(null, null, function onError(error) {
+          calls += 1;
+          expect(error).to.eql(fakeError);
+        });
+
+        expect(multiplexGum.userMedia).to.have.property('pending', true);
+
+        multiplexGum.getPermsAndCacheMedia(null, null, function onError(error) {
+          calls += 10;
+          expect(error).to.eql(fakeError);
+          expect(calls).to.eql(11);
+          done();
+        });
+      });
+
+    it("should not call a getPermsAndCacheMedia success callback at the time" +
+       " of gUM success callback fires",
+      function(done) {
+        var fakeLocalStream = {};
+        multiplexGum.userMedia.localStream = fakeLocalStream;
+        navigator.originalGum.callsArgWith(1, fakeLocalStream);
+        var calledOnce = false;
+        var promiseCalledOnce = new Promise(function(resolve, reject) {
+
+          multiplexGum.getPermsAndCacheMedia(null,
+            function gPACMSuccess(localStream) {
+              expect(localStream).to.eql(fakeLocalStream);
+              expect(multiplexGum.userMedia).to.have.property('pending', false);
+              expect(multiplexGum.userMedia.successCallbacks.length).to.equal(0);
+              if (calledOnce) {
+                sinon.assert.fail("original callback was called twice");
+              }
+              calledOnce = true;
+              resolve();
+            }, function() {
+              sinon.assert.fail("error callback should not have fired");
+              reject();
+              done();
+            });
+        });
+
+        promiseCalledOnce.then(function() {
+          defaultGum(null, function gUMSuccess(localStream2) {
+            expect(localStream2).to.eql(fakeLocalStream);
+            expect(multiplexGum.userMedia).to.have.property('pending', false);
+            expect(multiplexGum.userMedia.successCallbacks.length).to.equal(0);
+            done();
+          });
+        });
+      });
+
+    it("should not call a getPermsAndCacheMedia error callback when the " +
+      " gUM error callback fires",
+      function(done) {
+        var fakeError = "monkeys ate the stream";
+        multiplexGum.userMedia.error = fakeError;
+        navigator.originalGum.callsArgWith(2, fakeError);
+        var calledOnce = false;
+        var promiseCalledOnce = new Promise(function(resolve, reject) {
+          multiplexGum.getPermsAndCacheMedia(null, function() {
+            sinon.assert.fail("success callback should not have fired");
+            reject();
+            done();
+          }, function gPACMError(errString) {
+            expect(errString).to.eql(fakeError);
+            expect(multiplexGum.userMedia).to.have.property('pending', false);
+            if (calledOnce) {
+              sinon.assert.fail("original error callback was called twice");
+            }
+            calledOnce = true;
+            resolve();
+          });
+        });
+
+        promiseCalledOnce.then(function() {
+          defaultGum(null, function() {},
+            function gUMError(errString) {
+              expect(errString).to.eql(fakeError);
+              expect(multiplexGum.userMedia).to.have.property('pending', false);
+              done();
+            });
+        });
+      });
+
+    it("should call the success callback with a new stream, " +
+       " when a new stream is available",
+      function(done) {
+        var endedStream = {ended: true};
+        var newStream = {};
+        multiplexGum.userMedia.localStream = endedStream;
+        navigator.originalGum.callsArgWith(1, newStream);
+
+        multiplexGum.getPermsAndCacheMedia(null, function onSuccess(localStream) {
+          expect(localStream).to.eql(newStream);
+          done();
+        }, null);
+      });
+  });
+
+  describe("#reset", function () {
+    it("should reset all userMedia state to default", function() {
+      // If userMedia is defined, then it needs to have all of
+      // the properties that multipleGum will depend on. It is
+      // easier to simply delete the object than to setup a fake
+      // state of the object.
+      delete multiplexGum.userMedia;
+
+      multiplexGum.reset();
+
+      expect(multiplexGum.userMedia).to.deep.equal({
+          error: null,
+          localStream: null,
+          pending: false,
+          errorCallbacks: [],
+          successCallbacks: [],
+      });
+    });
+
+    it("should call all queued error callbacks with 'PERMISSION_DENIED'",
+      function(done) {
+        sandbox.stub(navigator, "originalGum");
+        multiplexGum.getPermsAndCacheMedia(null, function(localStream) {
+          sinon.assert.fail(
+            "The success callback shouldn't be called due to reset");
+        }, function(error) {
+          expect(error).to.equal("PERMISSION_DENIED");
+          done();
+        });
+        multiplexGum.reset();
+      });
+
+    it("should call MST.stop() on the stream tracks", function() {
+      var stopStub = sandbox.stub();
+      multiplexGum.userMedia.localStream = {stop: stopStub};
+
+      multiplexGum.reset();
+
+      sinon.assert.calledOnce(stopStub);
+    });
+
+    it("should not call MST.stop() on the stream tracks if .stop() doesn't exist",
+      function() {
+        multiplexGum.userMedia.localStream = {};
+
+        try {
+          multiplexGum.reset();
+        } catch (ex) {
+          sinon.assert.fail(
+            "reset shouldn't throw when a stream doesn't implement stop(): "
+            + ex);
+        }
+      });
+
+    it("should not get stuck in recursion if the error callback calls 'reset'",
+      function() {
+        sandbox.stub(navigator, "originalGum");
+        navigator.originalGum.callsArgWith(2, "PERMISSION_DENIED");
+
+        var calledOnce = false;
+        multiplexGum.getPermsAndCacheMedia(null, null, function() {
+          if (calledOnce) {
+            sinon.assert.fail("reset should only be called once");
+          }
+          calledOnce = true;
+          multiplexGum.reset.bind(multiplexGum)();
+        });
+      });
+
+    it("should not get stuck in recursion if the success callback calls 'reset'",
+      function() {
+        sandbox.stub(navigator, "originalGum");
+        navigator.originalGum.callsArgWith(1, {});
+
+        var calledOnce = false;
+        multiplexGum.getPermsAndCacheMedia(null, function() {
+          calledOnce = true;
+          multiplexGum.reset.bind(multiplexGum)();
+        }, function() {
+          if (calledOnce) {
+            sinon.assert.fail("reset should only be called once");
+          }
+          calledOnce = true;
+        });
+      });
+  });
+});
--- a/browser/components/loop/test/standalone/webapp_test.js
+++ b/browser/components/loop/test/standalone/webapp_test.js
@@ -8,26 +8,31 @@ var expect = chai.expect;
 var TestUtils = React.addons.TestUtils;
 
 describe("loop.webapp", function() {
   "use strict";
 
   var sharedModels = loop.shared.models,
       sharedViews = loop.shared.views,
       sharedUtils = loop.shared.utils,
+      standaloneMedia = loop.standaloneMedia,
       sandbox,
       notifications,
-      feedbackApiClient;
+      feedbackApiClient,
+      stubGetPermsAndCacheMedia;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     notifications = new sharedModels.NotificationCollection();
     feedbackApiClient = new loop.FeedbackAPIClient("http://invalid", {
       product: "Loop"
     });
+
+    stubGetPermsAndCacheMedia = sandbox.stub(
+      loop.standaloneMedia._MultiplexGum.prototype, "getPermsAndCacheMedia");
   });
 
   afterEach(function() {
     sandbox.restore();
   });
 
   describe("#init", function() {
     var conversationSetStub;
@@ -605,16 +610,29 @@ describe("loop.webapp", function() {
     it("should mount the Home view there is no loopToken", function() {
         var webappRootView = mountTestComponent();
 
         TestUtils.findRenderedComponentWithType(webappRootView,
           loop.webapp.HomeView);
     });
   });
 
+  describe("HomeView", function() {
+    it("should call loop.standaloneMedia.reset", function() {
+      var multiplexGum = new standaloneMedia._MultiplexGum();
+      standaloneMedia.setSingleton(multiplexGum);
+      sandbox.stub(standaloneMedia._MultiplexGum.prototype, "reset");
+
+      TestUtils.renderIntoDocument(loop.webapp.HomeView());
+
+      sinon.assert.calledOnce(multiplexGum.reset);
+      sinon.assert.calledWithExactly(multiplexGum.reset);
+    });
+  });
+
   describe("PendingConversationView", function() {
     var view, websocket, fakeAudio;
 
     beforeEach(function() {
       websocket = new loop.CallConnectionWebSocket({
         url: "wss://fake/",
         callId: "callId",
         websocketToken: "7b"
@@ -647,16 +665,28 @@ describe("loop.webapp", function() {
 
     describe("#_cancelOutgoingCall", function() {
       it("should inform the websocket to cancel the setup", function() {
         var button = view.getDOMNode().querySelector(".btn-cancel");
         React.addons.TestUtils.Simulate.click(button);
 
         sinon.assert.calledOnce(websocket.cancel);
       });
+
+      it("should call multiplexGum.reset to release the camera", function() {
+        var multiplexGum = new standaloneMedia._MultiplexGum();
+        standaloneMedia.setSingleton(multiplexGum);
+        sandbox.stub(standaloneMedia._MultiplexGum.prototype, "reset");
+
+        var button = view.getDOMNode().querySelector(".btn-cancel");
+        React.addons.TestUtils.Simulate.click(button);
+
+        sinon.assert.calledOnce(multiplexGum.reset);
+        sinon.assert.calledWithExactly(multiplexGum.reset);
+      });
     });
 
     describe("Events", function() {
       describe("progress:alerting", function() {
         it("should update the callstate to ringing", function () {
           websocket.trigger("progress:alerting");
 
           expect(view.state.callState).to.be.equal("ringing");
@@ -692,18 +722,37 @@ describe("loop.webapp", function() {
 
         view = React.addons.TestUtils.renderIntoDocument(
             loop.webapp.StartConversationView({
               conversation: conversation,
               notifications: notifications,
               client: standaloneClientStub
             })
         );
+
+        // default to succeeding with a null local media object
+        stubGetPermsAndCacheMedia.callsArgWith(1, {});
       });
 
+      it("should fire multiplexGum.reset when getPermsAndCacheMedia calls" +
+        " back an error",
+        function() {
+          var setupOutgoingCall = sinon.stub(conversation, "setupOutgoingCall");
+          var multiplexGum = new standaloneMedia._MultiplexGum();
+          standaloneMedia.setSingleton(multiplexGum);
+          sandbox.stub(standaloneMedia._MultiplexGum.prototype, "reset");
+          stubGetPermsAndCacheMedia.callsArgWith(2, "FAKE_ERROR");
+
+          var button = view.getDOMNode().querySelector(".btn-accept");
+          React.addons.TestUtils.Simulate.click(button);
+
+          sinon.assert.calledOnce(multiplexGum.reset);
+          sinon.assert.calledWithExactly(multiplexGum.reset);
+        });
+
       it("should start the audio-video conversation establishment process",
         function() {
           var setupOutgoingCall = sinon.stub(conversation, "setupOutgoingCall");
 
           var button = view.getDOMNode().querySelector(".btn-accept");
           React.addons.TestUtils.Simulate.click(button);
 
           sinon.assert.calledOnce(setupOutgoingCall);
@@ -997,16 +1046,19 @@ describe("loop.webapp", function() {
 
         view = React.addons.TestUtils.renderIntoDocument(
             loop.webapp.StartConversationView({
               conversation: conversation,
               notifications: notifications,
               client: standaloneClientStub
             })
         );
+
+        // default to succeeding with a null local media object
+        stubGetPermsAndCacheMedia.callsArgWith(1, {});
       });
 
       it("should start the conversation establishment process", function() {
         var button = view.getDOMNode().querySelector("button");
         React.addons.TestUtils.Simulate.click(button);
 
         sinon.assert.calledOnce(setupOutgoingCall);
       });
--- a/browser/components/loop/ui/index.html
+++ b/browser/components/loop/ui/index.html
@@ -21,16 +21,17 @@
     <script>
       window.OTProperties = {
         cdnURL: '../content/shared/libs/'
       };
       window.OTProperties.assetURL = window.OTProperties.cdnURL + 'sdk-content/';
       window.OTProperties.configURL = window.OTProperties.assetURL + 'js/dynamic_config.min.js';
       window.OTProperties.cssURL = window.OTProperties.assetURL + 'css/ot.css';
     </script>
+    <script src="../content/js/multiplexGum.js"></script>
     <script src="../content/shared/libs/sdk.js"></script>
     <script src="../content/shared/libs/react-0.11.2.js"></script>
     <script src="../content/shared/libs/jquery-2.1.0.js"></script>
     <script src="../content/shared/libs/lodash-2.4.1.js"></script>
     <script src="../content/shared/libs/backbone-1.1.2.js"></script>
     <script src="../content/shared/js/feedbackApiClient.js"></script>
     <script src="../content/shared/js/actions.js"></script>
     <script src="../content/shared/js/utils.js"></script>
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -408,24 +408,62 @@ BrowserGlue.prototype = {
           return;
         Services.obs.removeObserver(this, "browser-search-service");
         this._syncSearchEngines();
         break;
 #ifdef NIGHTLY_BUILD
       case "nsPref:changed":
         if (data == POLARIS_ENABLED) {
           let enabled = Services.prefs.getBoolPref(POLARIS_ENABLED);
-          Services.prefs.setBoolPref("privacy.donottrackheader.enabled", enabled);
-          Services.prefs.setBoolPref("privacy.trackingprotection.enabled", enabled);
-          Services.prefs.setBoolPref("privacy.trackingprotection.ui.enabled", enabled);
+          if (enabled) {
+            let e10sEnabled = Services.appinfo.browserTabsRemoteAutostart;
+            let shouldRestart = e10sEnabled && this._promptForE10sRestart();
+            // Only set the related prefs if e10s is not enabled or the user
+            // saw a notification that e10s would be disabled on restart.
+            if (!e10sEnabled || shouldRestart) {
+              Services.prefs.setBoolPref("privacy.donottrackheader.enabled", enabled);
+              Services.prefs.setBoolPref("privacy.trackingprotection.enabled", enabled);
+              Services.prefs.setBoolPref("privacy.trackingprotection.ui.enabled", enabled);
+              if (shouldRestart) {
+                Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit |
+                                      Ci.nsIAppStartup.eRestart);
+              }
+            } else {
+              // The user chose not to disable E10s which is temporarily
+              // incompatible with Polaris.
+              Services.prefs.clearUserPref(POLARIS_ENABLED);
+            }
+          } else {
+            // Don't reset DNT because its visible pref is independent of
+            // Polaris and may have been previously set.
+            Services.prefs.clearUserPref("privacy.trackingprotection.enabled");
+            Services.prefs.clearUserPref("privacy.trackingprotection.ui.enabled");
+          }
         }
 #endif
     }
   },
 
+  _promptForE10sRestart: function () {
+    let win = this.getMostRecentBrowserWindow();
+    let brandBundle = win.document.getElementById("bundle_brand");
+    let brandName = brandBundle.getString("brandShortName");
+    let prefBundle = win.document.getElementById("bundle_preferences");
+    let msg = "Multiprocess Nightly (e10s) does not yet support tracking protection. Multiprocessing will be disabled if you restart Firefox. Would you like to continue?";
+    let title = prefBundle.getFormattedString("shouldRestartTitle", [brandName]);
+    let shouldRestart = Services.prompt.confirm(win, title, msg);
+    if (shouldRestart) {
+      let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+                       .createInstance(Ci.nsISupportsPRBool);
+      Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+      shouldRestart = !cancelQuit.data;
+    }
+    return shouldRestart;
+  },
+
   _syncSearchEngines: function () {
     // Only do this if the search service is already initialized. This function
     // gets called in finalUIStartup and from a browser-search-service observer,
     // to catch both cases (search service initialization occurring before and
     // after final-ui-startup)
     if (Services.search.isInitialized) {
       Services.search.defaultEngine = Services.search.currentEngine;
     }
--- a/browser/components/test/browser.ini
+++ b/browser/components/test/browser.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 
 [browser_bug538331.js]
 skip-if = e10s # Bug ?????? - child process crash, but only when run as part of the suite (ie, probably not actually this tests fault!?)
 
 [browser_polaris_prefs.js]
+skip-if = e10s # Bug 1089774 - Tracking protection and e10s are incompatible.
--- a/browser/components/test/browser_polaris_prefs.js
+++ b/browser/components/test/browser_polaris_prefs.js
@@ -40,17 +40,22 @@ add_task(function* test_changing_pref_ch
   if (!isNightly()) {
     ok(true, "Skipping test, not Nightly")
     return;
   }
   function* testPref(pref) {
     Services.prefs.setBoolPref(POLARIS_ENABLED, true);
     yield assertPref(pref, true);
     Services.prefs.setBoolPref(POLARIS_ENABLED, false);
-    yield assertPref(pref, false);
+    // We don't clear the DNT pref if Polaris is disabled.
+    if (pref != PREF_DNT) {
+      yield assertPref(pref, false);
+    } else {
+      yield assertPref(pref, true);
+    }
     Services.prefs.setBoolPref(POLARIS_ENABLED, true);
     yield assertPref(pref, true);
   }
   yield testPrefs(testPref);
 });
 
 add_task(function* test_prefs_can_be_changed_individually() {
   if (!isNightly()) {
@@ -58,16 +63,18 @@ add_task(function* test_prefs_can_be_cha
     return;
   }
   function* testPref(pref) {
     Services.prefs.setBoolPref(POLARIS_ENABLED, true);
     yield assertPref(pref, true);
     Services.prefs.setBoolPref(pref, false);
     yield assertPref(pref, false);
     yield assertPref(POLARIS_ENABLED, true);
+
     Services.prefs.setBoolPref(POLARIS_ENABLED, false);
     yield assertPref(pref, false);
+
     Services.prefs.setBoolPref(pref, true);
     yield assertPref(pref, true);
     yield assertPref(POLARIS_ENABLED, false);
   }
   yield testPrefs(testPref);
 });
--- a/browser/devtools/webide/content/prefs.js
+++ b/browser/devtools/webide/content/prefs.js
@@ -14,17 +14,17 @@ window.addEventListener("load", function
     let pref = i.dataset.pref;
     Services.prefs.addObserver(pref, FillForm, false);
     i.addEventListener("change", SaveForm, false);
   }
 
   // Buttons
   document.querySelector("#close").onclick = CloseUI;
   document.querySelector("#restore").onclick = RestoreDefaults;
-  document.querySelector("#manageSimulators").onclick = ShowAddons;
+  document.querySelector("#manageComponents").onclick = ShowAddons;
 
   // Initialize the controls
   FillForm();
 
 }, true);
 
 window.addEventListener("unload", function onUnload() {
   window.removeEventListener("unload", onUnload);
--- a/browser/devtools/webide/content/prefs.xhtml
+++ b/browser/devtools/webide/content/prefs.xhtml
@@ -15,17 +15,17 @@
     <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://webide/skin/prefs.css" type="text/css"/>
     <script type="application/javascript;version=1.8" src="chrome://webide/content/prefs.js"></script>
   </head>
   <body>
 
     <div id="controls">
       <a id="restore">&prefs_restore;</a>
-      <a id="manageSimulators">&prefs_simulators;</a>
+      <a id="manageComponents">&prefs_manage_components;</a>
       <a id="close">&deck_close;</a>
     </div>
 
     <h1>&prefs_title;</h1>
 
     <h2>&prefs_general_title;</h2>
 
     <ul>
--- a/browser/devtools/webide/content/webide.xul
+++ b/browser/devtools/webide/content/webide.xul
@@ -63,16 +63,17 @@
         <menuseparator/>
         <menuitem command="cmd_play" key="key_play" label="&projectMenu_play_label;" accesskey="&projectMenu_play_accesskey;"/>
         <menuitem command="cmd_stop" accesskey="&projectMenu_stop_accesskey;"/>
         <menuitem command="cmd_toggleToolbox" key="key_toggleToolbox" label="&projectMenu_debug_label;" accesskey="&projectMenu_debug_accesskey;"/>
         <menuseparator/>
         <menuitem command="cmd_removeProject" accesskey="&projectMenu_remove_accesskey;"/>
         <menuseparator/>
         <menuitem command="cmd_showPrefs" label="&projectMenu_showPrefs_label;" accesskey="&projectMenu_showPrefs_accesskey;"/>
+        <menuitem command="cmd_showAddons" label="&projectMenu_manageComponents_label;" accesskey="&projectMenu_manageComponents_accesskey;"/>
       </menupopup>
     </menu>
 
     <menu id="menu-runtime" label="&runtimeMenu_label;" accesskey="&runtimeMenu_accesskey;">
       <menupopup id="menu-runtime-popup">
         <menuitem command="cmd_showMonitor" accesskey="&runtimeMenu_showMonitor_accesskey;"/>
         <menuitem command="cmd_takeScreenshot" accesskey="&runtimeMenu_takeScreenshot_accesskey;"/>
         <menuitem command="cmd_showPermissionsTable" accesskey="&runtimeMenu_showPermissionTable_accesskey;"/>
@@ -80,17 +81,16 @@
         <menuseparator/>
         <menuitem command="cmd_disconnectRuntime" accesskey="&runtimeMenu_disconnect_accesskey;"/>
       </menupopup>
     </menu>
 
     <menu id="menu-view" label="&viewMenu_label;" accesskey="&viewMenu_accesskey;">
       <menupopup id="menu-ViewPopup">
         <menuitem command="cmd_toggleEditor" key="key_toggleEditor" accesskey="&viewMenu_toggleEditor_accesskey;"/>
-        <menuitem command="cmd_showAddons" label="&viewMenu_showAddons_label;" accesskey="&viewMenu_showAddons_accesskey;"/>
       </menupopup>
     </menu>
 
   </menubar>
 
   <keyset id="mainKeyset">
     <key key="&key_quit;" id="key_quit" command="cmd_quit" modifiers="accel"/>
     <key key="&key_showProjectPanel;" id="key_showProjectPanel" command="cmd_showProjectPanel" modifiers="accel"/>
--- a/browser/locales/en-US/chrome/browser/devtools/webide.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/webide.dtd
@@ -19,16 +19,18 @@
 <!ENTITY projectMenu_stop_label "Stop App">
 <!ENTITY projectMenu_stop_accesskey "S">
 <!ENTITY projectMenu_debug_label "Debug App">
 <!ENTITY projectMenu_debug_accesskey "D">
 <!ENTITY projectMenu_remove_label "Remove Project">
 <!ENTITY projectMenu_remove_accesskey "R">
 <!ENTITY projectMenu_showPrefs_label "Preferences">
 <!ENTITY projectMenu_showPrefs_accesskey "e">
+<!ENTITY projectMenu_manageComponents_label "Manage Extra Components">
+<!ENTITY projectMenu_manageComponents_accesskey "M">
 
 <!ENTITY runtimeMenu_label "Runtime">
 <!ENTITY runtimeMenu_accesskey "R">
 <!ENTITY runtimeMenu_disconnect_label "Disconnect">
 <!ENTITY runtimeMenu_disconnect_accesskey "D">
 <!ENTITY runtimeMenu_showPermissionTable_label "Permissions Table">
 <!ENTITY runtimeMenu_showPermissionTable_accesskey "P">
 <!ENTITY runtimeMenu_takeScreenshot_label "Screenshot">
@@ -37,18 +39,16 @@
 <!ENTITY runtimeMenu_showDetails_accesskey "E">
 <!ENTITY runtimeMenu_showMonitor_label "Monitor">
 <!ENTITY runtimeMenu_showMonitor_accesskey "M">
 
 <!ENTITY viewMenu_label "View">
 <!ENTITY viewMenu_accesskey "V">
 <!ENTITY viewMenu_toggleEditor_label "Toggle Editor">
 <!ENTITY viewMenu_toggleEditor_accesskey "E">
-<!ENTITY viewMenu_showAddons_label "Manage Simulators">
-<!ENTITY viewMenu_showAddons_accesskey "M">
 
 <!ENTITY projectButton_label "Open App">
 <!ENTITY runtimeButton_label "Select Runtime">
 
 <!-- We try to repicate Firefox' bindings: -->
 <!-- quit app -->
 <!ENTITY key_quit "W">
 <!-- open menu -->
@@ -95,17 +95,17 @@
 <!ENTITY addons_title "Extra Components">
 <!ENTITY addons_aboutaddons "Open Add-ons Manager">
 
 <!-- Prefs -->
 <!ENTITY prefs_title "Preferences">
 <!ENTITY prefs_editor_title "Editor">
 <!ENTITY prefs_general_title "General">
 <!ENTITY prefs_restore "Restore Defaults">
-<!ENTITY prefs_simulators "Manage Simulators">
+<!ENTITY prefs_manage_components "Manage Extra Components">
 <!ENTITY prefs_options_rememberlastproject "Remember last project">
 <!ENTITY prefs_options_rememberlastproject_tooltip "Restore previous project when WebIDE starts">
 <!ENTITY prefs_options_templatesurl "Templates URL">
 <!ENTITY prefs_options_templatesurl_tooltip "Index of available templates">
 <!ENTITY prefs_options_showeditor "Show editor">
 <!ENTITY prefs_options_showeditor_tooltip "Show internal editor">
 <!ENTITY prefs_options_tabsize "Tab size">
 <!ENTITY prefs_options_expandtab "Soft tabs">
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -91,16 +91,17 @@ this.ContentSearch = {
   _destroyedPromise: null,
 
   init: function () {
     Cc["@mozilla.org/globalmessagemanager;1"].
       getService(Ci.nsIMessageListenerManager).
       addMessageListener(INBOUND_MESSAGE, this);
     Services.obs.addObserver(this, "browser-search-engine-modified", false);
     Services.obs.addObserver(this, "shutdown-leaks-before-check", false);
+    this._stringBundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
   },
 
   destroy: function () {
     if (this._destroyedPromise) {
       return this._destroyedPromise;
     }
 
     Cc["@mozilla.org/globalmessagemanager;1"].
@@ -386,18 +387,21 @@ this.ContentSearch = {
     return state;
   }),
 
   _currentEngineObj: Task.async(function* () {
     let engine = Services.search.currentEngine;
     let favicon = engine.getIconURLBySize(16, 16);
     let uri1x = engine.getIconURLBySize(65, 26);
     let uri2x = engine.getIconURLBySize(130, 52);
+    let placeholder = this._stringBundle.formatStringFromName(
+      "searchWithEngine", [engine.name], 1);
     let obj = {
       name: engine.name,
+      placeholder: placeholder,
       iconBuffer: yield this._arrayBufferFromDataURI(favicon),
       logoBuffer: yield this._arrayBufferFromDataURI(uri1x),
       logo2xBuffer: yield this._arrayBufferFromDataURI(uri2x),
     };
     return obj;
   }),
 
   _arrayBufferFromDataURI: function (uri) {
--- a/browser/modules/test/browser_ContentSearch.js
+++ b/browser/modules/test/browser_ContentSearch.js
@@ -383,18 +383,20 @@ let currentStateObj = Task.async(functio
   return state;
 });
 
 let currentEngineObj = Task.async(function* () {
   let engine = Services.search.currentEngine;
   let uri1x = engine.getIconURLBySize(65, 26);
   let uri2x = engine.getIconURLBySize(130, 52);
   let uriFavicon = engine.getIconURLBySize(16, 16);
+  let bundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
   return {
     name: engine.name,
+    placeholder: bundle.formatStringFromName("searchWithEngine", [engine.name], 1),
     logoBuffer: yield arrayBufferFromDataURI(uri1x),
     logo2xBuffer: yield arrayBufferFromDataURI(uri2x),
     iconBuffer: yield arrayBufferFromDataURI(uriFavicon),
   };
 });
 
 function arrayBufferFromDataURI(uri) {
   if (!uri) {
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1408,16 +1408,17 @@ toolbar .toolbarbutton-1 > .toolbarbutto
     -moz-image-region: rect(0px, 160px, 32px, 128px);
   }
 
   #zoom-controls@inAnyPanel@ > #zoom-in-button,
   toolbarpaletteitem[place="palette"] > #zoom-controls > #zoom-in-button {
     -moz-image-region: rect(0px, 192px, 32px, 160px);
   }
 
+  #PanelUI-update-status > .toolbarbutton-icon,
   #PanelUI-fxa-status > .toolbarbutton-icon,
   #PanelUI-quit > .toolbarbutton-icon,
   #PanelUI-customize > .toolbarbutton-icon,
   #PanelUI-help > .toolbarbutton-icon {
     width: 16px;
   }
 
   #add-share-provider {
--- a/browser/themes/osx/customizableui/panelUIOverlay.css
+++ b/browser/themes/osx/customizableui/panelUIOverlay.css
@@ -17,16 +17,20 @@
   }
 
   #PanelUI-help[panel-multiview-anchor="true"]:-moz-locale-dir(rtl)::after,
   toolbarbutton[panel-multiview-anchor="true"]:-moz-locale-dir(rtl) {
     background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted-rtl@2x.png),
                       linear-gradient(rgba(255,255,255,0.3), transparent);
   }
 
+  #PanelUI-update-status {
+    list-style-image: url(chrome://branding/content/icon32.png);
+  }
+
   #PanelUI-fxa-status {
     list-style-image: url(chrome://browser/skin/sync-horizontalbar@2x.png);
   }
 
   #PanelUI-fxa-status[status="active"] {
     list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar@2x.png);
   }
 
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -376,16 +376,17 @@ toolbaritem[cui-areatype="menu-panel"][s
 
 #PanelUI-multiView[viewtype="subview"] > .panel-viewcontainer > .panel-viewstack > .panel-mainview >  #PanelUI-mainView {
   background-color: hsla(210,4%,10%,.1);
 }
 
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-contents-scroller > #PanelUI-contents > .panel-wide-item,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-contents-scroller > #PanelUI-contents > .toolbarbutton-1:not([panel-multiview-anchor="true"]),
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-fxa-status,
+#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-update-status,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-inner > toolbarseparator,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-inner > #PanelUI-customize,
 #PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-inner > #PanelUI-help:not([panel-multiview-anchor="true"]) {
   opacity: .5;
 }
 
 /*
  * XXXgijs: this is a workaround for a layout issue that was caused by these iframes,
@@ -488,73 +489,88 @@ toolbarpaletteitem[place="palette"] > to
   margin: 7px 0 7px;
   -moz-appearance: none;
 }
 
 #PanelUI-footer-inner:hover > toolbarseparator {
   margin: 0;
 }
 
+#PanelUI-update-status,
 #PanelUI-help,
 #PanelUI-fxa-status,
 #PanelUI-customize,
 #PanelUI-quit {
   margin: 0;
   padding: 11px 0;
   box-sizing: border-box;
   min-height: 40px;
   -moz-appearance: none;
   box-shadow: none;
   border: none;  
   border-radius: 0;
   transition: background-color;
   -moz-box-orient: horizontal;
 }
 
+#PanelUI-update-status,
 #PanelUI-fxa-status {
   border-top: 1px solid hsla(210,4%,10%,.14);
   border-bottom: 1px solid transparent;
   margin-bottom: -1px;
 }
 
+#PanelUI-update-status > .toolbarbutton-text,
 #PanelUI-fxa-status > .toolbarbutton-text {
   width: 0; /* Fancy cropping solution for flexbox. */
 }
 
 #PanelUI-help,
 #PanelUI-quit {
   min-width: 46px;
 }
 
+#PanelUI-update-status > .toolbarbutton-text,
 #PanelUI-fxa-status > .toolbarbutton-text,
 #PanelUI-customize > .toolbarbutton-text {
   margin: 0;
   padding: 0 6px;
   text-align: start;
 }
 
 #PanelUI-help > .toolbarbutton-text,
 #PanelUI-quit > .toolbarbutton-text {
   display: none;
 }
 
+#PanelUI-update-status > .toolbarbutton-icon,
 #PanelUI-fxa-status > .toolbarbutton-icon,
 #PanelUI-customize > .toolbarbutton-icon,
 #PanelUI-help > .toolbarbutton-icon,
 #PanelUI-quit > .toolbarbutton-icon {
   -moz-margin-end: 0;
 }
 
 #PanelUI-fxa-status,
 #PanelUI-customize {
   flex: 1;
   -moz-padding-start: 15px;
   -moz-border-start-style: none;
 }
 
+#PanelUI-update-status {
+  width: calc(@menuPanelWidth@ + 30px);
+  -moz-padding-start: 15px;
+  -moz-border-start-style: none;
+}
+
+#PanelUI-update-status {
+  list-style-image: url(chrome://branding/content/icon16.png);
+}
+
 #PanelUI-fxa-status {
   list-style-image: url(chrome://browser/skin/sync-horizontalbar.png);
 }
 
 #PanelUI-fxa-status[status="active"] {
   list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar.png);
 }
 
@@ -620,16 +636,42 @@ toolbarpaletteitem[place="palette"] > to
   box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
 }
 
 #PanelUI-fxa-status:not([disabled]):hover,
 #PanelUI-fxa-status:not([disabled]):hover:active {
   outline: none;
 }
 
+#PanelUI-update-status[update-status="succeeded"] {
+  background-color: hsla(96, 65%, 75%, 0.1);
+}
+
+#PanelUI-update-status[update-status="succeeded"]:not([disabled]):hover {
+  background-color: hsla(96, 65%, 75%, 0.4);
+}
+
+#PanelUI-update-status[update-status="succeeded"]:not([disabled]):hover:active {
+  background-color: hsla(96, 65%, 75%, 0.6);
+  box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
+}
+
+#PanelUI-update-status[update-status="failed"] {
+  background-color: hsla(359, 69%, 84%, 0.1);
+}
+
+#PanelUI-update-status[update-status="failed"]:not([disabled]):hover {
+  background-color: hsla(359, 69%, 84%, 0.4);
+}
+
+#PanelUI-update-status[update-status="failed"]:not([disabled]):hover:active {
+  background-color: hsla(359, 69%, 84%, 0.6);
+  box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
+}
+
 #PanelUI-quit:not([disabled]):hover {
   background-color: #d94141;
   outline-color: #c23a3a;
 }
 
 #PanelUI-quit:not([disabled]):hover:active {
   background-color: #ad3434;
   outline-color: #992e2e;
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -407,36 +407,16 @@ case "$target" in
       AC_MSG_ERROR([The program aidl was not found.  Use --with-android-sdk={android-sdk-dir}.])
     fi
     if test -z "$ADB" -o "$ADB" = ":"; then
       AC_MSG_ERROR([The program adb was not found.  Use --with-android-sdk={android-sdk-dir}.])
     fi
     ;;
 esac
 
-MOZ_ARG_DISABLE_BOOL(android-include-fonts,
-[  --disable-android-include-fonts
-                          disable the inclusion of fonts into the final APK],
-    MOZ_ANDROID_EXCLUDE_FONTS=1)
-
-if test -n "$MOZ_ANDROID_EXCLUDE_FONTS"; then
-    AC_DEFINE(MOZ_ANDROID_EXCLUDE_FONTS, $MOZ_ANDROID_EXCLUDE_FONTS)
-    AC_SUBST(MOZ_ANDROID_EXCLUDE_FONTS)
-fi
-
-MOZ_ARG_ENABLE_BOOL(android-resource-constrained,
-[  --enable-android-resource-constrained
-                          exclude hi-res images and similar from the final APK],
-    MOZ_ANDROID_RESOURCE_CONSTRAINED=1)
-
-if test -n "$MOZ_ANDROID_RESOURCE_CONSTRAINED"; then
-    AC_DEFINE(MOZ_ANDROID_RESOURCE_CONSTRAINED, $MOZ_ANDROID_RESOURCE_CONSTRAINED)
-    AC_SUBST(MOZ_ANDROID_RESOURCE_CONSTRAINED)
-fi
-
 MOZ_ARG_WITH_STRING(android-min-sdk,
 [  --with-android-min-sdk=[VER]     Impose a minimum Firefox for Android SDK version],
 [ MOZ_ANDROID_MIN_SDK_VERSION=$withval ])
 
 MOZ_ARG_WITH_STRING(android-max-sdk,
 [  --with-android-max-sdk=[VER]     Impose a maximum Firefox for Android SDK version],
 [ MOZ_ANDROID_MAX_SDK_VERSION=$withval ])
 
--- a/configure.in
+++ b/configure.in
@@ -4088,16 +4088,40 @@ MOZ_ARG_WITH_STRING(bing-api-keyfile,
   MOZ_BING_API_KEY=`cat $withval | cut -f 2 -d " "`])
 if test -z "$MOZ_BING_API_CLIENTID"; then
     MOZ_BING_API_CLIENTID=no-bing-api-clientid
     MOZ_BING_API_KEY=no-bing-api-key
 fi
 AC_SUBST(MOZ_BING_API_CLIENTID)
 AC_SUBST(MOZ_BING_API_KEY)
 
+# Whether to include optional-but-large font files in the final APK.
+# We want this in mobile/android/confvars.sh, so it goes early.
+MOZ_ARG_DISABLE_BOOL(android-include-fonts,
+[  --disable-android-include-fonts
+                          Disable the inclusion of fonts into the final APK],
+    MOZ_ANDROID_EXCLUDE_FONTS=1)
+
+if test -n "$MOZ_ANDROID_EXCLUDE_FONTS"; then
+    AC_DEFINE(MOZ_ANDROID_EXCLUDE_FONTS)
+fi
+AC_SUBST(MOZ_ANDROID_EXCLUDE_FONTS)
+
+# Whether this APK is destined for resource constrained devices.
+# We want this in mobile/android/confvars.sh, so it goes early.
+MOZ_ARG_ENABLE_BOOL(android-resource-constrained,
+[  --enable-android-resource-constrained
+                          Exclude hi-res images and similar from the final APK],
+    MOZ_ANDROID_RESOURCE_CONSTRAINED=1)
+
+if test -n "$MOZ_ANDROID_RESOURCE_CONSTRAINED"; then
+    AC_DEFINE(MOZ_ANDROID_RESOURCE_CONSTRAINED)
+fi
+AC_SUBST(MOZ_ANDROID_RESOURCE_CONSTRAINED)
+
 # Allow the application to influence configure with a confvars.sh script.
 AC_MSG_CHECKING([if app-specific confvars.sh exists])
 if test -f "${srcdir}/${MOZ_BUILD_APP}/confvars.sh" ; then
   AC_MSG_RESULT([${srcdir}/${MOZ_BUILD_APP}/confvars.sh])
   . "${srcdir}/${MOZ_BUILD_APP}/confvars.sh"
 else
   AC_MSG_RESULT([no])
 fi
--- a/dom/apps/Webapps.jsm
+++ b/dom/apps/Webapps.jsm
@@ -446,16 +446,18 @@ this.DOMApplicationRegistry = {
     }
 
     // Install the permissions for this app, as if we were updating
     // to cleanup the old ones if needed.
     // TODO It's not clear what this should do when there are multiple profiles.
     if (supportUseCurrentProfile()) {
       this._readManifests([{ id: aId }]).then((aResult) => {
         let data = aResult[0];
+        this.webapps[aId].kind = this.webapps[aId].kind ||
+          this.appKind(this.webapps[aId], aResult[0].manifest);
         PermissionsInstaller.installPermissions({
           manifest: data.manifest,
           manifestURL: this.webapps[aId].manifestURL,
           origin: this.webapps[aId].origin,
           isPreinstalled: aIsPreinstalled,
           isSystemUpdate: aIsSystemUpdate,
           kind: this.webapps[aId].kind
         }, true, function() {
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -36,16 +36,17 @@
 #include "nsIScriptTimeoutHandler.h"
 #include "nsIController.h"
 #include "nsScriptNameSpaceManager.h"
 #include "nsISlowScriptDebug.h"
 #include "nsWindowMemoryReporter.h"
 #include "WindowNamedPropertiesHandler.h"
 #include "nsFrameSelection.h"
 #include "nsISelectionListener.h"
+#include "nsCaret.h"
 
 // Helper Classes
 #include "nsJSUtils.h"
 #include "jsapi.h"              // for JSAutoRequest
 #include "jswrapper.h"
 #include "nsReadableUtils.h"
 #include "nsDOMClassInfo.h"
 #include "nsJSEnvironment.h"
@@ -9327,16 +9328,48 @@ CheckReason(int16_t aReason, SelectionCh
       return aReason & nsISelectionListener::COLLAPSETOSTART_REASON;
     case SelectionChangeReason::Collapsetoend:
       return aReason & nsISelectionListener::COLLAPSETOEND_REASON;
     default:
       return false;
   }
 }
 
+static nsRect
+GetSelectionBoundingRect(Selection* aSel, nsIPresShell* aShell)
+{
+  nsRect res;
+  // Bounding client rect may be empty after calling GetBoundingClientRect
+  // when range is collapsed. So we get caret's rect when range is
+  // collapsed.
+  if (aSel->IsCollapsed()) {
+    aShell->FlushPendingNotifications(Flush_Layout);
+    nsIFrame* frame = nsCaret::GetGeometry(aSel, &res);
+    if (frame) {
+      nsIFrame* relativeTo =
+        nsLayoutUtils::GetContainingBlockForClientRect(frame);
+      res = nsLayoutUtils::TransformFrameRectToAncestor(frame, res, relativeTo);
+    }
+  } else {
+    int32_t rangeCount = aSel->GetRangeCount();
+    nsLayoutUtils::RectAccumulator accumulator;
+    for (int32_t idx = 0; idx < rangeCount; ++idx) {
+      nsRange* range = aSel->GetRangeAt(idx);
+      nsRange::CollectClientRects(&accumulator, range,
+                                  range->GetStartParent(), range->StartOffset(),
+                                  range->GetEndParent(), range->EndOffset(),
+                                  true, false);
+    }
+    res = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect :
+      accumulator.mResultRect;
+  }
+
+  return res;
+}
+
 NS_IMETHODIMP
 nsGlobalWindow::UpdateCommands(const nsAString& anAction, nsISelection* aSel, int16_t aReason)
 {
   nsPIDOMWindow *rootWindow = nsGlobalWindow::GetPrivateRoot();
   if (!rootWindow)
     return NS_OK;
 
   nsCOMPtr<nsIDOMXULDocument> xulDoc =
@@ -9372,27 +9405,17 @@ nsGlobalWindow::UpdateCommands(const nsA
       // Dispatch selection change events when touch caret is visible even if selection
       // is collapsed because it could be the shortcut mode, otherwise ignore this
       // UpdateCommands
       if (isCollapsed && !isTouchCaretVisible) {
         return NS_OK;
       }
 
       Selection* selection = static_cast<Selection*>(aSel);
-      int32_t rangeCount = selection->GetRangeCount();
-      nsLayoutUtils::RectAccumulator accumulator;
-      for (int32_t idx = 0; idx < rangeCount; ++idx) {
-        nsRange* range = selection->GetRangeAt(idx);
-        nsRange::CollectClientRects(&accumulator, range,
-                                    range->GetStartParent(), range->StartOffset(),
-                                    range->GetEndParent(), range->EndOffset(),
-                                    true, false);
-      }
-      nsRect rect = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect :
-        accumulator.mResultRect;
+      nsRect rect = GetSelectionBoundingRect(selection, mDoc->GetShell());
       nsRefPtr<DOMRect> domRect = new DOMRect(ToSupports(this));
       domRect->SetLayoutRect(rect);
       init.mBoundingClientRect = domRect;
 
       selection->Stringify(init.mSelectedText);
       for (uint32_t reasonType = 0;
            reasonType < static_cast<uint32_t>(SelectionChangeReason::EndGuard_);
            ++reasonType) {
--- a/dom/nfc/NfcContentHelper.js
+++ b/dom/nfc/NfcContentHelper.js
@@ -128,17 +128,17 @@ NfcContentHelper.prototype = {
       throw Components.Exception("No session token!",
                                   Cr.NS_ERROR_UNEXPECTED);
       return false;
     }
     // Report session to Nfc.js only.
     let val = cpmm.sendSyncMessage("NFC:CheckSessionToken", {
       sessionToken: sessionToken
     });
-    return (val[0] === NFC.NFC_SUCCESS);
+    return (val[0] === NFC.NFC_GECKO_SUCCESS);
   },
 
   // NFCTag interface
   readNDEF: function readNDEF(sessionToken) {
     let request = Services.DOMRequest.createRequest(this._window);
     let requestId = btoa(this.getRequestId(request));
     this._requestMap[requestId] = this._window;
 
--- a/dom/nfc/gonk/Nfc.js
+++ b/dom/nfc/gonk/Nfc.js
@@ -309,19 +309,19 @@ XPCOMUtils.defineLazyGetter(this, "gMess
 
       switch (message.name) {
         case "NFC:AddEventTarget":
           this.addEventTarget(message.target);
           return null;
         case "NFC:CheckSessionToken":
           if (!SessionHelper.isValidToken(message.data.sessionToken)) {
             debug("Received invalid Session Token: " + message.data.sessionToken);
-            return NFC.NFC_ERROR_BAD_SESSION_ID;
+            return NFC.NFC_GECKO_ERROR_BAD_SESSION_ID;
           }
-          return NFC.NFC_SUCCESS;
+          return NFC.NFC_GECKO_SUCCESS;
         case "NFC:RegisterPeerReadyTarget":
           this.registerPeerReadyTarget(message.target, message.data.appId);
           return null;
         case "NFC:UnregisterPeerReadyTarget":
           this.unregisterPeerReadyTarget(message.data.appId);
           return null;
         case "NFC:CheckP2PRegistration":
           this.checkP2PRegistration(message);
@@ -508,21 +508,16 @@ Nfc.prototype = {
 
   /**
    * Process the incoming message from the NFC Service.
    */
   onEvent: function onEvent(event) {
     let message = Cu.cloneInto(event, this);
     DEBUG && debug("Received message from NFC Service: " + JSON.stringify(message));
 
-    // mapping error code to error message
-    if (message.status !== undefined && message.status !== NFC.NFC_SUCCESS) {
-      message.errorMsg = this.getErrorMessage(message.status);
-    }
-
     switch (message.type) {
       case "InitializedNotification":
         // Do nothing.
         break;
       case "TechDiscoveredNotification":
         message.type = "techDiscovered";
         // Update the upper layers with a session token (alias)
         message.sessionToken =
@@ -556,17 +551,17 @@ Nfc.prototype = {
         delete message.sessionId;
 
         gSystemMessenger.broadcastMessage("nfc-manager-tech-lost", message);
         break;
      case "HCIEventTransactionNotification":
         this.notifyHCIEventTransaction(message);
         break;
      case "ConfigResponse":
-        if (message.status === NFC.NFC_SUCCESS) {
+        if (!message.errorMsg) {
           this.powerLevel = message.powerLevel;
         }
 
         this.sendNfcResponse(message);
         break;
       case "ConnectResponse": // Fall through.
       case "CloseResponse":
       case "ReadNDEFResponse":
@@ -612,17 +607,17 @@ Nfc.prototype = {
         debug("NFC is not enabled. current powerLevel:" + this.powerLevel);
         this.sendNfcErrorResponse(message, NFC.NFC_GECKO_ERROR_NOT_ENABLED);
         return null;
       }
 
       // Sanity check on sessionToken.
       if (!SessionHelper.isValidToken(message.data.sessionToken)) {
         debug("Invalid Session Token: " + message.data.sessionToken);
-        this.sendNfcErrorResponse(message, NFC.NFC_ERROR_BAD_SESSION_ID);
+        this.sendNfcErrorResponse(message, NFC.NFC_GECKO_ERROR_BAD_SESSION_ID);
         return null;
       }
 
       // Update the current sessionId before sending to the NFC service.
       message.data.sessionId = SessionHelper.getId(message.data.sessionToken);
     }
 
     switch (message.name) {
--- a/dom/nfc/gonk/NfcGonkMessage.h
+++ b/dom/nfc/gonk/NfcGonkMessage.h
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef NfcGonkMessage_h
 #define NfcGonkMessage_h
 
 namespace mozilla {
 
 #define NFCD_MAJOR_VERSION 1
-#define NFCD_MINOR_VERSION 13
+#define NFCD_MINOR_VERSION 14
 
 enum NfcRequest {
   ConfigReq = 0,
   ConnectReq,
   CloseReq,
   ReadNDEFReq,
   WriteNDEFReq,
   MakeReadOnlyNDEFReq,
@@ -36,47 +36,34 @@ enum NfcTechlogy {
   NDEF = 0,
   NDEFWritable,
   NDEFFormattable,
   P2P,
 };
 
 enum NfcErrorCode {
   Success = 0,
-  IOErr = -1,
-  Cancelled = -2,
-  Timeout = -3,
-  BusyErr = -4,
-  ConnectErr = -5,
-  DisconnectErr = -6,
-  ReadErr = -7,
-  WriteErr = -8,
-  InvalidParam = -9,
-  InsufficientResources = -10,
-  SocketCreation = -11,
-  SocketNotConnected = -12,
-  BufferTooSmall = -13,
-  SapUsed = -14,
-  ServiceNameUsed = -15,
-  SocketOptions = -16,
-  FailEnableDiscovery = -17,
-  FailDisableDiscovery = -18,
-  NotInitialized = -19,
-  InitializeFail = -20,
-  DeinitializeFail = -21,
-  SeConnected = -22,
-  NoSeConnected = -23,
-  NotSupported = -24,
-  BadSessionId = -25,
-  LostTech = -26,
-  BadTechType = -27,
-  SelectSeFail = -28,
-  DeselectSeFail = -29,
-  FailEnableLowPowerMode = -30,
-  FailDisableLowPowerMode = -31,
+  IOErr = 1,
+  Timeout = 2,
+  BusyErr = 3,
+  ConnectErr = 4,
+  DisconnectErr = 5,
+  ReadErr = 6,
+  WriteErr = 7,
+  InvalidParam = 8,
+  InsufficientResources = 9,
+  SocketCreation = 10,
+  FailEnableDiscovery = 11,
+  FailDisableDiscovery = 12,
+  NotInitialized = 13,
+  InitializeFail = 14,
+  DeinitializeFail = 15,
+  NotSupported = 16,
+  FailEnableLowPowerMode = 17,
+  FailDisableLowPowerMode = 18,
 };
 
 enum SecureElementOrigin {
   SIM = 0,
   ESE = 1,
   ASSD = 2,
   OriginEndGuard = 3
 };
--- a/dom/nfc/gonk/NfcMessageHandler.cpp
+++ b/dom/nfc/gonk/NfcMessageHandler.cpp
@@ -124,17 +124,17 @@ NfcMessageHandler::GeneralResponse(const
       type = kCloseResponse;
       break;
     default:
       CHROMIUM_LOG("Nfcd, unknown general response %d", pendingReq);
       return false;
   }
 
   aOptions.mType = NS_ConvertUTF8toUTF16(type);
-  aOptions.mStatus = aParcel.readInt32();
+  aOptions.mErrorCode = aParcel.readInt32();
   aOptions.mSessionId = aParcel.readInt32();
 
   NS_ENSURE_TRUE(!mRequestIdQueue.IsEmpty(), false);
   aOptions.mRequestId = mRequestIdQueue[0];
   mRequestIdQueue.RemoveElementAt(0);
   return true;
 }
 
@@ -147,18 +147,17 @@ NfcMessageHandler::ConfigRequest(Parcel&
   mPowerLevelQueue.AppendElement(aOptions.mPowerLevel);
   return true;
 }
 
 bool
 NfcMessageHandler::ConfigResponse(const Parcel& aParcel, EventOptions& aOptions)
 {
   aOptions.mType = NS_ConvertUTF8toUTF16(kConfigResponse);
-  aOptions.mStatus = aParcel.readInt32();
-
+  aOptions.mErrorCode = aParcel.readInt32();
   NS_ENSURE_TRUE(!mRequestIdQueue.IsEmpty(), false);
   aOptions.mRequestId = mRequestIdQueue[0];
   mRequestIdQueue.RemoveElementAt(0);
 
   NS_ENSURE_TRUE(!mPowerLevelQueue.IsEmpty(), false);
   aOptions.mPowerLevel = mPowerLevelQueue[0];
   mPowerLevelQueue.RemoveElementAt(0);
   return true;
@@ -172,24 +171,24 @@ NfcMessageHandler::ReadNDEFRequest(Parce
   mRequestIdQueue.AppendElement(aOptions.mRequestId);
   return true;
 }
 
 bool
 NfcMessageHandler::ReadNDEFResponse(const Parcel& aParcel, EventOptions& aOptions)
 {
   aOptions.mType = NS_ConvertUTF8toUTF16(kReadNDEFResponse);
-  aOptions.mStatus = aParcel.readInt32();
+  aOptions.mErrorCode = aParcel.readInt32();
   aOptions.mSessionId = aParcel.readInt32();
 
   NS_ENSURE_TRUE(!mRequestIdQueue.IsEmpty(), false);
   aOptions.mRequestId = mRequestIdQueue[0];
   mRequestIdQueue.RemoveElementAt(0);
 
-  if (aOptions.mStatus == NfcErrorCode::Success) {
+  if (aOptions.mErrorCode == NfcErrorCode::Success) {
     ReadNDEFMessage(aParcel, aOptions);
   }
 
   return true;
 }
 
 bool
 NfcMessageHandler::WriteNDEFRequest(Parcel& aParcel, const CommandOptions& aOptions)
--- a/dom/nfc/gonk/NfcOptions.h
+++ b/dom/nfc/gonk/NfcOptions.h
@@ -80,23 +80,24 @@ struct CommandOptions
   int32_t mTechType;
   bool mIsP2P;
   nsTArray<NDEFRecordStruct> mRecords;
 };
 
 struct EventOptions
 {
   EventOptions()
-    : mType(EmptyString()), mStatus(-1), mSessionId(-1), mRequestId(EmptyString()), mMajorVersion(-1), mMinorVersion(-1),
-      mIsReadOnly(-1), mCanBeMadeReadOnly(-1), mMaxSupportedLength(-1), mPowerLevel(-1),
-      mOriginType(-1), mOriginIndex(-1)
+    : mType(EmptyString()), mStatus(-1), mErrorCode(-1), mSessionId(-1), mRequestId(EmptyString()),
+      mMajorVersion(-1), mMinorVersion(-1), mIsReadOnly(-1), mCanBeMadeReadOnly(-1),
+      mMaxSupportedLength(-1), mPowerLevel(-1), mOriginType(-1), mOriginIndex(-1)
   {}
 
   nsString mType;
   int32_t mStatus;
+  int32_t mErrorCode;
   int32_t mSessionId;
   nsString mRequestId;
   int32_t mMajorVersion;
   int32_t mMinorVersion;
   nsTArray<uint8_t> mTechList;
   nsTArray<NDEFRecordStruct> mRecords;
   int32_t mIsReadOnly;
   int32_t mCanBeMadeReadOnly;
--- a/dom/nfc/gonk/NfcService.cpp
+++ b/dom/nfc/gonk/NfcService.cpp
@@ -101,16 +101,21 @@ public:
     COPY_FIELD(mType)
     COPY_OPT_FIELD(mRequestId, EmptyString())
     COPY_OPT_FIELD(mStatus, -1)
     COPY_OPT_FIELD(mSessionId, -1)
     COPY_OPT_FIELD(mMajorVersion, -1)
     COPY_OPT_FIELD(mMinorVersion, -1)
     COPY_OPT_FIELD(mPowerLevel, -1)
 
+    if (mEvent.mErrorCode != -1) {
+      event.mErrorMsg.Construct();
+      event.mErrorMsg.Value() = static_cast<NfcErrorMessage>(mEvent.mErrorCode);
+    }
+
     if (mEvent.mTechList.Length() > 0) {
       int length = mEvent.mTechList.Length();
       event.mTechList.Construct();
 
       if (!event.mTechList.Value().SetCapacity(length)) {
         return NS_ERROR_FAILURE;
       }
 
--- a/dom/nfc/gonk/nfc_consts.js
+++ b/dom/nfc/gonk/nfc_consts.js
@@ -17,68 +17,30 @@
 
 // Set to true to debug all NFC layers
 this.DEBUG_ALL = false;
 
 // Set individually to debug specific layers
 this.DEBUG_CONTENT_HELPER = false || DEBUG_ALL;
 this.DEBUG_NFC = false || DEBUG_ALL;
 
-// nfcd error codes
-this.NFC_SUCCESS = 0;
-this.NFC_ERROR_IO = -1;
-this.NFC_ERROR_TIMEOUT = -2;
-this.NFC_ERROR_BUSY = -3;
-this.NFC_ERROR_CONNECT = -4;
-this.NFC_ERROR_DISCONNECT = -5;
-this.NFC_ERROR_READ = -6;
-this.NFC_ERROR_WRITE = -7;
-this.NFC_ERROR_INVALID_PARAM = -8;
-this.NFC_ERROR_INSUFFICIENT_RESOURCES = -9;
-this.NFC_ERROR_SOCKET_CREATION = -10;
-this.NFC_ERROR_FAIL_ENABLE_DISCOVERY = -11;
-this.NFC_ERROR_FAIL_DISABLE_DISCOVERY = -12;
-this.NFC_ERROR_NOT_INITIALIZED = -13;
-this.NFC_ERROR_INITIALIZE_FAIL = -14;
-this.NFC_ERROR_DEINITIALIZE_FAIL = -15;
-this.NFC_ERROR_NOT_SUPPORTED = -16;
-this.NFC_ERROR_BAD_SESSION_ID = -17,
-this.NFC_ERROR_FAIL_ENABLE_LOW_POWER_MODE = -18;
-this.NFC_ERROR_FAIL_DISABLE_LOW_POWER_MODE = -19;
-
 // Gecko specific error codes
+this.NFC_GECKO_SUCCESS = 0;
 this.NFC_GECKO_ERROR_GENERIC_FAILURE = 1;
 this.NFC_GECKO_ERROR_P2P_REG_INVALID = 2;
 this.NFC_GECKO_ERROR_NOT_ENABLED = 3;
 this.NFC_GECKO_ERROR_SEND_FILE_FAILED = 4;
+this.NFC_GECKO_ERROR_BAD_SESSION_ID = 5;
 
 this.NFC_ERROR_MSG = {};
-this.NFC_ERROR_MSG[this.NFC_ERROR_IO] = "NfcIoError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_TIMEOUT] = "NfcTimeoutError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_BUSY] = "NfcBusyError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_CONNECT] = "NfcConnectError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_DISCONNECT] = "NfcDisconnectError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_READ] = "NfcReadError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_WRITE] = "NfcWriteError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_INVALID_PARAM] = "NfcInvalidParamError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_INSUFFICIENT_RESOURCES] = "NfcInsufficentResourcesError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_SOCKET_CREATION] = "NfcSocketCreationError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_FAIL_ENABLE_DISCOVERY] = "NfcFailEnableDiscoveryError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_FAIL_DISABLE_DISCOVERY] = "NfcFailDisableDiscoveryError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_NOT_INITIALIZED] = "NfcNotInitializedError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_INITIALIZE_FAIL] = "NfcInitializeFailError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_DEINITIALIZE_FAIL] = "NfcDeinitializeFailError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_NOT_SUPPORTED] = "NfcNotSupportedError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_BAD_SESSION_ID] = "NfcBadSessionIdError";
-this.NFC_ERROR_MSG[this.NFC_ERROR_FAIL_ENABLE_LOW_POWER_MODE] = "EnableLowPowerModeFail";
-this.NFC_ERROR_MSG[this.NFC_ERROR_FAIL_DISABLE_LOW_POWER_MODE] = "DisableLowPowerModeFail";
 this.NFC_ERROR_MSG[this.NFC_GECKO_ERROR_GENERIC_FAILURE] = "NfcGenericFailureError";
 this.NFC_ERROR_MSG[this.NFC_GECKO_ERROR_P2P_REG_INVALID] = "NfcP2PRegistrationInvalid";
 this.NFC_ERROR_MSG[this.NFC_GECKO_ERROR_NOT_ENABLED] = "NfcNotEnabledError";
 this.NFC_ERROR_MSG[this.NFC_GECKO_ERROR_SEND_FILE_FAILED] = "NfcSendFileFailed";
+this.NFC_ERROR_MSG[this.NFC_GECKO_ERROR_BAD_SESSION_ID] = "NfcBadSessionID";
 
 // NFC powerlevels must match config PDUs.
 this.NFC_POWER_LEVEL_UNKNOWN        = -1;
 this.NFC_POWER_LEVEL_DISABLED       = 0;
 this.NFC_POWER_LEVEL_LOW            = 1;
 this.NFC_POWER_LEVEL_ENABLED        = 2;
 
 this.TOPIC_MOZSETTINGS_CHANGED      = "mozsettings-changed";
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -3941,19 +3941,21 @@ RilObject.prototype = {
   },
 
   /**
    * Helpers for processing call state changes.
    */
   _processClassifiedCalls: function(removedCalls, remainedCalls, addedCalls,
                                     failCause) {
     // Handle removed calls.
+    // Only remove it from the map here. Notify callDisconnected later.
     for (let call of removedCalls) {
-      this._removeVoiceCall(call, call.hangUpLocal ?
-                            GECKO_CALL_ERROR_NORMAL_CALL_CLEARING : failCause);
+      delete this.currentCalls[call.callIndex];
+      call.failCause = call.hangUpLocal ? GECKO_CALL_ERROR_NORMAL_CALL_CLEARING
+                                        : failCause;
     }
 
     let changedCalls = new Set();
 
     // Handle remained calls.
     for (let newCall of remainedCalls) {
       let oldCall = this.currentCalls[newCall.callIndex];
       if (oldCall.state == newCall.state) {
@@ -3994,23 +3996,28 @@ RilObject.prototype = {
     for each (let call in this.currentCalls) {
       let isConference = conference.has(call);
       if (call.isConference != isConference) {
         call.isConference = isConference;
         changedCalls.add(call);
       }
     }
 
-    // Update audio state. We have to send the message before callstatechange
-    // to make sure that the audio state is ready first.
+    // Update audio state. We have to send this message before callStateChange
+    // and callDisconnected to make sure that the audio state is ready first.
     this.sendChromeMessage({
       rilMessageType: "audioStateChanged",
       state: this._detectAudioState()
     });
 
+    // Notify call disconnected.
+    for (let call of removedCalls) {
+      this._handleDisconnectedCall(call);
+    }
+
     // Notify call state change.
     for (let call of changedCalls) {
       this._handleChangedCallState(call);
     }
 
     // Notify conference state change.
     if (this.currentConferenceState != newConferenceState) {
       this.currentConferenceState = newConferenceState;
@@ -4063,22 +4070,16 @@ RilObject.prototype = {
   _addVoiceCall: function(newCall) {
     newCall.number = this._formatInternationalNumber(newCall.number, newCall.toa);
     newCall.isOutgoing = !(newCall.state == CALL_STATE_INCOMING);
     newCall.isConference = false;
 
     this.currentCalls[newCall.callIndex] = newCall;
   },
 
-  _removeVoiceCall: function(call, failCause) {
-    delete this.currentCalls[call.callIndex];
-    call.failCause = failCause;
-    this._handleDisconnectedCall(call);
-  },
-
   _handleChangedCallState: function(changedCall) {
     let message = {rilMessageType: "callStateChange",
                    call: changedCall};
     this.sendChromeMessage(message);
   },
 
   _handleDisconnectedCall: function(disconnectedCall) {
     let message = {rilMessageType: "callDisconnected",
--- a/dom/telephony/test/marionette/manifest.ini
+++ b/dom/telephony/test/marionette/manifest.ini
@@ -56,15 +56,14 @@ disabled = Bug 821958
 [test_conference_two_hangup_one.js]
 [test_conference_two_hold_resume.js]
 [test_conference_two_remove_one.js]
 [test_conference_three_hangup_one.js]
 [test_conference_three_remove_one.js]
 [test_conference_add_twice_error.js]
 [test_outgoing_when_two_calls_on_line.js]
 [test_call_presentation.js]
-[test_incomingcall_phonestate_speaker.js]
 [test_temporary_clir.js]
 [test_outgoing_error_state.js]
 [test_outgoing_auto_hold.js]
 [test_mmi.js]
 [test_mmi_change_pin.js]
 [test_mmi_call_forwarding.js]
--- a/dom/telephony/test/marionette/test_audiomanager_phonestate.js
+++ b/dom/telephony/test/marionette/test_audiomanager_phonestate.js
@@ -9,100 +9,88 @@ const AUDIO_MANAGER_CONTRACT_ID = "@mozi
 // See nsIAudioManager
 const PHONE_STATE_INVALID          = -2;
 const PHONE_STATE_CURRENT          = -1;
 const PHONE_STATE_NORMAL           = 0;
 const PHONE_STATE_RINGTONE         = 1;
 const PHONE_STATE_IN_CALL          = 2;
 const PHONE_STATE_IN_COMMUNICATION = 3;
 
-let audioManager;
-function checkStates(speakerEnabled, phoneState) {
-  if (!audioManager) {
-    audioManager = SpecialPowers.Cc[AUDIO_MANAGER_CONTRACT_ID]
+let audioManager = SpecialPowers.Cc[AUDIO_MANAGER_CONTRACT_ID]
                                 .getService(SpecialPowers.Ci.nsIAudioManager);
-    ok(audioManager, "nsIAudioManager instance");
-  }
+
+ok(audioManager, "nsIAudioManager instance");
 
-  is(telephony.speakerEnabled, speakerEnabled, "telephony.speakerEnabled");
-  if (phoneState == PHONE_STATE_CURRENT) {
-    ok(audioManager.phoneState === PHONE_STATE_CURRENT ||
-       audioManager.phoneState === PHONE_STATE_NORMAL, "audioManager.phoneState");
-  } else {
-    is(audioManager.phoneState, phoneState, "audioManager.phoneState");
-  }
-}
-
-function check(phoneStateOrig, phoneStateEnabled, phoneStateDisabled) {
-  checkStates(false, phoneStateOrig);
+function check(phoneState) {
+  return new Promise(function(resolve, reject) {
+    waitFor(function() {
+      resolve();
+    }, function() {
+      let currentPhoneState = audioManager.phoneState;
+      log("waiting.. audioState should change to " + phoneState +
+          ", current is" + currentPhoneState);
 
-  let canEnableSpeaker = arguments.length > 1;
-  telephony.speakerEnabled = true;
-  if (canEnableSpeaker) {
-    checkStates(true, phoneStateEnabled);
-  } else {
-    checkStates(false, phoneStateOrig);
-    return;
-  }
-
-  telephony.speakerEnabled = false;
-  checkStates(false, arguments.length > 2 ? phoneStateDisabled : phoneStateOrig);
+      return (phoneState == currentPhoneState ||
+              (phoneState == PHONE_STATE_CURRENT &&
+               currentPhoneState == PHONE_STATE_NORMAL));
+    });
+  });
 }
 
 // Start the test
 startTest(function() {
   let outNumber = "5555550101";
   let inNumber  = "5555550201";
   let outCall;
   let inCall;
 
   Promise.resolve()
-    .then(() => check(PHONE_STATE_CURRENT, PHONE_STATE_NORMAL, PHONE_STATE_NORMAL))
+    .then(() => check(PHONE_STATE_CURRENT))
 
     // Dial in
     .then(() => gRemoteDial(inNumber))
     .then(call => { inCall = call; })
-    .then(() => check(PHONE_STATE_RINGTONE, PHONE_STATE_RINGTONE, PHONE_STATE_RINGTONE))
+    .then(() => check(PHONE_STATE_RINGTONE))
     .then(() => gAnswer(inCall))
-    .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
+    .then(() => check(PHONE_STATE_IN_CALL))
     // Hang up all
     .then(() => gRemoteHangUp(inCall))
-    .then(() => check(PHONE_STATE_NORMAL, PHONE_STATE_NORMAL))
+    .then(() => check(PHONE_STATE_NORMAL))
 
     // Dial out
     .then(() => gDial(outNumber))
     .then(call => { outCall = call; })
-    .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
+    .then(() => check(PHONE_STATE_IN_CALL))
     .then(() => gRemoteAnswer(outCall))
-    .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
+    .then(() => check(PHONE_STATE_IN_CALL))
     // Hang up all
     .then(() => gRemoteHangUp(outCall))
-    .then(() => check(PHONE_STATE_NORMAL, PHONE_STATE_NORMAL))
+    .then(() => check(PHONE_STATE_NORMAL))
 
     // Dial out
     .then(() => gDial(outNumber))
     .then(call => { outCall = call; })
-    .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
+    .then(() => check(PHONE_STATE_IN_CALL))
     .then(() => gRemoteAnswer(outCall))
-    .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
+    .then(() => check(PHONE_STATE_IN_CALL))
     .then(() => gHold(outCall))
-    .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
+    .then(() => check(PHONE_STATE_IN_CALL))
     .then(() => gResume(outCall))
-    .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
+    .then(() => check(PHONE_STATE_IN_CALL))
     // Dial out and dial in
     .then(() => gRemoteDial(inNumber))
     .then(call => { inCall = call; })
-    .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
+    .then(() => check(PHONE_STATE_IN_CALL))
     .then(() => gAnswer(inCall))
-    .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
+    .then(() => check(PHONE_STATE_IN_CALL))
     // Conference
     .then(() => gAddCallsToConference([outCall, inCall]))
-    .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
+    .then(() => check(PHONE_STATE_IN_CALL))
     // Hang up all
     .then(() => gRemoteHangUpCalls([outCall, inCall]))
-    .then(() => check(PHONE_STATE_NORMAL, PHONE_STATE_NORMAL))
+    .then(() => check(PHONE_STATE_NORMAL))
 
     // End
     .then(null, error => {
       ok(false, 'promise rejects during test.');
     })
     .then(finish);
 });
deleted file mode 100644
--- a/dom/telephony/test/marionette/test_incomingcall_phonestate_speaker.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 60000;
-MARIONETTE_HEAD_JS = 'head.js';
-
-const AUDIO_MANAGER_CONTRACT_ID = "@mozilla.org/telephony/audiomanager;1";
-
-// See nsIAudioManager
-const PHONE_STATE_INVALID          = -2;
-const PHONE_STATE_CURRENT          = -1;
-const PHONE_STATE_NORMAL           = 0;
-const PHONE_STATE_RINGTONE         = 1;
-const PHONE_STATE_IN_CALL          = 2;
-const PHONE_STATE_IN_COMMUNICATION = 3;
-
-let audioManager;
-
-function checkSpeakerEnabled(phoneStatePrev, phoneStateNew, toggle, setSpeaker) {
-  if (!audioManager) {
-    audioManager = SpecialPowers.Cc[AUDIO_MANAGER_CONTRACT_ID]
-      .getService(SpecialPowers.Ci.nsIAudioManager);
-    ok(audioManager, "nsIAudioManager instance");
-  }
-
-  is(audioManager.phoneState, phoneStatePrev, "audioManager.phoneState");
-  if (toggle) {
-    telephony.speakerEnabled = setSpeaker;
-  }
-  is(audioManager.phoneState, phoneStateNew, "audioManager.phoneState");
-}
-
-// Start the test
-startTest(function() {
-  let inNumber  = "5555550201";
-  let inCall;
-
-  Promise.resolve()
-    .then(() => checkSpeakerEnabled(PHONE_STATE_NORMAL, PHONE_STATE_NORMAL, false, false))
-    // Dial in
-    .then(() => gRemoteDial(inNumber))
-    .then(call => { inCall = call; })
-    .then(() => checkSpeakerEnabled(PHONE_STATE_RINGTONE, PHONE_STATE_RINGTONE, false, false))
-    .then(() => gAnswer(inCall))
-    .then(() => checkSpeakerEnabled(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL, false, false))
-    // Go on Speaker (Don't go off speaker before hanging up.  This is to check
-    // the condition for bug 1021550)
-    .then(() => checkSpeakerEnabled(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL, true, true))
-    // Hang up all
-    .then(() => gRemoteHangUp(inCall))
-    .then(() => checkSpeakerEnabled(PHONE_STATE_NORMAL, PHONE_STATE_NORMAL, false, false))
-    // Make a second inbound call
-    .then(() => gRemoteDial(inNumber))
-    .then(call => { inCall = call; })
-    .then(() => checkSpeakerEnabled(PHONE_STATE_RINGTONE, PHONE_STATE_RINGTONE, false, false))
-    .then(() => gAnswer(inCall))
-    .then(() => checkSpeakerEnabled(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL, false, false))
-    // Hang up the call
-    .then(() => gRemoteHangUp(inCall))
-    .then(() => checkSpeakerEnabled(PHONE_STATE_NORMAL, PHONE_STATE_NORMAL, false, false))
-    // End
-    .then(null, error => {
-      ok(false, 'promise rejects during test.');
-    })
-    .then(finish);
-});
--- a/dom/webidl/MozNFC.webidl
+++ b/dom/webidl/MozNFC.webidl
@@ -1,14 +1,36 @@
 /* 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. */
 
+enum NfcErrorMessage {
+  "",
+  "IOError",
+  "Timeout",
+  "Busy",
+  "ErrorConnect",
+  "ErrorDisconnect",
+  "ErrorRead",
+  "ErrorWrite",
+  "InvalidParameter",
+  "InsufficientResource",
+  "ErrorSocketCreation",
+  "FailEnableDiscovery",
+  "FailDisableDiscovery",
+  "NotInitialize",
+  "InitializeFail",
+  "DeinitializeFail",
+  "NotSupport",
+  "FailEnableLowPowerMode",
+  "FailDisableLowPowerMode"
+};
+
 [NoInterfaceObject]
 interface MozNFCManager {
   /**
    * API to check if the given application's manifest
    * URL is registered with the Chrome Process or not.
    *
    * Returns success if given manifestUrl is registered for 'onpeerready',
    * otherwise error
--- a/dom/webidl/NfcOptions.webidl
+++ b/dom/webidl/NfcOptions.webidl
@@ -17,16 +17,17 @@ dictionary NfcCommandOptions
   sequence<MozNDEFRecordOptions> records;
 };
 
 dictionary NfcEventOptions
 {
   DOMString type = "";
 
   long status;
+  NfcErrorMessage errorMsg;
   long sessionId;
   DOMString requestId;
 
   long majorVersion;
   long minorVersion;
 
   sequence<NFCTechType> techList;
   sequence<MozNDEFRecordOptions> records;
--- a/mobile/android/base/ChromeCast.java
+++ b/mobile/android/base/ChromeCast.java
@@ -43,16 +43,35 @@ class ChromeCast implements GeckoMediaPl
     private final RouteInfo route;
     private GoogleApiClient apiClient;
     private RemoteMediaPlayer remoteMediaPlayer;
     private final boolean canMirror;
     private String mSessionId;
     private MirrorChannel mMirrorChannel;
     private boolean mApplicationStarted = false;
 
+    // EventCallback which is actually a GeckoEventCallback is sometimes being invoked more
+    // than once. That causes the IllegalStateException to be thrown. To prevent a crash,
+    // catch the exception and report it as an error to the log.
+    private static void sendSuccess(final EventCallback callback, final String msg) {
+        try {
+            callback.sendSuccess(msg);
+        } catch (final IllegalStateException e) {
+            Log.e(LOGTAG, "Attempting to invoke callback.sendSuccess more than once.", e);
+        }
+    }
+
+    private static void sendError(final EventCallback callback, final String msg) {
+        try {
+            callback.sendError(msg);
+        } catch (final IllegalStateException e) {
+            Log.e(LOGTAG, "Attempting to invoke callback.sendError more than once.", e);
+        }
+    }
+
     // Callback to start playback of a url on a remote device
     private class VideoPlayCallback implements ResultCallback<ApplicationConnectionResult>,
                                                RemoteMediaPlayer.OnStatusUpdatedListener,
                                                RemoteMediaPlayer.OnMetadataUpdatedListener {
         private final String url;
         private final String type;
         private final String title;
         private final EventCallback callback;
@@ -96,51 +115,51 @@ class ChromeCast implements GeckoMediaPl
                 try {
                     Cast.CastApi.setMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace(), remoteMediaPlayer);
                 } catch (IOException e) {
                     debug("Exception while creating media channel", e);
                 }
 
                 startPlayback();
             } else {
-                callback.sendError(status.toString());
+                sendError(callback, status.toString());
             }
         }
 
         private void startPlayback() {
             MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
             mediaMetadata.putString(MediaMetadata.KEY_TITLE, title);
             MediaInfo mediaInfo = new MediaInfo.Builder(url)
                                                .setContentType(type)
                                                .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
                                                .setMetadata(mediaMetadata)
                                                .build();
             try {
                 remoteMediaPlayer.load(apiClient, mediaInfo, true).setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
                     @Override
                     public void onResult(MediaChannelResult result) {
                         if (result.getStatus().isSuccess()) {
-                            callback.sendSuccess(null);
+                            sendSuccess(callback, null);
                             debug("Media loaded successfully");
                             return;
                         }
 
                         debug("Media load failed " + result.getStatus());
-                        callback.sendError(result.getStatus().toString());
+                        sendError(callback, result.getStatus().toString());
                     }
                 });
 
                 return;
             } catch (IllegalStateException e) {
                 debug("Problem occurred with media during loading", e);
             } catch (Exception e) {
                 debug("Problem opening media during loading", e);
             }
 
-            callback.sendError("");
+            sendError(callback, "");
         }
     }
 
     public ChromeCast(Context context, RouteInfo route) {
         int status =  GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
         if (status != ConnectionResult.SUCCESS) {
             throw new IllegalStateException("Play services are required for Chromecast support (go status code " + status + ")");
         }
@@ -193,17 +212,17 @@ class ChromeCast implements GeckoMediaPl
         apiClient = new GoogleApiClient.Builder(context)
             .addApi(Cast.API, apiOptionsBuilder.build())
             .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                 @Override
                 public void onConnected(Bundle connectionHint) {
                     // Sometimes apiClient is null here. See bug 1061032
                     if (apiClient != null && !apiClient.isConnected()) {
                         debug("Connection failed");
-                        callback.sendError("Not connected");
+                        sendError(callback, "Not connected");
                         return;
                     }
 
                     // Launch the media player app and launch this url once its loaded
                     try {
                         Cast.CastApi.launchApplication(apiClient, CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID, true)
                                     .setResultCallback(new VideoPlayCallback(url, type, title, callback));
                     } catch (Exception e) {
@@ -218,39 +237,39 @@ class ChromeCast implements GeckoMediaPl
         }).build();
 
         apiClient.connect();
     }
 
     @Override
     public void start(final EventCallback callback) {
         // Nothing to be done here
-        callback.sendSuccess(null);
+        sendSuccess(callback, null);
     }
 
     @Override
     public void stop(final EventCallback callback) {
         // Nothing to be done here
-        callback.sendSuccess(null);
+        sendSuccess(callback, null);
     }
 
     public boolean verifySession(final EventCallback callback) {
         String msg = null;
         if (apiClient == null || !apiClient.isConnected()) {
             msg = "Not connected";
         }
 
         if (mSessionId == null) {
             msg = "No session";
         }
 
         if (msg != null) {
             debug(msg);
             if (callback != null) {
-                callback.sendError(msg);
+                sendError(callback, msg);
             }
             return false;
         }
 
         return true;
     }
 
     @Override
@@ -261,50 +280,50 @@ class ChromeCast implements GeckoMediaPl
 
         try {
             remoteMediaPlayer.play(apiClient).setResultCallback(new ResultCallback<MediaChannelResult>() {
                 @Override
                 public void onResult(MediaChannelResult result) {
                     Status status = result.getStatus();
                     if (!status.isSuccess()) {
                         debug("Unable to play: " + status.getStatusCode());
-                        callback.sendError(status.toString());
+                        sendError(callback, status.toString());
                     } else {
-                        callback.sendSuccess(null);
+                        sendSuccess(callback, null);
                     }
                 }
             });
         } catch(IllegalStateException ex) {
             // The media player may throw if the session has been killed. For now, we're just catching this here.
-            callback.sendError("Error playing");
+            sendError(callback, "Error playing");
         }
     }
 
     @Override
     public void pause(final EventCallback callback) {
         if (!verifySession(callback)) {
             return;
         }
 
         try {
             remoteMediaPlayer.pause(apiClient).setResultCallback(new ResultCallback<MediaChannelResult>() {
                 @Override
                 public void onResult(MediaChannelResult result) {
                     Status status = result.getStatus();
                     if (!status.isSuccess()) {
                         debug("Unable to pause: " + status.getStatusCode());
-                        callback.sendError(status.toString());
+                        sendError(callback, status.toString());
                     } else {
-                        callback.sendSuccess(null);
+                        sendSuccess(callback, null);
                     }
                 }
             });
         } catch(IllegalStateException ex) {
             // The media player may throw if the session has been killed. For now, we're just catching this here.
-            callback.sendError("Error pausing");
+            sendError(callback, "Error pausing");
         }
     }
 
     @Override
     public void end(final EventCallback callback) {
         if (!verifySession(callback)) {
             return;
         }
@@ -317,33 +336,33 @@ class ChromeCast implements GeckoMediaPl
                         try {
                             Cast.CastApi.removeMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace());
                             remoteMediaPlayer = null;
                             mSessionId = null;
                             apiClient.disconnect();
                             apiClient = null;
 
                             if (callback != null) {
-                                callback.sendSuccess(null);
+                                sendSuccess(callback, null);
                             }
 
                             return;
                         } catch(Exception ex) {
                             debug("Error ending", ex);
                         }
                     }
 
                     if (callback != null) {
-                        callback.sendError(result.getStatus().toString());
+                        sendError(callback, result.getStatus().toString());
                     }
                 }
             });
         } catch(IllegalStateException ex) {
             // The media player may throw if the session has been killed. For now, we're just catching this here.
-            callback.sendError("Error stopping");
+            sendError(callback, "Error stopping");
         }
     }
 
     class MirrorChannel implements MessageReceivedCallback {
         /**
          * @return custom namespace
          */
         public String getNamespace() {
@@ -371,56 +390,48 @@ class ChromeCast implements GeckoMediaPl
                                            });
                 } catch (Exception e) {
                     Log.e(LOGTAG, "Exception while sending message", e);
                 }
             }
         }
     }
     private class MirrorCallback implements ResultCallback<ApplicationConnectionResult> {
-        // See Bug 1055562, callback is set to null after it has been
-        // invoked so that it will not be called a second time.
-        EventCallback callback;
+        final EventCallback callback;
         MirrorCallback(final EventCallback callback) {
             this.callback = callback;
         }
 
 
         @Override
         public void onResult(ApplicationConnectionResult result) {
-            if (callback == null) {
-                Log.e(LOGTAG, "Attempting to invoke MirrorChannel callback more than once.");
-                return;
-            }
             Status status = result.getStatus();
             if (status.isSuccess()) {
                 ApplicationMetadata applicationMetadata = result.getApplicationMetadata();
                 mSessionId = result.getSessionId();
                 String applicationStatus = result.getApplicationStatus();
                 boolean wasLaunched = result.getWasLaunched();
                 mApplicationStarted = true;
 
                 // Create the custom message
                 // channel
                 mMirrorChannel = new MirrorChannel();
                 try {
                     Cast.CastApi.setMessageReceivedCallbacks(apiClient,
                                                              mMirrorChannel
                                                              .getNamespace(),
                                                              mMirrorChannel);
-                    callback.sendSuccess(null);
-                    callback = null;
+                    sendSuccess(callback, null);
                 } catch (IOException e) {
                     Log.e(LOGTAG, "Exception while creating channel", e);
                 }
 
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Casting:Mirror", route.getId()));
             } else {
-                callback.sendError(status.toString());
-                callback = null;
+                sendError(callback, status.toString());
             }
         }
     }
 
     @Override
     public void message(String msg, final EventCallback callback) {
         if (mMirrorChannel != null) {
             mMirrorChannel.sendMessage(msg);
--- a/mobile/android/chrome/content/WebcompatReporter.js
+++ b/mobile/android/chrome/content/WebcompatReporter.js
@@ -8,32 +8,42 @@ Cu.import("resource://gre/modules/Privat
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 var WebcompatReporter = {
   menuItem: null,
   menuItemEnabled: null,
   init: function() {
     Services.obs.addObserver(this, "DesktopMode:Change", false);
-    Services.obs.addObserver(this, "content-page-shown", false);
+    Services.obs.addObserver(this, "chrome-document-global-created", false);
+    Services.obs.addObserver(this, "content-document-global-created", false);
     this.addMenuItem();
   },
 
   uninit: function() {
     Services.obs.removeObserver(this, "DesktopMode:Change");
+    Services.obs.removeObserver(this, "chrome-document-global-created");
+    Services.obs.removeObserver(this, "content-document-global-created");
 
     if (this.menuItem) {
       NativeWindow.menu.remove(this.menuItem);
       this.menuItem = null;
     }
   },
 
   observe: function(subject, topic, data) {
-    if (topic === "content-page-shown") {
-      let currentURI = subject.documentURI;
+    if (topic == "content-document-global-created" || topic == "chrome-document-global-created") {
+      let win = subject;
+      let currentURI = win.document.documentURI;
+
+      // Ignore non top-level documents
+      if (currentURI !== win.top.location.href) {
+        return;
+      }
+
       if (!this.menuItemEnabled && this.isReportableUrl(currentURI)) {
         NativeWindow.menu.update(this.menuItem, {enabled: true});
         this.menuItemEnabled = true;
       } else if (this.menuItemEnabled && !this.isReportableUrl(currentURI)) {
         NativeWindow.menu.update(this.menuItem, {enabled: false});
         this.menuItemEnabled = false;
       }
     } else if (topic === "DesktopMode:Change") {
--- a/mobile/android/confvars.sh
+++ b/mobile/android/confvars.sh
@@ -61,18 +61,21 @@ MOZ_DATA_REPORTING=1
 fi
 
 # Enable runtime locale switching.
 MOZ_LOCALE_SWITCHER=1
 
 # Enable second screen and casting support for external devices.
 MOZ_DEVICES=1
 
-# Enable second screen using native Android libraries
-MOZ_NATIVE_DEVICES=1
+# Enable second screen using native Android libraries, provided we're
+# not resource constrained.
+if test -z "$MOZ_ANDROID_RESOURCE_CONSTRAINED"; then
+  MOZ_NATIVE_DEVICES=1
+fi
 
 # Mark as WebGL conformant
 MOZ_WEBGL_CONFORMANT=1
 
 # Enable the Search Activity.
 MOZ_ANDROID_SEARCH_ACTIVITY=1
 
 # Enable the new tablet UI in pre-release builds
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -25,17 +25,17 @@
 @BINPATH@/defaults/profile/chrome/*
 #ifdef MOZ_UPDATER
 @BINPATH@/update.locale
 @BINPATH@/updater.ini
 #endif
 @BINPATH@/dictionaries/*
 @BINPATH@/hyphenation/*
 
-[assets destdir="assets"]
+[assets destdir="assets/@ANDROID_CPU_ARCH@"]
 #ifndef MOZ_STATIC_JS
 @BINPATH@/@DLL_PREFIX@mozjs@DLL_SUFFIX@
 #endif
 #ifdef MOZ_DMD
 @BINPATH@/@DLL_PREFIX@dmd@DLL_SUFFIX@
 #endif
 #ifndef MOZ_FOLD_LIBS
 @BINPATH@/@DLL_PREFIX@plc4@DLL_SUFFIX@
--- a/mozglue/android/APKOpen.cpp
+++ b/mozglue/android/APKOpen.cpp
@@ -214,18 +214,18 @@ loadGeckoLibs(const char *apkName)
 
   uint64_t t0 = TimeStamp_Now();
   struct rusage usage1_thread, usage1;
   getrusage(RUSAGE_THREAD, &usage1_thread);
   getrusage(RUSAGE_SELF, &usage1);
   
   RefPtr<Zip> zip = ZipCollection::GetZip(apkName);
 
-  char *file = new char[strlen(apkName) + sizeof("!/assets/libxul.so")];
-  sprintf(file, "%s!/assets/libxul.so", apkName);
+  char *file = new char[strlen(apkName) + sizeof("!/assets/" ANDROID_CPU_ARCH "/libxul.so")];
+  sprintf(file, "%s!/assets/" ANDROID_CPU_ARCH "/libxul.so", apkName);
   xul_handle = __wrap_dlopen(file, RTLD_GLOBAL | RTLD_LAZY);
   delete[] file;
 
   if (!xul_handle) {
     __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libxul!");
     return FAILURE;
   }
 
@@ -273,18 +273,18 @@ loadSQLiteLibs(const char *apkName)
 #else
   chdir(getenv("GRE_HOME"));
 
   RefPtr<Zip> zip = ZipCollection::GetZip(apkName);
   if (!lib_mapping) {
     lib_mapping = (struct mapping_info *)calloc(MAX_MAPPING_INFO, sizeof(*lib_mapping));
   }
 
-  char *file = new char[strlen(apkName) + sizeof("!/assets/libmozsqlite3.so")];
-  sprintf(file, "%s!/assets/libmozsqlite3.so", apkName);
+  char *file = new char[strlen(apkName) + sizeof("!/assets/" ANDROID_CPU_ARCH "/libmozsqlite3.so")];
+  sprintf(file, "%s!/assets/" ANDROID_CPU_ARCH "/libmozsqlite3.so", apkName);
   sqlite_handle = __wrap_dlopen(file, RTLD_GLOBAL | RTLD_LAZY);
   delete [] file;
 
   if (!sqlite_handle) {
     __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libmozsqlite3!");
     return FAILURE;
   }
 #endif
@@ -301,29 +301,29 @@ loadNSSLibs(const char *apkName)
 
   chdir(getenv("GRE_HOME"));
 
   RefPtr<Zip> zip = ZipCollection::GetZip(apkName);
   if (!lib_mapping) {
     lib_mapping = (struct mapping_info *)calloc(MAX_MAPPING_INFO, sizeof(*lib_mapping));
   }
 
-  char *file = new char[strlen(apkName) + sizeof("!/assets/libnss3.so")];
-  sprintf(file, "%s!/assets/libnss3.so", apkName);
+  char *file = new char[strlen(apkName) + sizeof("!/assets/" ANDROID_CPU_ARCH "/libnss3.so")];
+  sprintf(file, "%s!/assets/" ANDROID_CPU_ARCH "/libnss3.so", apkName);
   nss_handle = __wrap_dlopen(file, RTLD_GLOBAL | RTLD_LAZY);
   delete [] file;
 
 #ifndef MOZ_FOLD_LIBS
-  file = new char[strlen(apkName) + sizeof("!/assets/libnspr4.so")];
-  sprintf(file, "%s!/assets/libnspr4.so", apkName);
+  file = new char[strlen(apkName) + sizeof("!/assets/" ANDROID_CPU_ARCH "/libnspr4.so")];
+  sprintf(file, "%s!/assets/" ANDROID_CPU_ARCH "/libnspr4.so", apkName);
   nspr_handle = __wrap_dlopen(file, RTLD_GLOBAL | RTLD_LAZY);
   delete [] file;
 
-  file = new char[strlen(apkName) + sizeof("!/assets/libplc4.so")];
-  sprintf(file, "%s!/assets/libplc4.so", apkName);
+  file = new char[strlen(apkName) + sizeof("!/assets/" ANDROID_CPU_ARCH "/libplc4.so")];
+  sprintf(file, "%s!/assets/" ANDROID_CPU_ARCH "/libplc4.so", apkName);
   plc_handle = __wrap_dlopen(file, RTLD_GLOBAL | RTLD_LAZY);
   delete [] file;
 #endif
 
   if (!nss_handle) {
     __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libnss3!");
     return FAILURE;
   }
--- a/mozglue/android/moz.build
+++ b/mozglue/android/moz.build
@@ -16,17 +16,19 @@ SOURCES += [
     'pbkdf2_sha256.c',
     'SQLiteBridge.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 FINAL_LIBRARY = 'mozglue'
 
-DEFINES['ANDROID_PACKAGE_NAME'] = '"%s"' % CONFIG['ANDROID_PACKAGE_NAME']
+for var in ('ANDROID_PACKAGE_NAME',
+            'ANDROID_CPU_ARCH'):
+    DEFINES[var] = '"%s"' % CONFIG[var]
 
 if CONFIG['MOZ_FOLD_LIBS']:
     DEFINES['MOZ_FOLD_LIBS'] = True
 
 GENERATED_INCLUDES += ['/build']
 LOCAL_INCLUDES += [
     '../linker',
     '/db/sqlite3/src',
--- a/toolkit/crashreporter/google-breakpad/src/common/android/include/ucontext.h
+++ b/toolkit/crashreporter/google-breakpad/src/common/android/include/ucontext.h
@@ -26,31 +26,30 @@
 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #ifndef GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_UCONTEXT_H
 #define GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_UCONTEXT_H
 
 #include <sys/cdefs.h>
+#include <signal.h>
 
-#ifdef __BIONIC_UCONTEXT_H
-#include <ucontext.h>
+#ifdef __BIONIC_HAVE_UCONTEXT_H
+# include_next <ucontext.h>
 #else
-
-#include <sys/ucontext.h>
+# include <sys/ucontext.h>
+#endif  // __BIONIC_UCONTEXT_H
 
 #ifdef __cplusplus
 extern "C" {
 #endif  // __cplusplus
 
 // Provided by src/android/common/breakpad_getcontext.S
 int breakpad_getcontext(ucontext_t* ucp);
 
 #define getcontext(x)   breakpad_getcontext(x)
 
 #ifdef __cplusplus
 }  // extern "C"
 #endif  // __cplusplus
 
-#endif  // __BIONIC_UCONTEXT_H
-
 #endif  // GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_UCONTEXT_H
--- a/toolkit/devtools/client/dbg-client.jsm
+++ b/toolkit/devtools/client/dbg-client.jsm
@@ -261,17 +261,16 @@ this.DebuggerClient = function (aTranspo
 
   // Map actor ID to client instance for each actor type.
   this._clients = new Map;
 
   this._pendingRequests = [];
   this._activeRequests = new Map;
   this._eventsEnabled = true;
 
-  this.compat = new ProtocolCompatibility(this, []);
   this.traits = {};
 
   this.request = this.request.bind(this);
   this.localTransport = this._transport.onOutputStreamReady === undefined;
 
   /*
    * As the first thing on the connection, expect a greeting packet from
    * the connection's root actor.
@@ -853,91 +852,83 @@ DebuggerClient.prototype = {
 
   // Transport hooks.
 
   /**
    * Called by DebuggerTransport to dispatch incoming packets as appropriate.
    *
    * @param aPacket object
    *        The incoming packet.
-   * @param aIgnoreCompatibility boolean
-   *        Set true to not pass the packet through the compatibility layer.
    */
-  onPacket: function (aPacket, aIgnoreCompatibility=false) {
-    let packet = aIgnoreCompatibility
-      ? aPacket
-      : this.compat.onPacket(aPacket);
+  onPacket: function (aPacket) {
+    if (!aPacket.from) {
+      DevToolsUtils.reportException(
+        "onPacket",
+        new Error("Server did not specify an actor, dropping packet: " +
+                  JSON.stringify(aPacket)));
+      return;
+    }
 
-    resolve(packet).then(aPacket => {
-      if (!aPacket.from) {
-        DevToolsUtils.reportException(
-          "onPacket",
-          new Error("Server did not specify an actor, dropping packet: " +
-                    JSON.stringify(aPacket)));
-        return;
-      }
+    // If we have a registered Front for this actor, let it handle the packet
+    // and skip all the rest of this unpleasantness.
+    let front = this.getActor(aPacket.from);
+    if (front) {
+      front.onPacket(aPacket);
+      return;
+    }
 
-      // If we have a registered Front for this actor, let it handle the packet
-      // and skip all the rest of this unpleasantness.
-      let front = this.getActor(aPacket.from);
-      if (front) {
-        front.onPacket(aPacket);
+    if (this._clients.has(aPacket.from) && aPacket.type) {
+      let client = this._clients.get(aPacket.from);
+      let type = aPacket.type;
+      if (client.events.indexOf(type) != -1) {
+        client.emit(type, aPacket);
+        // we ignore the rest, as the client is expected to handle this packet.
         return;
       }
-
-      if (this._clients.has(aPacket.from) && aPacket.type) {
-        let client = this._clients.get(aPacket.from);
-        let type = aPacket.type;
-        if (client.events.indexOf(type) != -1) {
-          client.emit(type, aPacket);
-          // we ignore the rest, as the client is expected to handle this packet.
-          return;
-        }
-      }
+    }
 
-      let activeRequest;
-      // See if we have a handler function waiting for a reply from this
-      // actor. (Don't count unsolicited notifications or pauses as
-      // replies.)
-      if (this._activeRequests.has(aPacket.from) &&
-          !(aPacket.type in UnsolicitedNotifications) &&
-          !(aPacket.type == ThreadStateTypes.paused &&
-            aPacket.why.type in UnsolicitedPauses)) {
-        activeRequest = this._activeRequests.get(aPacket.from);
-        this._activeRequests.delete(aPacket.from);
-      }
+    let activeRequest;
+    // See if we have a handler function waiting for a reply from this
+    // actor. (Don't count unsolicited notifications or pauses as
+    // replies.)
+    if (this._activeRequests.has(aPacket.from) &&
+        !(aPacket.type in UnsolicitedNotifications) &&
+        !(aPacket.type == ThreadStateTypes.paused &&
+          aPacket.why.type in UnsolicitedPauses)) {
+      activeRequest = this._activeRequests.get(aPacket.from);
+      this._activeRequests.delete(aPacket.from);
+    }
 
-      // Packets that indicate thread state changes get special treatment.
-      if (aPacket.type in ThreadStateTypes &&
-          this._clients.has(aPacket.from) &&
-          typeof this._clients.get(aPacket.from)._onThreadState == "function") {
-        this._clients.get(aPacket.from)._onThreadState(aPacket);
-      }
-      // On navigation the server resumes, so the client must resume as well.
-      // We achieve that by generating a fake resumption packet that triggers
-      // the client's thread state change listeners.
-      if (aPacket.type == UnsolicitedNotifications.tabNavigated &&
-          this._clients.has(aPacket.from) &&
-          this._clients.get(aPacket.from).thread) {
-        let thread = this._clients.get(aPacket.from).thread;
-        let resumption = { from: thread._actor, type: "resumed" };
-        thread._onThreadState(resumption);
-      }
-      // Only try to notify listeners on events, not responses to requests
-      // that lack a packet type.
-      if (aPacket.type) {
-        this.emit(aPacket.type, aPacket);
-      }
+    // Packets that indicate thread state changes get special treatment.
+    if (aPacket.type in ThreadStateTypes &&
+        this._clients.has(aPacket.from) &&
+        typeof this._clients.get(aPacket.from)._onThreadState == "function") {
+      this._clients.get(aPacket.from)._onThreadState(aPacket);
+    }
+    // On navigation the server resumes, so the client must resume as well.
+    // We achieve that by generating a fake resumption packet that triggers
+    // the client's thread state change listeners.
+    if (aPacket.type == UnsolicitedNotifications.tabNavigated &&
+        this._clients.has(aPacket.from) &&
+        this._clients.get(aPacket.from).thread) {
+      let thread = this._clients.get(aPacket.from).thread;
+      let resumption = { from: thread._actor, type: "resumed" };
+      thread._onThreadState(resumption);
+    }
+    // Only try to notify listeners on events, not responses to requests
+    // that lack a packet type.
+    if (aPacket.type) {
+      this.emit(aPacket.type, aPacket);
+    }
 
-      if (activeRequest) {
-        activeRequest.emit("json-reply", aPacket);
-      }
+    if (activeRequest) {
+      activeRequest.emit("json-reply", aPacket);
+    }
 
-      this._sendRequests();
-    }, ex => DevToolsUtils.reportException("onPacket handler", ex));
+    this._sendRequests();
   },
 
   /**
    * Called by the DebuggerTransport to dispatch incoming bulk packets as
    * appropriate.
    *
    * @param packet object
    *        The incoming packet, which contains:
@@ -1091,166 +1082,16 @@ Request.prototype = {
   emit: function(type, ...args) {
     events.emit(this, type, ...args);
   },
 
   get actor() { return this.request.to || this.request.actor; }
 
 };
 
-// Constants returned by `FeatureCompatibilityShim.onPacketTest`.
-const SUPPORTED = 1;
-const NOT_SUPPORTED = 2;
-const SKIP = 3;
-
-/**
- * This object provides an abstraction layer over all of our backwards
- * compatibility, feature detection, and shimming with regards to the remote
- * debugging prototcol.
- *
- * @param aFeatures Array
- *        An array of FeatureCompatibilityShim objects
- */
-function ProtocolCompatibility(aClient, aFeatures) {
-  this._client = aClient;
-  this._featuresWithUnknownSupport = new Set(aFeatures);
-  this._featuresWithoutSupport = new Set();
-
-  this._featureDeferreds = Object.create(null)
-  for (let f of aFeatures) {
-    this._featureDeferreds[f.name] = defer();
-  }
-}
-
-ProtocolCompatibility.prototype = {
-  /**
-   * Returns a promise that resolves to true if the RDP supports the feature,
-   * and is rejected otherwise.
-   *
-   * @param aFeatureName String
-   *        The name of the feature we are testing.
-   */
-  supportsFeature: function (aFeatureName) {
-    return this._featureDeferreds[aFeatureName].promise;
-  },
-
-  /**
-   * Force a feature to be considered unsupported.
-   *
-   * @param aFeatureName String
-   *        The name of the feature we are testing.
-   */
-  rejectFeature: function (aFeatureName) {
-    this._featureDeferreds[aFeatureName].reject(false);
-  },
-
-  /**
-   * Called for each packet received over the RDP from the server. Tests for
-   * protocol features and shims packets to support needed features.
-   *
-   * @param aPacket Object
-   *        Packet received over the RDP from the server.
-   */
-  onPacket: function (aPacket) {
-    this._detectFeatures(aPacket);
-    return this._shimPacket(aPacket);
-  },
-
-  /**
-   * For each of the features we don't know whether the server supports or not,
-   * attempt to detect support based on the packet we just received.
-   */
-  _detectFeatures: function (aPacket) {
-    for (let feature of this._featuresWithUnknownSupport) {
-      try {
-        switch (feature.onPacketTest(aPacket)) {
-        case SKIP:
-          break;
-        case SUPPORTED:
-          this._featuresWithUnknownSupport.delete(feature);
-          this._featureDeferreds[feature.name].resolve(true);
-          break;
-        case NOT_SUPPORTED:
-          this._featuresWithUnknownSupport.delete(feature);
-          this._featuresWithoutSupport.add(feature);
-          this.rejectFeature(feature.name);
-          break;
-        default:
-          DevToolsUtils.reportException(
-            "PC__detectFeatures",
-            new Error("Bad return value from `onPacketTest` for feature '"
-                      + feature.name + "'"));
-        }
-      } catch (ex) {
-        DevToolsUtils.reportException("PC__detectFeatures", ex);
-      }
-    }
-  },
-
-  /**
-   * Go through each of the features that we know are unsupported by the current
-   * server and attempt to shim support.
-   */
-  _shimPacket: function (aPacket) {
-    let extraPackets = [];
-
-    let loop = (aFeatures, aPacket) => {
-      if (aFeatures.length === 0) {
-        for (let packet of extraPackets) {
-          this._client.onPacket(packet, true);
-        }
-        return aPacket;
-      } else {
-        let replacePacket = function (aNewPacket) {
-          return aNewPacket;
-        };
-        let extraPacket = function (aExtraPacket) {
-          extraPackets.push(aExtraPacket);
-          return aPacket;
-        };
-        let keepPacket = function () {
-          return aPacket;
-        };
-        let newPacket = aFeatures[0].translatePacket(aPacket,
-                                                     replacePacket,
-                                                     extraPacket,
-                                                     keepPacket);
-        return resolve(newPacket).then(loop.bind(null, aFeatures.slice(1)));
-      }
-    };
-
-    return loop([f for (f of this._featuresWithoutSupport)],
-                aPacket);
-  }
-};
-
-/**
- * Interface defining what methods a feature compatibility shim should have.
- */
-const FeatureCompatibilityShim = {
-  // The name of the feature
-  name: null,
-
-  /**
-   * Takes a packet and returns boolean (or promise of boolean) indicating
-   * whether the server supports the RDP feature we are possibly shimming.
-   */
-  onPacketTest: function (aPacket) {
-    throw new Error("Not yet implemented");
-  },
-
-  /**
-   * Takes a packet actually sent from the server and decides whether to replace
-   * it with a new packet, create an extra packet, or keep it.
-   */
-  translatePacket: function (aPacket, aReplacePacket, aExtraPacket, aKeepPacket) {
-    throw new Error("Not yet implemented");
-  }
-};
-
 /**
  * Creates a tab client for the remote debugging protocol server. This client
  * is a front to the tab actor created in the server side, hiding the protocol
  * details in a traditional JavaScript API.
  *
  * @param aClient DebuggerClient
  *        The debugger client parent.
  * @param aForm object
@@ -1498,17 +1339,16 @@ ThreadClient.prototype = {
 
   _pauseOnExceptions: false,
   _ignoreCaughtExceptions: false,
   _pauseOnDOMEvents: null,
 
   _actor: null,
   get actor() { return this._actor; },
 
-  get compat() { return this.client.compat; },
   get _transport() { return this.client._transport; },
 
   _assertPaused: function (aCommand) {
     if (!this.paused) {
       throw Error(aCommand + " command sent while not paused. Currently " + this._state);
     }
   },
 
--- a/toolkit/modules/Promise-backend.js
+++ b/toolkit/modules/Promise-backend.js
@@ -267,17 +267,17 @@ PendingErrors.addObserver(function(detai
     return;
   }
   let message = details.message;
   if (details.stack) {
     message += "\nFull Stack: " + details.stack;
   }
   error.init(
              /*message*/ generalDescription +
-             "Date: " + details.date + "\nFull Message: " + details.message,
+             "Date: " + details.date + "\nFull Message: " + message,
              /*sourceName*/ details.fileName,
              /*sourceLine*/ details.lineNumber?("" + details.lineNumber):0,
              /*lineNumber*/ details.lineNumber || 0,
              /*columnNumber*/ 0,
              /*flags*/ Ci.nsIScriptError.errorFlag,
              /*category*/ "chrome javascript");
   Services.console.logMessage(error);
 });
--- a/toolkit/mozapps/installer/packager.mk
+++ b/toolkit/mozapps/installer/packager.mk
@@ -375,24 +375,25 @@ INNER_MAKE_GECKOVIEW_EXAMPLE=echo 'Gecko
 endif
 
 ifdef MOZ_OMX_PLUGIN
 DIST_FILES += libomxplugin.so libomxplugingb.so libomxplugingb235.so \
               libomxpluginhc.so libomxpluginkk.so
 endif
 
 SO_LIBRARIES := $(filter %.so,$(DIST_FILES))
-# These libraries are placed in the assets/ directory by packager.py.
-ASSET_SO_LIBRARIES := $(addprefix assets/,$(filter-out libmozglue.so $(MOZ_CHILD_PROCESS_NAME),$(SO_LIBRARIES)))
+# These libraries are placed in the assets/$(ANDROID_CPU_ARCH) directory by packager.py.
+ASSET_SO_LIBRARIES := $(addprefix assets/$(ANDROID_CPU_ARCH)/,$(filter-out libmozglue.so $(MOZ_CHILD_PROCESS_NAME),$(SO_LIBRARIES)))
 
 DIST_FILES := $(filter-out $(SO_LIBRARIES),$(DIST_FILES))
 NON_DIST_FILES += libmozglue.so $(MOZ_CHILD_PROCESS_NAME) $(ASSET_SO_LIBRARIES)
 
 ifdef MOZ_ENABLE_SZIP
-# These libraries are szipped in-place in the assets/ directory.
+# These libraries are szipped in-place in the
+# assets/$(ANDROID_CPU_ARCH) directory.
 SZIP_LIBRARIES := $(ASSET_SO_LIBRARIES)
 endif
 
 # Fennec's OMNIJAR_NAME can include a directory; for example, it might
 # be "assets/omni.ja". This path specifies where the omni.ja file
 # lives in the APK, but should not root the resources it contains
 # under assets/ (i.e., resources should not live at chrome://assets/).
 # packager.py writes /omni.ja in order to be consistent with the