Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 28 Jan 2015 14:30:12 +0100
changeset 239614 c738e5c9a061aece7914714b5c18ee6c6bd1bce9
parent 239613 c4df6e6c0321ad8622ac4a68a3e9efca6d93d77d (current diff)
parent 239605 d8260c1a0689da5f4f3252bf41063e8a03c8fbbc (diff)
child 239615 3f291f100e26507fef3bc08b4d8d66733eacfbd1
push id500
push userjoshua.m.grant@gmail.com
push dateThu, 29 Jan 2015 01:48:36 +0000
milestone38.0a1
Merge mozilla-central to mozilla-inbound
mobile/android/themes/core/images/reader-minus-icon-hdpi.png
mobile/android/themes/core/images/reader-minus-icon-mdpi.png
mobile/android/themes/core/images/reader-minus-icon-xhdpi.png
mobile/android/themes/core/images/reader-plus-icon-hdpi.png
mobile/android/themes/core/images/reader-plus-icon-mdpi.png
mobile/android/themes/core/images/reader-plus-icon-xhdpi.png
--- a/b2g/components/HelperAppDialog.js
+++ b/b2g/components/HelperAppDialog.js
@@ -28,24 +28,16 @@ HelperAppLauncherDialog.prototype = {
   classID: Components.ID("{710322af-e6ae-4b0c-b2c9-1474a87b077e}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]),
 
   show: function(aLauncher, aContext, aReason) {
     aLauncher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.saveToDisk;
     aLauncher.saveToDisk(null, false);
   },
 
-  promptForSaveToFile: function(aLauncher,
-                                aContext,
-                                aDefaultFile,
-                                aSuggestedFileExt,
-                                aForcePrompt) {
-    throw Cr.NS_ERROR_NOT_AVAILABLE;
-  },
-
   promptForSaveToFileAsync: function(aLauncher,
                                      aContext,
                                      aDefaultFile,
                                      aSuggestedFileExt,
                                      aForcePrompt) {
     // Retrieve the user's default download directory.
     Task.spawn(function() {
       let file = null;
--- 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="e06971db7acf7a35c32eb74d675a4e12e288e6be">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="1d53fb07984298253aad64bfa4236b7167ee3d4d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="ba613ae583a706131c45e885f65d428d4a541a81"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d6a27295acb0a25926bf6290dd2532a7f9027864"/>
@@ -131,14 +131,14 @@
   <project name="device/sprd" path="device/sprd" revision="8491e6338aa1e12699a2064895b02fb015f91b89"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="4e58336019b5cbcfd134caf55b142236cf986618"/>
   <project name="platform/frameworks/av" path="frameworks/av" revision="4387fe988e5a1001f29ce05fcfda03ed2d32137b"/>
   <project name="platform/hardware/akm" path="hardware/akm" revision="6d3be412647b0eab0adff8a2768736cf4eb68039"/>
   <project groups="invensense" name="platform/hardware/invensense" path="hardware/invensense" revision="e6d9ab28b4f4e7684f6c07874ee819c9ea0002a2"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="865ce3b4a2ba0b3a31421ca671f4d6c5595f8690"/>
   <project name="kernel/common" path="kernel" revision="6c6f012cea17fb8b3263605737816cf6663432f1"/>
   <project name="platform/system/core" path="system/core" revision="53d584d4a4b4316e4de9ee5f210d662f89b44e7e"/>
-  <project name="u-boot" path="u-boot" revision="5167e5eec5cb6b3147839da158637e6d953a4e4f"/>
+  <project name="u-boot" path="u-boot" revision="cb37a7434073bb47c4c066f6922006442dcd8531"/>
   <project name="vendor/sprd/gps" path="vendor/sprd/gps" revision="6974f8e771d4d8e910357a6739ab124768891e8f"/>
   <project name="vendor/sprd/open-source" path="vendor/sprd/open-source" revision="f56ab768cb9f1ad42fb0809ffec1424b1e693369"/>
   <project name="vendor/sprd/partner" path="vendor/sprd/partner" revision="8649c7145972251af11b0639997edfecabfc7c2e"/>
   <project name="vendor/sprd/proprietories" path="vendor/sprd/proprietories" revision="d2466593022f7078aaaf69026adf3367c2adb7bb"/>
 </manifest>
--- 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="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="1d53fb07984298253aad64bfa4236b7167ee3d4d"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="ba613ae583a706131c45e885f65d428d4a541a81"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="6fa7a4936414ceb4055fd27f7a30e76790f834fb"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d6a27295acb0a25926bf6290dd2532a7f9027864"/>
   <!-- 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="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="1d53fb07984298253aad64bfa4236b7167ee3d4d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="ba613ae583a706131c45e885f65d428d4a541a81"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d6a27295acb0a25926bf6290dd2532a7f9027864"/>
   <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="e06971db7acf7a35c32eb74d675a4e12e288e6be">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="1d53fb07984298253aad64bfa4236b7167ee3d4d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="ba613ae583a706131c45e885f65d428d4a541a81"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d6a27295acb0a25926bf6290dd2532a7f9027864"/>
--- 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="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="1d53fb07984298253aad64bfa4236b7167ee3d4d"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="ba613ae583a706131c45e885f65d428d4a541a81"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="6fa7a4936414ceb4055fd27f7a30e76790f834fb"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d6a27295acb0a25926bf6290dd2532a7f9027864"/>
   <!-- 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="e06971db7acf7a35c32eb74d675a4e12e288e6be">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="1d53fb07984298253aad64bfa4236b7167ee3d4d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="ba613ae583a706131c45e885f65d428d4a541a81"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d6a27295acb0a25926bf6290dd2532a7f9027864"/>
--- 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="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="1d53fb07984298253aad64bfa4236b7167ee3d4d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="ba613ae583a706131c45e885f65d428d4a541a81"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d6a27295acb0a25926bf6290dd2532a7f9027864"/>
   <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": "1d53fb07984298253aad64bfa4236b7167ee3d4d", 
+        "git_revision": "ba613ae583a706131c45e885f65d428d4a541a81", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "08a288892d8f0b41a960104150fba34f113629e6", 
+    "revision": "4705c493adb5c766382b27e4fbff42f7447900e9", 
     "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="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="1d53fb07984298253aad64bfa4236b7167ee3d4d"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="ba613ae583a706131c45e885f65d428d4a541a81"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d6a27295acb0a25926bf6290dd2532a7f9027864"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform_bionic" path="bionic" remote="b2g" revision="1a2a32eda22ef2cd18f57f423a5e7b22a105a6f8"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="746bc48f34f5060f90801925dcdd964030c1ab6d"/>
--- 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="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="1d53fb07984298253aad64bfa4236b7167ee3d4d"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="ba613ae583a706131c45e885f65d428d4a541a81"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <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" remote="b2g" revision="1a2a32eda22ef2cd18f57f423a5e7b22a105a6f8"/>
--- 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="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="1d53fb07984298253aad64bfa4236b7167ee3d4d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="ba613ae583a706131c45e885f65d428d4a541a81"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d6a27295acb0a25926bf6290dd2532a7f9027864"/>
   <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="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="1d53fb07984298253aad64bfa4236b7167ee3d4d"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="ba613ae583a706131c45e885f65d428d4a541a81"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="d6a27295acb0a25926bf6290dd2532a7f9027864"/>
   <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/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -212,19 +212,25 @@
 
         if (err == "cspBlocked") {
           // Remove the "Try again" button for CSP violations, since it's
           // almost certainly useless. (Bug 553180)
           document.getElementById("errorTryAgain").style.display = "none";
         }
 
         window.addEventListener("AboutNetErrorOptions", function(evt) {
-        // Pinning errors are of type nssFailure2 (don't ask me why)
+        // Pinning errors are of type nssFailure2
           if (getErrorCode() == "nssFailure2" && !errTitle.hasAttribute("sslv3")) {
-          // TODO: and the pref is set...
+            var learnMoreLink = document.getElementById("learnMoreLink");
+            // nssFailure2 also gets us other non-overrideable errors. Choose
+            // a "learn more" link based on description:
+            if (getDescription().contains("mozilla_pkix_error_key_pinning_failure")) {
+              learnMoreLink.href = "https://support.mozilla.org/kb/certificate-pinning-reports";
+            }
+
             var options = JSON.parse(evt.detail);
             if (options && options.enabled) {
               var checkbox = document.getElementById('automaticallyReportInFuture');
               showCertificateErrorReporting();
               if (options.automatic) {
                 // set the checkbox
                 checkbox.checked = true;
               }
@@ -471,18 +477,17 @@
       </div>
 
       <div id="certificateErrorReportingPanel">
         <p>&errorReporting.longDesc;</p>
         <p>
           <input type="checkbox" id="automaticallyReportInFuture" />
           <label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic;</label>
         </p>
-        <!-- TODO add link to relevant page on sumo -->
-        <a href="https://support.mozilla.org/kb/certificate-pinning-reports" target="new">&errorReporting.learnMore;</a>
+        <a href="https://support.mozilla.org/kb/tls-error-reports" id="learnMoreLink" target="new">&errorReporting.learnMore;</a>
         <span id="reportingState">
           <button id="reportCertificateError">&errorReporting.report;</button>
           <button id="reportCertificateErrorRetry">&errorReporting.tryAgain;</button>
           <span id="reportSendingMessage">&errorReporting.sending;</span>
           <span id="reportSentMessage">&errorReporting.sent;</span>
         </span>
       </div>
 
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1263,16 +1263,19 @@
       <handler event="command"><![CDATA[
         let target = event.originalTarget;
         if (target.classList.contains("addengine-item")) {
           // On success, hide and reshow the panel to show the new engine.
           let installCallback = {
             onSuccess: function(engine) {
               event.target.hidePopup();
               BrowserSearch.searchBar.openSuggestionsPanel();
+            },
+            onError: function(errorCode) {
+              Components.utils.reportError("Error adding search engine: " + errorCode);
             }
           }
           Services.search.addEngine(target.getAttribute("uri"),
                                     Ci.nsISearchEngine.DATA_XML,
                                     target.getAttribute("image"), false,
                                     installCallback);
         }
       ]]></handler>
--- a/browser/components/loop/content/libs/l10n.js
+++ b/browser/components/loop/content/libs/l10n.js
@@ -35,17 +35,16 @@
       return name in args ? args[name] : '{{' + name + '}}';
     });
   }
 
   // translate a string
   function translateString(key, args, fallback) {
     if (args && args.num) {
       var num = args && args.num;
-      delete args.num;
     }
     var data = getL10nData(key, num);
     if (!data && fallback)
       data = {textContent: fallback};
     if (!data)
       return '{{' + key + '}}';
     return substArguments(data.textContent, args);
   }
--- a/browser/components/loop/test/desktop-local/index.html
+++ b/browser/components/loop/test/desktop-local/index.html
@@ -68,16 +68,17 @@
   <!-- Test scripts -->
   <script src="conversationAppStore_test.js"></script>
   <script src="client_test.js"></script>
   <script src="conversation_test.js"></script>
   <script src="panel_test.js"></script>
   <script src="roomViews_test.js"></script>
   <script src="conversationViews_test.js"></script>
   <script src="contacts_test.js"></script>
+  <script src="l10n_test.js"></script>
   <script>
     // Stop the default init functions running to avoid conflicts in tests
     document.removeEventListener('DOMContentLoaded', loop.panel.init);
     document.removeEventListener('DOMContentLoaded', loop.conversation.init);
 
     describe("Uncaught Error Check", function() {
       it("should load the tests without errors", function() {
         expect(uncaughtError && uncaughtError.message).to.be.undefined;
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/desktop-local/l10n_test.js
@@ -0,0 +1,37 @@
+/* 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 expect = chai.expect;
+
+describe("document.mozL10n", function() {
+  "use strict";
+
+  var fakeMozLoop;
+
+  beforeEach(function() {
+    fakeMozLoop = {
+      locale: "en-US",
+      getStrings: function(key) {
+        if (key === "plural") {
+          return '{"textContent":"{{num}} plural form;{{num}} plural forms"}';
+        }
+
+        return '{"textContent":"' + key + '"}';
+      },
+      getPluralForm: function(num, string) {
+        return string.split(";")[num === 0 ? 0 : 1];
+      }
+    };
+
+    document.mozL10n.initialize(fakeMozLoop);
+  });
+
+  it("should get a simple string", function() {
+    expect(document.mozL10n.get("test")).eql("test");
+  });
+
+  it("should get a plural form", function() {
+    expect(document.mozL10n.get("plural", {num:10})).eql("10 plural forms");
+  });
+});
--- a/browser/components/loop/test/functional/serversetup.py
+++ b/browser/components/loop/test/functional/serversetup.py
@@ -18,27 +18,27 @@ from config import *
 
 CONTENT_SERVER_COMMAND = ["make", "runserver"]
 CONTENT_SERVER_ENV = os.environ.copy()
 # Set PORT so that it does not interfere with any other
 # development server that might be running
 CONTENT_SERVER_ENV.update({"PORT": str(CONTENT_SERVER_PORT),
                            "LOOP_SERVER_PORT": str(LOOP_SERVER_PORT)})
 
-WEB_APP_URL = "http://localhost:" + str(CONTENT_SERVER_PORT) + \
-              "/content/#call/{token}"
+ROOMS_WEB_APP_URL = "http://localhost:" + str(CONTENT_SERVER_PORT) + \
+  "/content/{token}"
 
 LOOP_SERVER_COMMAND = ["make", "runserver"]
 LOOP_SERVER_ENV = os.environ.copy()
 # Set PORT so that it does not interfere with any other
 # development server that might be running
 LOOP_SERVER_ENV.update({"NODE_ENV": "dev",
                         "PORT": str(LOOP_SERVER_PORT),
                         "SERVER_ADDRESS": "localhost:" + str(LOOP_SERVER_PORT),
-                        "WEB_APP_URL": WEB_APP_URL})
+                        "ROOMS_WEB_APP_URL": ROOMS_WEB_APP_URL})
 
 
 class LoopTestServers:
     def __init__(self):
         self.loop_server = self.start_loop_server()
         self.content_server = self.start_content_server()
 
     @staticmethod
--- a/browser/components/loop/test/functional/test_1_browser_call.py
+++ b/browser/components/loop/test/functional/test_1_browser_call.py
@@ -122,25 +122,24 @@ class Test1BrowserCall(MarionetteTestCas
 
         # Join the room
         join_button = self.wait_for_element_displayed(By.CLASS_NAME,
                                                       "btn-join")
         join_button.click()
 
     # Assumes the standlone or the conversation window is selected first.
     def check_remote_video(self):
-        # TODO: This is disabled currently due to bug 1122486
-        # video_wrapper = self.wait_for_element_displayed(By.CSS_SELECTOR, ".media .OT_subscriber .OT_video-container", 20)
-        # video = self.wait_for_subelement_displayed(video_wrapper, By.TAG_NAME, "video")
+        video_wrapper = self.wait_for_element_displayed(
+            By.CSS_SELECTOR,
+            ".media .OT_subscriber .OT_video-container", 20)
+        video = self.wait_for_subelement_displayed(
+            video_wrapper, By.TAG_NAME, "video")
 
-        # self.wait_for_element_attribute_to_be_false(video, "paused")
-        # self.assertEqual(video.get_attribute("ended"), "false")
-
-        # Due to the above waits being disabled, we do a sleep.
-        sleep(15)
+        self.wait_for_element_attribute_to_be_false(video, "paused")
+        self.assertEqual(video.get_attribute("ended"), "false")
 
     def standalone_check_remote_video(self):
         self.switch_to_standalone()
         self.check_remote_video()
 
     def local_check_remote_video(self):
         self.switch_to_chatbox()
         self.check_remote_video()
--- a/browser/components/preferences/advanced.js
+++ b/browser/components/preferences/advanced.js
@@ -26,16 +26,22 @@ var gAdvancedPane = {
       advancedPrefs.selectedTab = document.getElementById(extraArgs["advancedTab"]);
     } else {
       var preference = document.getElementById("browser.preferences.advanced.selectedTabIndex");
       if (preference.value !== null)
         advancedPrefs.selectedIndex = preference.value;
     }
 
 #ifdef MOZ_UPDATER
+    let onUnload = function () {
+      window.removeEventListener("unload", onUnload, false);
+      Services.prefs.removeObserver("app.update.", this);
+    }.bind(this);
+    window.addEventListener("unload", onUnload, false);
+    Services.prefs.addObserver("app.update.", this, false);
     this.updateReadPrefs();
 #endif
     this.updateOfflineApps();
 #ifdef MOZ_CRASHREPORTER
     this.initSubmitCrashes();
 #endif
     this.initTelemetry();
 #ifdef MOZ_SERVICES_HEALTHREPORT
@@ -817,10 +823,20 @@ var gAdvancedPane = {
   /**
    * Displays a dialog from which the user can manage his security devices.
    */
   showSecurityDevices: function ()
   {
     document.documentElement.openWindow("mozilla:devicemanager",
                                         "chrome://pippki/content/device_manager.xul",
                                         "", null);
-  }
+  },
+
+#ifdef MOZ_UPDATER
+  observe: function (aSubject, aTopic, aData) {
+    switch(aTopic) {
+      case "nsPref:changed":
+        this.updateReadPrefs();
+        break;
+    }
+  },
+#endif
 };
--- a/browser/components/preferences/in-content/advanced.js
+++ b/browser/components/preferences/in-content/advanced.js
@@ -24,16 +24,22 @@ var gAdvancedPane = {
     this._inited = true;
     var advancedPrefs = document.getElementById("advancedPrefs");
 
     var preference = document.getElementById("browser.preferences.advanced.selectedTabIndex");
     if (preference.value !== null)
         advancedPrefs.selectedIndex = preference.value;
 
 #ifdef MOZ_UPDATER
+    let onUnload = function () {
+      window.removeEventListener("unload", onUnload, false);
+      Services.prefs.removeObserver("app.update.", this);
+    }.bind(this);
+    window.addEventListener("unload", onUnload, false);
+    Services.prefs.addObserver("app.update.", this, false);
     this.updateReadPrefs();
 #endif
     this.updateOfflineApps();
 #ifdef MOZ_CRASHREPORTER
     this.initSubmitCrashes();
 #endif
     this.initTelemetry();
 #ifdef MOZ_SERVICES_HEALTHREPORT
@@ -836,10 +842,20 @@ var gAdvancedPane = {
   /**
    * Displays a dialog from which the user can manage his security devices.
    */
   showSecurityDevices: function ()
   {
     openDialog("chrome://pippki/content/device_manager.xul",
                "mozilla:devicemanager",
                "modal=yes", null);
-  }
+  },
+
+#ifdef MOZ_UPDATER
+  observe: function (aSubject, aTopic, aData) {
+    switch(aTopic) {
+      case "nsPref:changed":
+        this.updateReadPrefs();
+        break;
+    }
+  },
+#endif
 };
--- a/browser/components/uitour/UITour-lib.js
+++ b/browser/components/uitour/UITour-lib.js
@@ -93,16 +93,25 @@ if (typeof Mozilla == 'undefined') {
   };
 
 	Mozilla.UITour.registerPageID = function(pageID) {
 		_sendEvent('registerPageID', {
 			pageID: pageID
 		});
 	};
 
+	Mozilla.UITour.showHeartbeat = function(message, thankyouMessage, flowId, engagementURL) {
+		_sendEvent('showHeartbeat', {
+			message: message,
+			thankyouMessage: thankyouMessage,
+			flowId: flowId,
+			engagementURL: engagementURL
+		});
+	};
+
 	Mozilla.UITour.showHighlight = function(target, effect) {
 		_sendEvent('showHighlight', {
 			target: target,
 			effect: effect
 		});
 	};
 
 	Mozilla.UITour.hideHighlight = function() {
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -8,16 +8,18 @@ this.EXPORTED_SYMBOLS = ["UITour", "UITo
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
+Cu.importGlobalProperties(["URL"]);
+
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
   "resource://gre/modules/LightweightThemeManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ResetProfile",
   "resource://gre/modules/ResetProfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
   "resource://gre/modules/UITelemetry.jsm");
@@ -113,21 +115,25 @@ this.UITour = {
                                                         "toolbarbutton-icon");
       },
       widgetName: "PanelUI-customize",
     }],
     ["devtools",    {query: "#developer-button"}],
     ["help",        {query: "#PanelUI-help"}],
     ["home",        {query: "#home-button"}],
     ["forget", {
+      allowAdd: true,
       query: "#panic-button",
       widgetName: "panic-button",
-      allowAdd: true,
     }],
-    ["loop",        {query: "#loop-button"}],
+    ["loop",        {
+      allowAdd: true,
+      query: "#loop-button",
+      widgetName: "loop-button",
+    }],
     ["loop-newRoom", {
       infoPanelPosition: "leftcenter topright",
       query: (aDocument) => {
         let loopBrowser = aDocument.querySelector("#loop-notification-panel > #loop-panel-iframe");
         if (!loopBrowser) {
           return null;
         }
         // Use the parentElement full-width container of the button so our arrow
@@ -324,17 +330,16 @@ this.UITour = {
       return;
     }
 
     Services.prefs.setCharPref(PREF_SEENPAGEIDS,
                                JSON.stringify([...this.seenPageIDs]));
   },
 
   onPageEvent: function(aMessage, aEvent) {
-    let contentDocument = null;
     let browser = aMessage.target;
     let window = browser.ownerDocument.defaultView;
     let tab = window.gBrowser.getTabForBrowser(browser);
     let messageManager = browser.messageManager;
 
     log.debug("onPageEvent:", aEvent.detail, aMessage);
 
     if (typeof aEvent.detail != "object") {
@@ -359,20 +364,16 @@ this.UITour = {
         !BACKGROUND_PAGE_ACTIONS_ALLOWED.has(action)) {
       log.warn("Ignoring disallowed action from a hidden page:", action);
       return false;
     }
 
     // Do this before bailing if there's no tab, so later we can pick up the pieces:
     window.gBrowser.tabContainer.addEventListener("TabSelect", this);
 
-    if (!window.gMultiProcessBrowser) { // Non-e10s. See bug 1089000.
-      contentDocument = browser.contentWindow.document;
-    }
-
     switch (action) {
       case "registerPageID": {
         // This is only relevant if Telemtry is enabled.
         if (!UITelemetry.enabled) {
           log.debug("registerPageID: Telemery disabled, not doing anything");
           break;
         }
 
@@ -386,16 +387,39 @@ this.UITour = {
 
         this.addSeenPageID(data.pageID);
         this.pageIDSourceBrowsers.set(browser, data.pageID);
         this.setTelemetryBucket(data.pageID);
 
         break;
       }
 
+      case "showHeartbeat": {
+        // Validate the input parameters.
+        if (typeof data.message !== "string" || data.message === "") {
+          log.error("showHeartbeat: Invalid message specified.");
+          break;
+        }
+
+        if (typeof data.thankyouMessage !== "string" || data.thankyouMessage === "") {
+          log.error("showHeartbeat: Invalid thank you message specified.");
+          break;
+        }
+
+        if (typeof data.flowId !== "string" || data.flowId === "") {
+          log.error("showHeartbeat: Invalid flowId specified.");
+          break;
+        }
+
+        // Finally show the Heartbeat UI.
+        this.showHeartbeat(window, messageManager, data.message, data.thankyouMessage, data.flowId,
+                           data.engagementURL);
+        break;
+      }
+
       case "showHighlight": {
         let targetPromise = this.getTarget(window, data.target);
         targetPromise.then(target => {
           if (!target.node) {
             log.error("UITour: Target could not be resolved: " + data.target);
             return;
           }
           let effect = undefined;
@@ -502,16 +526,17 @@ this.UITour = {
         try {
           uri = Services.io.newURI(data.url, null, null);
         } catch (e) {
           log.warn("startUrlbarCapture: Malformed URL specified");
           return false;
         }
 
         let secman = Services.scriptSecurityManager;
+        let contentDocument = browser.contentWindow.document;
         let principal = contentDocument.nodePrincipal;
         let flags = secman.DISALLOW_INHERIT_PRINCIPAL;
         try {
           secman.checkLoadURIWithPrincipal(principal, uri, flags);
         } catch (e) {
           log.warn("startUrlbarCapture: Orginating page doesn't have permission to open specified URL");
           return false;
         }
@@ -544,17 +569,17 @@ this.UITour = {
         this.setConfiguration(data.configuration, data.value);
         break;
       }
 
       case "showFirefoxAccounts": {
         // 'signup' is the only action that makes sense currently, so we don't
         // accept arbitrary actions just to be safe...
         // We want to replace the current tab.
-        contentDocument.location.href = "about:accounts?action=signup&entrypoint=uitour";
+        browser.loadURI("about:accounts?action=signup&entrypoint=uitour");
         break;
       }
 
       case "resetFirefox": {
         // Open a reset profile dialog window.
         ResetProfile.openConfirmationDialog(window);
         break;
       }
@@ -955,16 +980,147 @@ this.UITour = {
       LightweightThemeManager.previewTheme(data);
   },
 
   resetTheme: function() {
     LightweightThemeManager.resetPreview();
   },
 
   /**
+   * Show the Heartbeat UI to request user feedback. This function reports back to the
+   * caller using |notify|. The notification event name reflects the current status the UI
+   * is in (either "Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed" or
+   * "Heartbeat:Voted"). When a "Heartbeat:Voted" event is notified the data payload contains
+   * a |score| field which holds the rating picked by the user.
+   * Please note that input parameters are already validated by the caller.
+   *
+   * @param aChromeWindow
+   *        The chrome window that the heartbeat notification is displayed in.
+   * @param aMessageManager
+   *        The message manager to communicate with the API caller.
+   * @param aMessage
+   *        The message, or question, to display on the notification.
+   * @param aThankyouMessage
+   *        The thank you message to display after user votes.
+   * @param aFlowId
+   *        An identifier for this rating flow. Please note that this is only used to
+   *        identify the notification box.
+   * @param [aEngagementURL]
+   *        The engagement URL to open in a new tab once user has voted. If this is null
+   *        or invalid, no new tab is opened.
+   */
+  showHeartbeat: function(aChromeWindow, aMessageManager, aMessage, aThankyouMessage, aFlowId,
+                          aEngagementURL = null) {
+    let nb = aChromeWindow.document.getElementById("high-priority-global-notificationbox");
+
+    // Create the notification. Prefix its ID to decrease the chances of collisions.
+    let notice = nb.appendNotification(aMessage, "heartbeat-" + aFlowId,
+      "chrome://branding/content/icon64.png", nb.PRIORITY_INFO_HIGH, null, function() {
+        // Let the consumer know the notification bar was closed. This also happens
+        // after voting.
+        this.notify("Heartbeat:NotificationClosed", { flowId: aFlowId, timestamp: Date.now() });
+    }.bind(this));
+
+    // Get the elements we need to style.
+    let messageImage =
+      aChromeWindow.document.getAnonymousElementByAttribute(notice, "anonid", "messageImage");
+    let messageText =
+      aChromeWindow.document.getAnonymousElementByAttribute(notice, "anonid", "messageText");
+
+    // Create the fragment holding the rating UI.
+    let frag = aChromeWindow.document.createDocumentFragment();
+
+    // Build the Heartbeat star rating.
+    const numStars = 5;
+    let ratingContainer = aChromeWindow.document.createElement("hbox");
+    ratingContainer.id = "star-rating-container";
+
+    for (let i = 0; i < numStars; i++) {
+      // Create a star rating element.
+      let ratingElement = aChromeWindow.document.createElement("toolbarbutton");
+
+      // Style it.
+      let starIndex = numStars - i;
+      ratingElement.className = "plain star-x";
+      ratingElement.id = "star" + starIndex;
+      ratingElement.setAttribute("data-score", starIndex);
+
+      // Add the click handler.
+      ratingElement.addEventListener("click", function (evt) {
+        let rating = Number(evt.target.getAttribute("data-score"), 10);
+
+        // Let the consumer know user voted.
+        this.notify("Heartbeat:Voted", { flowId: aFlowId, score: rating, timestamp: Date.now() });
+
+        // Display the Heart and make it pulse twice.
+        notice.image = "chrome://browser/skin/heartbeat-icon.svg";
+        notice.label = aThankyouMessage;
+        messageImage.classList.remove("pulse-onshow");
+        messageImage.classList.add("pulse-twice");
+
+        // Remove all the children of the notice (rating container
+        // and the flex).
+        while (notice.firstChild) {
+          notice.removeChild(notice.firstChild);
+        }
+
+        // Make sure that we have a valid URL. If we haven't, do not open the engagement page.
+        let engagementURL = null;
+        try {
+          engagementURL = new URL(aEngagementURL);
+        } catch (error) {
+          log.error("showHeartbeat: Invalid URL specified.");
+        }
+
+        // Just open the engagement tab if we have a valid engagement URL.
+        if (engagementURL) {
+          // Append the score data to the engagement URL.
+          engagementURL.searchParams.append("type", "stars");
+          engagementURL.searchParams.append("score", rating);
+          engagementURL.searchParams.append("flowid", aFlowId);
+
+          // Open the engagement URL in a new tab.
+          aChromeWindow.gBrowser.selectedTab =
+            aChromeWindow.gBrowser.addTab(engagementURL.toString(), {
+              owner: aChromeWindow.gBrowser.selectedTab,
+              relatedToCurrent: true
+            });
+        }
+
+        // Remove the notification bar after 3 seconds.
+        aChromeWindow.setTimeout(() => {
+          nb.removeNotification(notice);
+        }, 3000);
+      }.bind(this));
+
+      // Add it to the container.
+      ratingContainer.appendChild(ratingElement);
+    }
+
+    frag.appendChild(ratingContainer);
+
+    // Make sure the stars are not pushed to the right by the spacer.
+    let rightSpacer = aChromeWindow.document.createElement("spacer");
+    rightSpacer.flex = 20;
+    frag.appendChild(rightSpacer);
+
+    let leftSpacer = messageText.nextSibling;
+    leftSpacer.flex = 0;
+
+    // Append the fragment and apply the styling.
+    notice.appendChild(frag);
+    notice.classList.add("heartbeat");
+    messageImage.classList.add("heartbeat", "pulse-onshow");
+    messageText.classList.add("heartbeat");
+
+    // Let the consumer know the notification was shown.
+    this.notify("Heartbeat:NotificationOffered", { flowId: aFlowId, timestamp: Date.now() });
+  },
+
+  /**
    * @param aChromeWindow The chrome window that the highlight is in. Necessary since some targets
    *                      are in a sub-frame so the defaultView is not the same as the chrome
    *                      window.
    * @param aTarget    The element to highlight.
    * @param aEffect    (optional) The effect to use from UITour.highlightEffects or "none".
    * @see UITour.highlightEffects
    */
   showHighlight: function(aChromeWindow, aTarget, aEffect = "none") {
--- a/browser/components/uitour/test/browser.ini
+++ b/browser/components/uitour/test/browser.ini
@@ -12,16 +12,18 @@ skip-if = e10s # Bug 941428 - UITour.jsm
 # [browser_UITour3.js] Bug 1113038
 # skip-if = os == "linux" || e10s # Linux: Bug 986760, Bug 989101; e10s: Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_availableTargets.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_detach_tab.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_annotation_size_attributes.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
+[browser_UITour_heartbeat.js]
+skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
 [browser_UITour_loop.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
 [browser_UITour_modalDialog.js]
 run-if = os == "mac" # modal dialog disabling only working on OS X
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_observe.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
 [browser_UITour_panel_close_annotation.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_heartbeat.js
@@ -0,0 +1,231 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let gTestTab;
+let gContentAPI;
+let gContentWindow;
+let notificationBox = document.getElementById("high-priority-global-notificationbox");
+
+Components.utils.import("resource:///modules/UITour.jsm");
+
+function test() {
+  UITourTest();
+}
+
+/**
+ * Simulate a click on a rating element in the Heartbeat notification.
+ *
+ * @param aId
+ *        The id of the notification box.
+ * @param aScore
+ *        The score related to the rating element we want to click on.
+ */
+function simulateVote(aId, aScore) {
+  // UITour.jsm prefixes the notification box ID with "heartbeat-" to prevent collisions.
+  let notification = notificationBox.getNotificationWithValue("heartbeat-" + aId);
+
+  let ratingContainer = notification.childNodes[0];
+  ok(ratingContainer, "The notification has a valid rating container.");
+
+  let ratingElement = ratingContainer.getElementsByAttribute("data-score", aScore);
+  ok(ratingElement[0], "The rating container contains the requested rating element.");
+
+  ratingElement[0].click();
+}
+
+/**
+ * Remove the notification box.
+ *
+ * @param aId
+ *        The id of the notification box to remove.
+ */
+function cleanUpNotification(aId) {
+  let notification = notificationBox.getNotificationWithValue("heartbeat-" + aId);
+  notificationBox.removeNotification(notification);
+}
+
+let tests = [
+  /**
+   * Check that the "stars" heartbeat UI correctly shows and closes.
+   */
+  function test_heartbeat_stars_show(done) {
+    let flowId = "ui-ratefirefox-" + Math.random();
+    let engagementURL = "http://example.com";
+
+    gContentAPI.observe(function (aEventName, aData) {
+      switch (aEventName) {
+        case "Heartbeat:NotificationOffered": {
+          info("'Heartbeat:Offered' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          cleanUpNotification(flowId);
+          break;
+        }
+        case "Heartbeat:NotificationClosed": {
+          info("'Heartbeat:NotificationClosed' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          done();
+          break;
+        }
+        default:
+          // We are not expecting other states for this test.
+          ok(false, "Unexpected notification received: " + aEventName);
+      }
+    });
+
+    gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, engagementURL);
+  },
+
+  /**
+   * Test that the heartbeat UI correctly works with null engagement URL.
+   */
+  function test_heartbeat_null_engagementURL(done) {
+    let flowId = "ui-ratefirefox-" + Math.random();
+    let originalTabCount = gBrowser.tabs.length;
+
+    gContentAPI.observe(function (aEventName, aData) {
+      switch (aEventName) {
+        case "Heartbeat:NotificationOffered": {
+          info("'Heartbeat:Offered' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          // The UI was just shown. We can simulate a click on a rating element (i.e., "star").
+          simulateVote(flowId, 2);
+          break;
+        }
+        case "Heartbeat:Voted": {
+          info("'Heartbeat:Voted' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          break;
+        }
+        case "Heartbeat:NotificationClosed": {
+          info("'Heartbeat:NotificationClosed' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          is(gBrowser.tabs.length, originalTabCount, "No engagement tab should be opened.");
+          done();
+          break;
+        }
+        default:
+          // We are not expecting other states for this test.
+          ok(false, "Unexpected notification received: " + aEventName);
+      }
+    });
+
+    gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, null);
+  },
+
+   /**
+   * Test that the heartbeat UI correctly works with an invalid, but non null, engagement URL.
+   */
+  function test_heartbeat_invalid_engagement_URL(done) {
+    let flowId = "ui-ratefirefox-" + Math.random();
+    let originalTabCount = gBrowser.tabs.length;
+    let invalidEngagementURL = "invalidEngagement";
+
+    gContentAPI.observe(function (aEventName, aData) {
+      switch (aEventName) {
+        case "Heartbeat:NotificationOffered": {
+          info("'Heartbeat:Offered' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          // The UI was just shown. We can simulate a click on a rating element (i.e., "star").
+          simulateVote(flowId, 2);
+          break;
+        }
+        case "Heartbeat:Voted": {
+          info("'Heartbeat:Voted' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          break;
+        }
+        case "Heartbeat:NotificationClosed": {
+          info("'Heartbeat:NotificationClosed' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          is(gBrowser.tabs.length, originalTabCount, "No engagement tab should be opened.");
+          done();
+          break;
+        }
+        default:
+          // We are not expecting other states for this test.
+          ok(false, "Unexpected notification received: " + aEventName);
+      }
+    });
+
+    gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, invalidEngagementURL);
+  },
+
+  /**
+   * Test that the score is correctly reported.
+   */
+  function test_heartbeat_stars_vote(done) {
+    const expectedScore = 4;
+    let flowId = "ui-ratefirefox-" + Math.random();
+
+    gContentAPI.observe(function (aEventName, aData) {
+      switch (aEventName) {
+        case "Heartbeat:NotificationOffered": {
+          info("'Heartbeat:Offered' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          // The UI was just shown. We can simulate a click on a rating element (i.e., "star").
+          simulateVote(flowId, expectedScore);
+          break;
+        }
+        case "Heartbeat:Voted": {
+          info("'Heartbeat:Voted' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          is(aData.score, expectedScore, "Should report a score of " + expectedScore);
+          done();
+          break;
+        }
+        case "Heartbeat:NotificationClosed": {
+          info("'Heartbeat:NotificationClosed' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          break;
+        }
+        default:
+          // We are not expecting other states for this test.
+          ok(false, "Unexpected notification received: " + aEventName);
+      }
+    });
+
+    gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, null);
+  },
+
+  /**
+   * Test that the engagement page is correctly opened when voting.
+   */
+  function test_heartbeat_engagement_tab(done) {
+    let engagementURL = "http://example.com";
+    let flowId = "ui-ratefirefox-" + Math.random();
+    let originalTabCount = gBrowser.tabs.length;
+    const expectedTabCount = originalTabCount + 1;
+
+    gContentAPI.observe(function (aEventName, aData) {
+      switch (aEventName) {
+        case "Heartbeat:NotificationOffered": {
+          info("'Heartbeat:Offered' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          // The UI was just shown. We can simulate a click on a rating element (i.e., "star").
+          simulateVote(flowId, 1);
+          break;
+        }
+        case "Heartbeat:Voted": {
+          info("'Heartbeat:Voted' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          break;
+        }
+        case "Heartbeat:NotificationClosed": {
+          info("'Heartbeat:NotificationClosed' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          is(gBrowser.tabs.length, expectedTabCount, "Engagement URL should open in a new tab.");
+          gBrowser.removeCurrentTab();
+          done();
+          break;
+        }
+        default:
+          // We are not expecting other states for this test.
+          ok(false, "Unexpected notification received: " + aEventName);
+      }
+    });
+
+    gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, engagementURL);
+  }
+];
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -1289,19 +1289,23 @@ MarkupView.prototype = {
   /**
    * Tear down the markup panel.
    */
   destroy: function() {
     if (this._destroyer) {
       return this._destroyer;
     }
 
+    this._destroyer = promise.resolve();
+
     // Note that if the toolbox is closed, this will work fine, but will fail
     // in case the browser is closed and will trigger a noSuchActor message.
-    this._destroyer = this._hideBoxModel();
+    // We ignore the promise that |_hideBoxModel| returns, since we should still
+    // proceed with the rest of destruction if it fails.
+    this._hideBoxModel();
 
     this._elt.removeEventListener("click", this._onMouseClick, false);
 
     this._hoveredNode = null;
     this._inspector.toolbox.off("picker-node-hovered", this._onToolboxPickerHover);
 
     this.htmlEditor.destroy();
     this.htmlEditor = null;
--- a/browser/devtools/webaudioeditor/controller.js
+++ b/browser/devtools/webaudioeditor/controller.js
@@ -88,42 +88,34 @@ let WebAudioEditorController = {
    */
   reset: function () {
     $("#content").hidden = true;
     ContextView.resetUI();
     InspectorView.resetUI();
     PropertiesView.resetUI();
   },
 
-  // Since node create and connect are probably executed back to back,
-  // and the controller's `_onCreateNode` needs to look up type,
-  // the edge creation could be called before the graph node is actually
-  // created. This way, we can check and listen for the event before
-  // adding an edge.
-  _waitForNodeCreation: function (sourceActor, destActor) {
-    let deferred = defer();
-    let source = gAudioNodes.get(sourceActor.actorID);
-    let dest = gAudioNodes.get(destActor.actorID);
+  // Since node events (create, disconnect, connect) are all async,
+  // we have to make sure to wait that the node has finished creating
+  // before performing an operation on it.
+  getNode: function* (nodeActor) {
+    let id = nodeActor.actorID;
+    let node = gAudioNodes.get(id);
 
-    if (!source || !dest) {
+    if (!node) {
+      let { resolve, promise } = defer();
       gAudioNodes.on("add", function createNodeListener (createdNode) {
-        if (sourceActor.actorID === createdNode.id)
-          source = createdNode;
-        if (destActor.actorID === createdNode.id)
-          dest = createdNode;
-        if (source && dest) {
+        if (createdNode.id === id) {
           gAudioNodes.off("add", createNodeListener);
-          deferred.resolve([source, dest]);
+          resolve(createdNode);
         }
       });
+      node = yield promise;
     }
-    else {
-      deferred.resolve([source, dest]);
-    }
-    return deferred.promise;
+    return node;
   },
 
   /**
    * Fired when the devtools theme changes (light, dark, etc.)
    * so that the graph can update marker styling, as that
    * cannot currently be done with CSS.
    */
   _onThemeChange: function (event, data) {
@@ -197,35 +189,38 @@ let WebAudioEditorController = {
   _onDestroyNode: function (nodeActor) {
     gAudioNodes.remove(gAudioNodes.get(nodeActor.actorID));
   },
 
   /**
    * Called when a node is connected to another node.
    */
   _onConnectNode: Task.async(function* ({ source: sourceActor, dest: destActor }) {
-    let [source, dest] = yield WebAudioEditorController._waitForNodeCreation(sourceActor, destActor);
+    let source = yield WebAudioEditorController.getNode(sourceActor);
+    let dest = yield WebAudioEditorController.getNode(destActor);
     source.connect(dest);
   }),
 
   /**
    * Called when a node is conneceted to another node's AudioParam.
    */
   _onConnectParam: Task.async(function* ({ source: sourceActor, dest: destActor, param }) {
-    let [source, dest] = yield WebAudioEditorController._waitForNodeCreation(sourceActor, destActor);
+    let source = yield WebAudioEditorController.getNode(sourceActor);
+    let dest = yield WebAudioEditorController.getNode(destActor);
     source.connect(dest, param);
   }),
 
   /**
    * Called when a node is disconnected.
    */
-  _onDisconnectNode: function(nodeActor) {
-    let node = gAudioNodes.get(nodeActor.actorID);
+  _onDisconnectNode: Task.async(function* (nodeActor) {
+    let node = yield WebAudioEditorController.getNode(nodeActor);
     node.disconnect();
-  },
+  }),
 
   /**
    * Called when a node param is changed.
    */
-  _onChangeParam: function({ actor, param, value }) {
-    window.emit(EVENTS.CHANGE_PARAM, gAudioNodes.get(actor.actorID), param, value);
-  }
+  _onChangeParam: Task.async(function* ({ actor, param, value }) {
+    let node = yield WebAudioEditorController.getNode(actor);
+    window.emit(EVENTS.CHANGE_PARAM, node, param, value);
+  })
 };
--- a/browser/devtools/webaudioeditor/test/browser.ini
+++ b/browser/devtools/webaudioeditor/test/browser.ini
@@ -6,16 +6,17 @@ support-files =
   doc_simple-node-creation.html
   doc_buffer-and-array.html
   doc_media-node-creation.html
   doc_destroy-nodes.html
   doc_connect-param.html
   doc_connect-multi-param.html
   doc_iframe-context.html
   doc_automation.html
+  doc_bug_1125817.html
   440hz_sine.ogg
   head.js
 
 [browser_audionode-actor-get-param-flags.js]
 [browser_audionode-actor-get-params-01.js]
 [browser_audionode-actor-get-params-02.js]
 [browser_audionode-actor-get-set-param.js]
 [browser_audionode-actor-get-type.js]
@@ -34,16 +35,17 @@ support-files =
 [browser_wa_destroy-node-01.js]
 
 [browser_wa_first-run.js]
 [browser_wa_reset-01.js]
 [browser_wa_reset-02.js]
 [browser_wa_reset-03.js]
 [browser_wa_reset-04.js]
 [browser_wa_navigate.js]
+[browser_wa_controller-01.js]
 
 [browser_wa_graph-click.js]
 [browser_wa_graph-markers.js]
 [browser_wa_graph-render-01.js]
 [browser_wa_graph-render-02.js]
 [browser_wa_graph-render-03.js]
 [browser_wa_graph-render-04.js]
 [browser_wa_graph-render-05.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webaudioeditor/test/browser_wa_controller-01.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 1125817
+ * Tests to ensure that disconnecting a node immediately
+ * after creating it does not fail.
+ */
+
+const BUG_1125817_URL = EXAMPLE_URL + "doc_bug_1125817.html";
+
+add_task(function*() {
+  let { target, panel } = yield initWebAudioEditor(BUG_1125817_URL);
+  let { panelWin } = panel;
+  let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
+
+  reload(target);
+
+  let [actors] = yield Promise.all([
+    once(gAudioNodes, "add", 2),
+    once(gAudioNodes, "disconnect")
+  ]);
+
+  ok(true, "Successfully disconnected a just-created node.");
+
+  yield teardown(target);
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webaudioeditor/test/doc_bug_1125817.html
@@ -0,0 +1,23 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Web Audio Editor test page</title>
+  </head>
+
+  <body>
+
+    <script type="text/javascript;version=1.8">
+      "use strict";
+
+      let ctx = new AudioContext();
+      let osc = ctx.createOscillator();
+      osc.frequency.value = 200;
+      osc.disconnect();
+    </script>
+  </body>
+
+</html>
--- a/browser/metro/components/HelperAppDialog.js
+++ b/browser/metro/components/HelperAppDialog.js
@@ -133,20 +133,16 @@ HelperAppLauncherDialog.prototype = {
                                                     "save-download",
                                                     URI_GENERIC_ICON_DOWNLOAD,
                                                     notificationBox.PRIORITY_WARNING_HIGH,
                                                     buttons);
     let messageContainer = document.getAnonymousElementByAttribute(newBar, "anonid", "messageText");
     messageContainer.appendChild(fragment);
   },
 
-  promptForSaveToFile: function hald_promptForSaveToFile(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) {
-    throw new Components.Exception("Async version must be used", Cr.NS_ERROR_NOT_AVAILABLE);
-  },
-
   promptForSaveToFileAsync: function hald_promptForSaveToFileAsync(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) {
     let file = null;
     let prefs = Services.prefs;
 
     Task.spawn(function() {
       if (!aForcePrompt) {
         // Check to see if the user wishes to auto save to the default download
         // folder without prompting. Note that preference might not be set.
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -29,16 +29,19 @@ browser.jar:
   skin/classic/browser/click-to-play-warning-stripes.png
   skin/classic/browser/content-contextmenu.svg
   skin/classic/browser/dots.png                             (../shared/dots.png)
   skin/classic/browser/dots@2x.png                          (../shared/dots@2x.png)
 * skin/classic/browser/engineManager.css
   skin/classic/browser/fullscreen-darknoise.png
   skin/classic/browser/Geolocation-16.png
   skin/classic/browser/Geolocation-64.png
+  skin/classic/browser/heartbeat-icon.svg                   (../shared/heartbeat-icon.svg)
+  skin/classic/browser/heartbeat-star-lit.svg               (../shared/heartbeat-star-lit.svg)
+  skin/classic/browser/heartbeat-star-off.svg               (../shared/heartbeat-star-off.svg)
   skin/classic/browser/identity.png
   skin/classic/browser/identity-icons-generic.png
   skin/classic/browser/identity-icons-https.png
   skin/classic/browser/identity-icons-https-ev.png
   skin/classic/browser/identity-icons-https-mixed-active.png
   skin/classic/browser/identity-icons-https-mixed-display.png
   skin/classic/browser/Info.png
   skin/classic/browser/magnifier.png                        (../shared/magnifier.png)
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -31,16 +31,19 @@ browser.jar:
   skin/classic/browser/dots.png                             (../shared/dots.png)
   skin/classic/browser/dots@2x.png                          (../shared/dots@2x.png)
 * skin/classic/browser/engineManager.css                    (engineManager.css)
   skin/classic/browser/fullscreen-darknoise.png
   skin/classic/browser/Geolocation-16.png
   skin/classic/browser/Geolocation-16@2x.png
   skin/classic/browser/Geolocation-64.png
   skin/classic/browser/Geolocation-64@2x.png
+  skin/classic/browser/heartbeat-icon.svg                   (../shared/heartbeat-icon.svg)
+  skin/classic/browser/heartbeat-star-lit.svg               (../shared/heartbeat-star-lit.svg)
+  skin/classic/browser/heartbeat-star-off.svg               (../shared/heartbeat-star-off.svg)
   skin/classic/browser/identity.png
   skin/classic/browser/identity@2x.png
   skin/classic/browser/identity-icons-generic.png
   skin/classic/browser/identity-icons-generic@2x.png
   skin/classic/browser/identity-icons-https.png
   skin/classic/browser/identity-icons-https@2x.png
   skin/classic/browser/identity-icons-https-ev.png
   skin/classic/browser/identity-icons-https-ev@2x.png
--- a/browser/themes/shared/UITour.inc.css
+++ b/browser/themes/shared/UITour.inc.css
@@ -190,8 +190,115 @@
   .SearchHighlight .dot {
     background-image: -moz-image-rect(url("chrome://browser/skin/dots@2x.png"), 0, 100%, 100%, 18);
   }
 
   .SearchHighlight .dot.filled {
     background-image: -moz-image-rect(url("chrome://browser/skin/dots@2x.png"), 0, 14, 100%, 0);
   }
 }
+
+/* Notification overrides for Heartbeat UI */
+
+notification.heartbeat {
+  background-color: #F1F1F1;
+%ifdef XP_MACOSX
+  background-image: linear-gradient(-179deg, #FBFBFB 0%, #EBEBEB 100%);
+%endif
+  box-shadow: 0px 1px 0px 0px rgba(0,0,0,0.35);
+}
+
+@keyframes pulse-onshow {
+ 0% {
+   opacity: 0;
+   transform: scale(1.0);
+ }
+ 25% {
+   opacity: 1;
+   transform: scale(1.1);
+ }
+ 50% {
+   transform: scale(1.0);
+ }
+ 75% {
+   transform: scale(1.1);
+ }
+ 100% {
+   transform: scale(1.0);
+ }
+}
+
+@keyframes pulse-twice {
+ 0% {
+   transform: scale(1.1);
+ }
+ 50% {
+   transform: scale(0.8);
+ }
+ 100% {
+   transform: scale(1);
+ }
+}
+
+.messageText.heartbeat {
+  color: #333333;
+  font-weight: normal;
+  font-family: "Lucida Grande", Segoe, Ubuntu;
+  font-size: 14px;
+  line-height: 16px;
+  text-shadow: none;
+}
+
+.messageImage.heartbeat {
+  width: 36px;
+  height: 36px;
+  -moz-margin-end: 10px;
+}
+
+.messageImage.heartbeat.pulse-onshow {
+  animation-name: pulse-onshow;
+  animation-duration: 1.5s;
+  animation-iteration-count: 1;
+  animation-timing-function: cubic-bezier(.7,1.8,.9,1.1);
+}
+
+.messageImage.heartbeat.pulse-twice {
+  animation-name: pulse-twice;
+  animation-duration: 1s;
+  animation-iteration-count: 2;
+  animation-timing-function: linear;
+}
+
+/* Heartbeat UI Rating Star Classes */
+.heartbeat > #star-rating-container {
+  display: -moz-box;
+}
+
+.heartbeat > #star-rating-container > #star5 {
+  -moz-box-ordinal-group: 5;
+}
+
+.heartbeat > #star-rating-container > #star4 {
+  -moz-box-ordinal-group: 4;
+}
+
+.heartbeat > #star-rating-container > #star3 {
+  -moz-box-ordinal-group: 3;
+}
+
+.heartbeat > #star-rating-container > #star2 {
+  -moz-box-ordinal-group: 2;
+}
+
+.heartbeat > #star-rating-container > .star-x  {
+  background: url("chrome://browser/skin/heartbeat-star-off.svg");
+  cursor: pointer;
+  width: 24px;
+  height: 24px;
+}
+
+.heartbeat > #star-rating-container > .star-x:hover,
+.heartbeat > #star-rating-container > .star-x:hover ~ .star-x {
+  background: url("chrome://browser/skin/heartbeat-star-lit.svg");
+  cursor: pointer;
+  width: 24px;
+  height: 24px;
+}
--- a/browser/themes/shared/aboutNetError.css
+++ b/browser/themes/shared/aboutNetError.css
@@ -23,16 +23,17 @@ ul > li, ol > li {
   margin-bottom: .5em;
 }
 
 ul {
   list-style: disc;
 }
 
 #errorPageContainer {
+  position: relative;
   min-width: 320px;
   max-width: 512px;
 }
 
 #errorTitleText {
   background: url("aboutNetError_info.svg") left 0 no-repeat;
   background-size: 1.2em;
   -moz-margin-start: -2em;
@@ -69,53 +70,59 @@ ul {
  * not-allowed. Override the disabled cursor behaviour since we will never show
  * the button disabled as the initial state. */
 button:disabled {
   cursor: pointer;
 }
 
 div#certificateErrorReporting {
   display: none;
-  float:right;
+  float: right;
   /* Align with the "Try Again" button */
-  margin-top:24px;
-  margin-right:24px;
-}
-
-div#certificateErrorReporting a,
-div#certificateErrorReportingPanel a {
-  color: #0095DD;
+  margin-top: 24px;
+  -moz-margin-end: 24px;
 }
 
 div#certificateErrorReporting a {
   text-decoration: none;
 }
 
 div#certificateErrorReporting a:hover {
   text-decoration: underline;
 }
 
 span.downArrow {
-  font-size: 0.9em;
+  display: inline-block;
+  vertical-align: middle;
+  font-size: 0.6em;
+  -moz-margin-start: 0.5em;
+  transform: scaleY(0.7);
 }
 
 div#certificateErrorReportingPanel {
   /* Hidden until the link is clicked */
   display: none;
   background-color: white;
   border: 1px lightgray solid;
   /* Don't use top padding because the default p style has top padding, and it
    * makes the overall div look uneven */
   padding: 0 12px 12px 12px;
   box-shadow: 0 0 4px #ddd;
-  position: relative;
+  font-size: 0.9em;
+  position: absolute;
   width: 75%;
+  margin-top: 10px;
+}
+
+div#certificateErrorReportingPanel:-moz-dir(ltr) {
   left: 34%;
-  font-size: 0.9em;
-  top: 8px;
+}
+
+div#certificateErrorReportingPanel:-moz-dir(rtl) {
+  right: 0;
 }
 
 span#hostname {
   font-weight: bold;
 }
 
 #automaticallyReportInFuture {
   cursor: pointer;
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/heartbeat-icon.svg
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="288px" height="248px" viewBox="0 0 288 248" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+    <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
+    <title>  + Line 14</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <path id="path-1" d="M144,246.857143 C141.214272,246.857143 138.857152,245.892867 136.928571,243.964286 L36.6428571,147.214286 C35.5714232,146.357139 34.0982237,144.964295 32.2232143,143.035714 C30.3482049,141.107133 27.3750204,137.59824 23.3035714,132.508929 C19.2321225,127.419617 15.5893018,122.196455 12.375,116.839286 C9.16069821,111.482116 6.29465545,105.000038 3.77678571,97.3928571 C1.25891598,89.7856763 0,82.392893 0,75.2142857 C0,51.6427393 6.80350339,33.2143521 20.4107143,19.9285714 C34.0179252,6.64279071 52.8213086,0 76.8214286,0 C83.4643189,0 90.2410369,1.1517742 97.1517857,3.45535714 C104.062535,5.75894009 110.491042,8.86605187 116.4375,12.7767857 C122.383958,16.6875196 127.499979,20.3571257 131.785714,23.7857143 C136.07145,27.2143029 140.142838,30.8571236 144,34.7142857 C147.857162,30.8571236 151.92855,27.2143029 156.214286,23.7857143 C160.500021,20.3571257 165.616042,16.6875196 171.5625,12.7767857 C177.508958,8.86605187 183.937465,5.75894009 190.848214,3.45535714 C197.758963,1.1517742 204.535681,0 211.178571,0 C235.178691,0 253.982075,6.64279071 267.589286,19.9285714 C281.196497,33.2143521 288,51.6427393 288,75.2142857 C288,98.8929755 275.732266,122.999877 251.196429,147.535714 L151.071429,243.964286 C149.142847,245.892867 146.785728,246.857143 144,246.857143 L144,246.857143 Z"/>
+    </defs>
+    <g id="Prompt---Spec" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+        <g id="--+-Line-14" sketch:type="MSLayerGroup" transform="translate(0.000000, -1.000000)">
+            <path d="M144,248.571429 C141.214272,248.571429 138.857152,247.607152 136.928571,245.678571 L36.6428571,148.928571 C35.5714232,148.071424 34.0982237,146.678581 32.2232143,144.75 C30.3482049,142.821419 27.3750204,139.312525 23.3035714,134.223214 C19.2321225,129.133903 15.5893018,123.910741 12.375,118.553571 C9.16069821,113.196402 6.29465545,106.714324 3.77678571,99.1071429 C1.25891598,91.499962 0,84.1071788 0,76.9285714 C0,53.357025 6.80350339,34.9286379 20.4107143,21.6428571 C34.0179252,8.35707643 52.8213086,1.71428571 76.8214286,1.71428571 C83.4643189,1.71428571 90.2410369,2.86605991 97.1517857,5.16964286 C104.062535,7.4732258 110.491042,10.5803376 116.4375,14.4910714 C122.383958,18.4018053 127.499979,22.0714114 131.785714,25.5 C136.07145,28.9285886 140.142838,32.5714093 144,36.4285714 C147.857162,32.5714093 151.92855,28.9285886 156.214286,25.5 C160.500021,22.0714114 165.616042,18.4018053 171.5625,14.4910714 C177.508958,10.5803376 183.937465,7.4732258 190.848214,5.16964286 C197.758963,2.86605991 204.535681,1.71428571 211.178571,1.71428571 C235.178691,1.71428571 253.982075,8.35707643 267.589286,21.6428571 C281.196497,34.9286379 288,53.357025 288,76.9285714 C288,100.607261 275.732266,124.714163 251.196429,149.25 L151.071429,245.678571 C149.142847,247.607152 146.785728,248.571429 144,248.571429 L144,248.571429 Z" id="-" fill="#D74345" sketch:type="MSShapeGroup"/>
+            <g id="Line-14" transform="translate(0.000000, 0.714286)">
+                <mask id="mask-2" sketch:name="Mask" fill="white">
+                    <use xlink:href="#path-1"/>
+                </mask>
+                <use id="Mask" sketch:type="MSShapeGroup" xlink:href="#path-1"/>
+                <path d="M-166,115.135254 C-166,115.135254 0.595052083,115.135254 2.9765625,115.135254 L91.9101562,115.135254 L97.9638977,100.101562 L105.430695,115.135254 L114.893585,115.135254 L131.129913,189.53125 L148.161163,57 L165.348663,131.027344 L172.272491,115.135254 L250.84967,115.135254 L428.259813,115.135254" stroke="#FFFFFF" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" sketch:type="MSShapeGroup" mask="url(#mask-2)">
+                    <g transform="translate(131.129906, 123.265625) scale(1, -1) translate(-131.129906, -123.265625) "/>
+                </path>
+            </g>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/heartbeat-star-lit.svg
@@ -0,0 +1,428 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Toolbar"
+   x="0px"
+   y="0px"
+   viewBox="0 0 16 16"
+   enable-background="new 0 0 16 16"
+   xml:space="preserve"
+   inkscape:version="0.48.5 r10040"
+   width="100%"
+   height="100%"
+   sodipodi:docname="star-lit.svg"><metadata
+   id="metadata4111"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+   id="defs4109" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1440"
+   inkscape:window-height="838"
+   id="namedview4107"
+   showgrid="false"
+   inkscape:zoom="41.7193"
+   inkscape:cx="6.7712219"
+   inkscape:cy="7.3971752"
+   inkscape:window-x="-8"
+   inkscape:window-y="-8"
+   inkscape:window-maximized="1"
+   inkscape:current-layer="Toolbar" />
+<g
+   id="g3926">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-238.2,6h-6.1l2.7-2.7c0.4-0.4,0.5-1.1,0.2-1.4l-1.2-1.2   c-0.3-0.3-1-0.3-1.4,0.2l-6.4,6.4c-0.1,0.1-0.1,0.2-0.2,0.2l0,0c0,0,0,0,0,0c0,0.1-0.1,0.1-0.1,0.1c0,0.1-0.1,0.2-0.1,0.3   c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0.1,0,0.2,0.1,0.3c0,0.1,0.1,0.1,0.1,0.1c0,0,0,0,0,0l0,0c0.1,0.1,0.1,0.2,0.2,0.2l6.4,6.4   c0.4,0.4,1.1,0.5,1.4,0.2l1.2-1.2c0.3-0.3,0.3-1-0.2-1.4l-2.8-2.8h6.2c0.6,0,1-0.4,1-1V7C-237.2,6.4-237.6,6-238.2,6z"
+   id="path3928" />
+</g>
+<g
+   id="g3930">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-212.7,6.3l-1,1c-0.2,0.2-0.2,0.4-0.2,0.7c0,0.3,0,0.5,0.2,0.7   l0.8,0.9l4.5,4.5c0.5,0.5,1.2,0.6,1.6,0.2l0.8-0.9c0.4-0.4,0.3-1.1-0.2-1.6l-1.8-1.8h4.7c0.6,0,1-0.5,1-1V7c0-0.6-0.5-1-1-1h-4.8   l1.9-1.9c0.5-0.5,0.6-1.3,0.2-1.6l-0.8-0.9c-0.4-0.4-1.1-0.3-1.6,0.2L-212.7,6.3z"
+   id="path3932" />
+</g>
+<g
+   id="g3934">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-167.3,9.7l1-1c0.2-0.2,0.2-0.4,0.2-0.7c0-0.3,0-0.5-0.2-0.7   l-0.8-0.9l-4.5-4.5c-0.5-0.5-1.2-0.6-1.6-0.2l-0.8,0.9c-0.4,0.4-0.3,1.1,0.2,1.6l1.8,1.8h-4.7c-0.6,0-1,0.5-1,1v2c0,0.6,0.5,1,1,1   h4.8l-1.9,1.9c-0.5,0.5-0.6,1.3-0.2,1.6l0.8,0.9c0.4,0.4,1.1,0.3,1.6-0.2L-167.3,9.7z"
+   id="path3936" />
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-129.5,8h-7l2.8-2.8c-0.7-0.5-1.4-0.7-2.3-0.7c-2.2,0-4,1.8-4,4  c0,2.2,1.8,4,4,4c1.4,0,2.7-0.7,3.4-1.9l2.3,1c-1.1,2-3.2,3.4-5.7,3.4c-3.6,0-6.5-2.9-6.5-6.5c0-3.6,2.9-6.5,6.5-6.5  c1.5,0,2.9,0.5,4.1,1.4l2.4-2.4V8z"
+   id="path3938" />
+<g
+   id="g3940">
+	<g
+   id="g3942">
+		<polygon
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   points="-93.6,3.8 -95.8,1.7 -100,5.9 -104.3,1.6 -106.4,3.8    -102.1,8 -106.3,12.2 -104.1,14.3 -99.9,10.2 -95.7,14.4 -93.6,12.2 -97.8,8.1   "
+   id="polygon3944" />
+	</g>
+</g>
+<g
+   id="g3946">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-64,1l-8,7h2l6-5l6,5h2L-64,1z M-69,8v7h4v-5h2v5h4V8l-5-4L-69,8   z"
+   id="path3948" />
+</g>
+<path
+   fill="#00A3F2"
+   d="M-28,3.6l1,2l0.5,0.9l1,0.2l2.3,0.4l-1.7,1.8l-0.7,0.7l0.1,1l0.4,2.4l-2-1l-0.9-0.5l-0.9,0.5l-2,1l0.4-2.4  l0.1-1l-0.7-0.7l-1.7-1.8l2.4-0.4l1-0.2l0.5-0.9L-28,3.6 M-28,0c-0.3,0-0.6,0.2-0.8,0.7l-2,4.1l-4.3,0.7c-1,0.2-1.2,0.9-0.5,1.6  l3.1,3.3l-0.7,4.6c-0.1,0.7,0.2,1.1,0.7,1.1c0.2,0,0.4-0.1,0.6-0.2l3.9-2.1l3.9,2.1c0.2,0.1,0.5,0.2,0.6,0.2c0.5,0,0.8-0.4,0.7-1.1  l-0.7-4.6l3.1-3.3c0.7-0.7,0.4-1.4-0.5-1.6l-4.3-0.7l-2-4.1C-27.4,0.2-27.7,0-28,0L-28,0z"
+   id="path3950" />
+<path
+   fill="#00A3F2"
+   d="M8,0C7.7,0,7.4,0.2,7.2,0.7l-2,4.1L0.9,5.5c-1,0.2-1.2,0.9-0.5,1.6l3.1,3.3l-0.7,4.6C2.7,15.6,3,16,3.4,16  c0.2,0,0.4-0.1,0.6-0.2L8,13.7l3.9,2.1c0.2,0.1,0.5,0.2,0.6,0.2c0.5,0,0.8-0.4,0.7-1.1l-0.7-4.6l3.1-3.3c0.7-0.7,0.4-1.4-0.5-1.6  l-4.3-0.7l-2-4.1C8.6,0.2,8.3,0,8,0L8,0z"
+   id="path3952"
+   style="fill:#0095dd;fill-opacity:1" />
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M44,15.2c-4,0-7.2-3.2-7.2-7.2c0-4,3.2-7.2,7.2-7.2  c4,0,7.2,3.2,7.2,7.2C51.2,12,48,15.2,44,15.2z M44,3c-2.8,0-5,2.2-5,5c0,2.7,2.2,5,5,5c2.8,0,5-2.2,5-5C49,5.3,46.8,3,44,3z   M43.7,8.9C43.3,8.8,43,8.4,43,8V5c0-0.6,0.4-1,1-1c0.6,0,1,0.4,1,1v2.8c1.1,1.1,2,3.2,2,3.2S44.8,10,43.7,8.9z"
+   id="path3954" />
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M86.7,9.1l-5.6,5.5C80.8,14.9,80.4,15,80,15  c-0.4,0-0.8-0.1-1.1-0.4l-5.6-5.5C72.7,8.5,72.9,8,73.8,8H77l0-6c0-0.6,0.4-1,1-1h4c0.6,0,1,0.4,1,1v6h3.2C87.1,8,87.3,8.5,86.7,9.1  z"
+   id="path3956" />
+<path
+   fill="#00A3F2"
+   d="M-241,52c0.5,0,1-0.4,1-1v-4c0,0,0.1-0.8,0.8-0.8c0.6,0,0.6,0.8,1.8,0.8c0.6,0,1.5-0.2,1.5-2  c0-1.8-0.9-2-1.5-2c-1.1,0-1.1,0.8-1.8,0.8c-0.6,0-0.8-0.8-0.8-0.8v-2c0-0.6-0.4-1-1-1h-3c0,0-0.8-0.1-0.8-0.8  c0-0.6,0.8-0.6,0.8-1.8c0-0.6-0.2-1.5-2-1.5c-1.8,0-2,0.9-2,1.5c0,1.1,0.8,1.1,0.8,1.8c0,0.6-0.8,0.8-0.8,0.8h-3c-0.5,0-1,0.4-1,1  l0,2.5c0,0-0.1,1.5,1.1,1.5c0.8,0,0.9-1,1.9-1c0.5,0,1,0.5,1,1.6c0,1-0.5,1.6-1,1.6c-1,0-1.1-1-1.9-1c-1.2,0-1.1,1.5-1.1,1.5l0,3.5  c0,0.6,0.4,1,1,1h3.8c0,0,1.5,0.1,1.5-1.1c0-0.8-1-0.9-1-1.9c0-0.5,0.7-1.2,1.8-1.2c1,0,1.8,0.7,1.8,1.2c0,1-1,1.1-1,1.9  c0,1.2,1.5,1.1,1.5,1.1H-241z"
+   id="path3958" />
+<g
+   id="g3960">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-200.4,41.9h-0.3v0h-14.4v0h-0.3c-0.2,0-0.4,0.2-0.4,0.4c0,0,0.3,2.2,0.5,4.4   c0.2,2.5,0.2,4.2,0.2,4.2c0,0.2,0.2,0.4,0.4,0.4h13.7c0.2,0,0.4-0.2,0.4-0.4c0,0,0.1-2.1,0.2-4.2c0.1-2.2,0.5-4.4,0.5-4.4   C-200.1,42.1-200.2,41.9-200.4,41.9z"
+   id="path3962" />
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-200.8,41.3v-2c0-0.3-0.2-0.5-0.5-0.5l-6.7,0l-0.8-1.1c0,0-0.6-0.9-1.2-0.9h-4.2   c-0.6,0-1,0.5-1,1v1l0,2.5H-200.8z"
+   id="path3964" />
+</g>
+<g
+   id="g3966">
+	<g
+   id="g3968">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-169,37h-7.5c-0.8,0-1.5,0.7-1.5,1.5v11c0,0.8,0.7,1.5,1.5,1.5    h9c0.8,0,1.5-0.7,1.5-1.5V40L-169,37z M-170,41v-3.3l3.3,3.3H-170z"
+   id="path3970" />
+	</g>
+</g>
+<g
+   id="g3972">
+	<g
+   id="g3974">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-132.5,38c-0.4,0.2-0.9,0.6-1.3,1.2c-0.3,0.4-0.5,0.9-0.8,1.3    c0.7,0.3,1.3,0.8,1.7,1.4c0.4,0.7,0.7,1.5,0.6,2.3c-0.1,1.3-1,2.4-2.1,3c-0.5-0.5-1.4-2.1-1.4-2.1c0,0,0,1.8-0.6,3    c-0.4,0.8-0.9,1.5-1.6,2.1c1,0.4,2,0.6,3,0.6l0-0.1c0,0,0.1,0.1,0.1,0.1c2.1,0,4.1-0.3,4.1-0.3s-0.9-0.6-1.3-0.9    c1.3-0.7,2.2-2,2.5-3.3c0.2-0.5,0.3-1.1,0.4-1.7C-128.7,41.9-130.2,39.3-132.5,38z M-139.8,44.2c0-1.4,0.8-2.8,2.2-3.5    c0.5,0.5,1.6,2,1.6,2s0-3.6,1.2-5.6c-4.3-0.5-6.8,0.3-6.8,0.3s1.2,0.4,1.6,0.8c-0.1,0.1-0.2,0.1-0.3,0.2c-1,0.7-1.7,1.6-2.1,2.7    c-0.3,0.6-0.5,1.3-0.5,2c-0.3,2.8,1.1,5.4,3.4,6.7c0.4-0.3,0.8-0.6,1.2-1.1c0.4-0.4,0.7-0.9,1-1.4    C-138.7,46.9-139.7,45.7-139.8,44.2z"
+   id="path3976" />
+	</g>
+</g>
+<g
+   id="g3978">
+	<g
+   id="g3980">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-96.6,41.5c-3.3-4-9.6-4.5-9.6-4.5c-0.4,0-0.8,0.3-0.8,0.7v1.4    c0,0.4,0.3,0.7,0.8,0.7c0,0,4.5,0.1,7.1,3.2c3.5,3.6,3.2,7.2,3.2,7.2c0,0.4,0.3,0.8,0.8,0.8h1.5c0.4,0,0.7-0.3,0.7-0.8    C-93,50.2-93.3,44.5-96.6,41.5z M-106.2,42c-0.4,0-0.8,0.3-0.8,0.7V44c0,0.4,0.3,0.7,0.8,0.7c0,0,2.4,0.1,3.8,1.4    c1.9,1.7,1.8,4.2,1.8,4.2c0,0.4,0.2,0.8,0.6,0.8h1.5c0.4,0,0.5-0.3,0.5-0.8c0,0-0.4-3.9-2.5-5.9C-102.5,42.4-106.2,42-106.2,42z     M-105,47c-1.1,0-2,0.9-2,2c0,1.1,0.9,2,2,2c1.1,0,2-0.9,2-2C-103,47.9-103.9,47-105,47z"
+   id="path3982" />
+	</g>
+</g>
+<path
+   fill="none"
+   d="M-102.6,34.4c0.5,0,1-0.4,1-1v-4c0,0,0.1-0.8,0.8-0.8c0.6,0,0.6,0.8,1.8,0.8c0.6,0,1.5-0.2,1.5-2  c0-1.8-0.9-2-1.5-2c-1.1,0-1.1,0.8-1.8,0.8c-0.6,0-0.8-0.8-0.8-0.8v-2c0-0.6-0.4-1-1-1h-3c0,0-0.8-0.1-0.8-0.8  c0-0.6,0.8-0.6,0.8-1.8c0-0.6-0.2-1.5-2-1.5c-1.8,0-2,0.9-2,1.5c0,1.1,0.8,1.1,0.8,1.8c0,0.6-0.8,0.8-0.8,0.8h-3c-0.5,0-1,0.4-1,1  l0,2.5c0,0-0.1,1.5,1.1,1.5c0.8,0,0.9-1,1.9-1c0.5,0,1,0.5,1,1.6c0,1-0.5,1.6-1,1.6c-1,0-1.1-1-1.9-1c-1.2,0-1.1,1.5-1.1,1.5l0,3.5  c0,0.6,0.4,1,1,1h3.8c0,0,1.5,0.1,1.5-1.1c0-0.8-1-0.9-1-1.9c0-0.5,0.7-1.2,1.8-1.2c1,0,1.8,0.7,1.8,1.2c0,1-1,1.1-1,1.9  c0,1.2,1.5,1.1,1.5,1.1H-102.6z"
+   id="path3984" />
+<g
+   id="g3986">
+	<g
+   id="g3988">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-63,46.7l-0.3-0.1L-63,52l2.6-3.8L-63,46.7z M-63.3,46l0.3,0    l6,2.9V36l-14,11l4.6-0.6L-63,52l-1.9-5.7l-0.3-0.1l0.3,0L-65,46l7-8L-63.3,46L-63.3,46z"
+   id="path3990" />
+	</g>
+</g>
+<g
+   id="g3992">
+	<g
+   id="g3994">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-23,37h-10c-1.1,0-2,0.9-2,2v10c0,1.1,0.9,2,2,2h10    c1.1,0,2-0.9,2-2V39C-21,37.9-21.9,37-23,37z M-23,46c0,1.7-1.3,2-3,2h-4c-1.7,0-3-1.3-3-3v-4c0-1.7,1.3-3,3-3h4c1.7,0,3,1.3,3,3    V46z"
+   id="path3996" />
+	</g>
+</g>
+<path
+   fill="#00A3F2"
+   d="M-23.7,42.6c0-0.8-0.2-1.5-0.6-2c-0.4-0.5-1-0.7-1.7-0.7c-0.4,0-0.8,0.1-1.1,0.3s-0.6,0.4-0.8,0.8  c-0.2-0.4-0.4-0.6-0.7-0.8c-0.3-0.2-0.7-0.2-1.2-0.2c-0.4,0-0.7,0-1.1,0.1s-0.7,0.2-1,0.4l0.3,0.7c0.6-0.3,1.2-0.5,1.7-0.5  c0.4,0,0.8,0.1,1,0.3c0.2,0.2,0.3,0.6,0.3,1.1v0.4l-1,0c-0.9,0-1.6,0.2-2.1,0.5c-0.5,0.3-0.7,0.8-0.7,1.4c0,0.6,0.2,1,0.5,1.3  c0.3,0.3,0.8,0.5,1.4,0.5c0.5,0,0.9-0.1,1.3-0.3c0.4-0.2,0.7-0.5,1-1c0.5,0.9,1.2,1.3,2.3,1.3c0.4,0,0.7,0,1-0.1  c0.3-0.1,0.6-0.2,0.9-0.3v-0.8c-0.3,0.1-0.6,0.3-0.9,0.3c-0.3,0.1-0.6,0.1-0.9,0.1c-1.2,0-1.8-0.7-1.8-2.2h3.9V42.6z M-28.5,43.6  c0,0.6-0.2,1-0.5,1.3c-0.3,0.3-0.7,0.5-1.3,0.5c-0.3,0-0.6-0.1-0.8-0.2c-0.2-0.2-0.3-0.4-0.3-0.8c0-0.4,0.2-0.7,0.5-0.9  c0.3-0.2,0.8-0.3,1.5-0.3l0.9,0V43.6z M-27.5,42.4c0-0.6,0.2-1,0.4-1.3c0.3-0.3,0.6-0.5,1.1-0.5c0.4,0,0.8,0.2,1,0.5  c0.2,0.3,0.4,0.8,0.4,1.3H-27.5z"
+   id="path3998" />
+<g
+   id="g4000">
+	<g
+   id="g4002">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M14.5,37h-13C0.7,37,0,37.7,0,38.5v11C0,50.3,0.7,51,1.5,51h13    c0.8,0,1.5-0.7,1.5-1.5v-11C16,37.7,15.3,37,14.5,37z M6.5,38C6.8,38,7,38.2,7,38.5C7,38.8,6.8,39,6.5,39C6.2,39,6,38.8,6,38.5    C6,38.2,6.2,38,6.5,38z M4.4,38c0.3,0,0.5,0.2,0.5,0.5c0,0.3-0.2,0.5-0.5,0.5c-0.3,0-0.5-0.2-0.5-0.5C3.9,38.2,4.2,38,4.4,38z     M2.5,38C2.8,38,3,38.2,3,38.5C3,38.8,2.8,39,2.5,39C2.2,39,2,38.8,2,38.5C2,38.2,2.2,38,2.5,38z M14,48c0,0.6-0.4,1-1,1H3    c-0.6,0-1-0.4-1-1v-6c0-0.6,0.4-1,1-1h10c0.6,0,1,0.4,1,1V48z"
+   id="path4004" />
+	</g>
+</g>
+<path
+   fill="#00A3F2"
+   d="M51.2,45.9L51.2,45.9c-0.4-0.1-0.7-0.3-1-0.6c-0.1-0.1-0.1-0.1-0.2-0.2c-0.1-0.1-0.1-0.1-0.2-0.2  c0-0.1-0.1-0.1-0.1-0.2c0-0.1-0.1-0.1-0.1-0.2c-0.3-0.6-0.4-1.5-0.5-2.4c-0.3-2.2-0.1-3.8-3-3.8h-4.4c-2.9,0-2.6,1.6-2.9,3.8  c-0.1,0.9-0.3,1.8-0.6,2.4c0,0,0,0,0,0c-0.1,0.1-0.1,0.2-0.2,0.3c0,0,0,0-0.1,0.1c-0.1,0.2-0.2,0.3-0.4,0.4  c-0.3,0.2-0.5,0.4-0.9,0.5l0,0C36.4,46,36,46.3,36,46.8v1.9c0,0.5,0.4,0.9,0.9,0.9h14.1c0.5,0,0.9-0.4,0.9-0.9v-1.9  C52,46.3,51.6,46,51.2,45.9z M46.4,43.8c0,0.1-0.1,0.2-0.2,0.2h-1.6v1.6c0,0.1-0.1,0.2-0.2,0.2h-0.5c-0.1,0-0.2-0.1-0.2-0.2V44h-1.6  c-0.1,0-0.2-0.1-0.2-0.2v-0.5c0-0.1,0.1-0.2,0.2-0.2h1.6v-1.6c0-0.1,0.1-0.2,0.2-0.2h0.5c0.1,0,0.2,0.1,0.2,0.2v1.6h1.6  c0.1,0,0.2,0.1,0.2,0.2V43.8z"
+   id="path4006" />
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M87.7,42c-0.1,1.1,0.2,2.5-1.3,4.4c-1.6,2.1-3.1,2.1-3.4,2.1  c-1.8-0.1-2-1.5-3-1.5c-0.9,0-1.6,1.4-3,1.5c-0.3,0-1.9,0-3.4-2c-1.5-1.9-1.2-3.3-1.3-4.4S72,39.7,72,39.7s0.7,0.7,1.6,0.8  c0.9,0.1,1.1-0.3,3-0.9c2.1-0.6,3.4,1.9,3.4,1.9s1.4-2.4,3.4-1.9c1.9,0.6,2,0.9,2.9,0.9c0.9-0.1,1.7-0.8,1.7-0.8S87.8,40.9,87.7,42z   M76.9,42.5c-1.1-0.3-1.6,0.2-2.1,0.4C74.4,43,74,43.1,74,43.1s0.1,0.7,1.2,1.2c1.1,0.6,3.5,0.3,3.5,0.3S78.9,42.9,76.9,42.5z   M85.2,42.9c-0.5-0.2-1-0.6-2.1-0.4c-2,0.4-1.8,2.1-1.8,2.1s2.4,0.3,3.5-0.3c1.1-0.6,1.2-1.2,1.2-1.2S85.6,43,85.2,42.9z"
+   id="path4008" />
+<g
+   id="g4010">
+	<g
+   id="g4012">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-237.5,84.9l-3.3-3.3c0.5-0.9,0.9-1.9,0.9-2.9c0-3-2.4-5.5-5.5-5.5    c-3,0-5.5,2.4-5.5,5.5c0,3,2.4,5.5,5.5,5.5c1.1,0,2.1-0.3,3-0.9l3.3,3.3c0.4,0.4,1,0.4,1.4,0l0.2-0.2    C-237.1,85.9-237.1,85.3-237.5,84.9z M-245.4,82c-1.8,0-3.3-1.5-3.3-3.4c0-1.9,1.5-3.4,3.3-3.4c1.8,0,3.3,1.5,3.3,3.4    C-242,80.5-243.5,82-245.4,82z"
+   id="path4014" />
+	</g>
+</g>
+<g
+   id="g4016">
+	<g
+   id="g4018">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-214.5,86.3l0.2,0.2c0.4,0.4,1,0.4,1.4,0l3.3-3.3c0.9,0.6,1.9,0.9,3,0.9    c3,0,5.5-2.4,5.5-5.5c0-3-2.4-5.5-5.5-5.5c-3,0-5.5,2.4-5.5,5.5c0,1.1,0.3,2.1,0.9,2.9l-3.3,3.3    C-214.9,85.3-214.9,85.9-214.5,86.3z M-210,78.6c0-1.9,1.5-3.4,3.3-3.4c1.8,0,3.3,1.5,3.3,3.4c0,1.9-1.5,3.4-3.3,3.4    C-208.5,82-210,80.5-210,78.6z"
+   id="path4020" />
+	</g>
+</g>
+<g
+   id="g4022">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-165,77.5h-1v-2c0-0.6-0.4-1-1-1v-1c0-0.6-0.4-1-1-1h-8   c-0.6,0-1,0.4-1,1v1c-0.6,0-1,0.4-1,1v2h-1c-0.6,0-1,0.4-1,1v5c0,0.6,0.4,1,1,1h2v-1h0.5l-1.5,3h12l-1.5-3h0.5v1h2c0.6,0,1-0.4,1-1   v-5C-164,77.9-164.4,77.5-165,77.5z M-176.5,80.5h-1c-0.3,0-0.5-0.2-0.5-0.5c0-0.3,0.2-0.5,0.5-0.5h1c0.3,0,0.5,0.2,0.5,0.5   C-176,80.2-176.2,80.5-176.5,80.5z M-176,85.5l0.9-2h6.2l0.9,2H-176z M-168,77.5c0,0.6-0.4,1-1,1h-6c-0.6,0-1-0.4-1-1v-3   c0-0.6,0.4-1,1-1h6c0.6,0,1,0.4,1,1V77.5z"
+   id="path4024" />
+</g>
+<g
+   id="g4026">
+	<path
+   fill="#00A3F2"
+   d="M-135,73l1.9,1.9l-2.6,2.6c-0.5,0.5-0.5,1.4,0.1,2c0.6,0.6,1.5,0.6,2,0.1l2.6-2.6l1.9,1.9v-6H-135z    M-138.5,80.4l-2.6,2.6L-143,81v6h6l-1.9-1.9l2.6-2.6c0.5-0.5,0.5-1.4-0.1-2C-137.1,79.9-138,79.9-138.5,80.4z"
+   id="path4028" />
+</g>
+<g
+   id="g4030">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-93.5,73.5c-0.6-0.6-1.5-0.6-2-0.1l-2.6,2.6L-100,74v6h6   l-1.9-1.9l2.6-2.6C-92.9,75-92.9,74.1-93.5,73.5z M-104.1,81.9l-2.6,2.6c-0.5,0.5-0.5,1.4,0.1,2c0.6,0.6,1.5,0.6,2,0.1l2.6-2.6   l1.9,1.9v-6h-6L-104.1,81.9z"
+   id="path4032" />
+</g>
+<g
+   id="g4034">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M14.6,75.1l-2.3,2.3c-0.5,0.5-1.3,0.5-1.7,0   c-0.5-0.5-0.5-1.2,0-1.7l2.4-2.3c-0.6-0.3-1.2-0.5-1.9-0.5C8.8,72.9,7,74.7,7,77c0,0.5,0.1,1.1,0.3,1.5l-5.9,5.8   c-0.6,0.6-0.6,1.7,0,2.3c0.6,0.6,1.7,0.6,2.3,0l6-5.9c0.4,0.2,0.9,0.3,1.4,0.3c2.2,0,4.1-1.8,4.1-4.1   C15.1,76.3,14.9,75.7,14.6,75.1z M2.5,86.4c-0.5,0-0.9-0.4-0.9-0.9c0-0.5,0.4-0.9,0.9-0.9c0.5,0,0.9,0.4,0.9,0.9   C3.4,86,3,86.4,2.5,86.4z"
+   id="path4036" />
+</g>
+<path
+   fill="#00A3F2"
+   d="M-22.7,83.9l-1.1-1.1c0.2-0.3,0.3-0.6,0.5-0.9c0.1-0.3,0.2-0.6,0.3-0.9l1.6,0c0.3,0,0.5-0.2,0.5-0.5l0-1  c0-0.3-0.2-0.5-0.5-0.5l-1.6,0c-0.1-0.6-0.4-1.2-0.7-1.8l1.1-1.1c0.2-0.2,0.2-0.5,0-0.7l-0.7-0.7c-0.2-0.2-0.5-0.2-0.7,0l-1.1,1.1  c-0.3-0.2-0.6-0.3-0.9-0.5c-0.3-0.1-0.6-0.2-0.9-0.3l0-1.6c0-0.3-0.2-0.5-0.5-0.5l-1,0c-0.3,0-0.5,0.2-0.5,0.5l0,1.6  c-0.6,0.1-1.2,0.4-1.8,0.7l-1.1-1.1c-0.2-0.2-0.5-0.2-0.7,0l-0.7,0.7c-0.2,0.2-0.2,0.5,0,0.7l1.1,1.1c-0.2,0.3-0.3,0.6-0.5,0.9  c-0.1,0.3-0.2,0.6-0.3,0.9l-1.6,0c-0.3,0-0.5,0.2-0.5,0.5l0,1c0,0.3,0.2,0.5,0.5,0.5l1.6,0c0.1,0.6,0.4,1.2,0.7,1.8l-1.1,1.1  c-0.2,0.2-0.2,0.5,0,0.7l0.7,0.7c0.2,0.2,0.5,0.2,0.7,0l1.1-1.1c0.3,0.2,0.6,0.3,0.9,0.5c0.3,0.1,0.6,0.2,0.9,0.3l0,1.6  c0,0.3,0.2,0.5,0.5,0.5l1,0c0.3,0,0.5-0.2,0.5-0.5l0-1.6c0.6-0.1,1.2-0.4,1.8-0.7l1.1,1.1c0.2,0.2,0.5,0.2,0.7,0l0.7-0.7  C-22.5,84.4-22.5,84.1-22.7,83.9z M-28.8,82.1c-1.2-0.5-1.7-1.8-1.3-3c0.5-1.2,1.8-1.7,3-1.3c1.2,0.5,1.7,1.8,1.3,3  C-26.4,82-27.7,82.6-28.8,82.1z"
+   id="path4038" />
+<g
+   id="g4040">
+	<g
+   id="g4042">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-62,77h-4c-0.6,0-1,0.4-1,1v4c0,0.6,0.4,1,1,1h4    c0.6,0,1-0.4,1-1v-4C-61,77.4-61.4,77-62,77z M-62,81c0,0.6-0.4,1-1,1h-2c-0.6,0-1-0.4-1-1v-1c0-0.6,0.4-1,1-1h2c0.6,0,1,0.4,1,1    V81z M-64,87l3-3h-6L-64,87z M-67,76h6l-3-3L-67,76z M-60,76.9v6l3-3L-60,76.9z M-68,82.9v-6l-3,3L-68,82.9z"
+   id="path4044" />
+	</g>
+</g>
+<g
+   id="g4046">
+	<g
+   id="g4048">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M37.5,76.5h13c0.8,0,1.5-0.7,1.5-1.5c0-0.8-0.7-1.5-1.5-1.5h-13    c-0.8,0-1.5,0.7-1.5,1.5C36,75.8,36.7,76.5,37.5,76.5z M50.5,78.5h-13c-0.8,0-1.5,0.7-1.5,1.5c0,0.8,0.7,1.5,1.5,1.5h13    c0.8,0,1.5-0.7,1.5-1.5C52,79.2,51.3,78.5,50.5,78.5z M50.5,83.5h-13c-0.8,0-1.5,0.7-1.5,1.5c0,0.8,0.7,1.5,1.5,1.5h13    c0.8,0,1.5-0.7,1.5-1.5C52,84.2,51.3,83.5,50.5,83.5z"
+   id="path4050" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M85.6,87c-0.8,0-1.5-0.2-2.3-1.3c-0.8-1.1-1.7-2.5-1.7-2.5  s-0.7-0.9-1.1-1.6c-0.4-0.7-1.1-0.5-1.1-0.5s-2.8-4.6-3.3-5.4c-0.7-1,0.6-2.7,0.6-2.7l4.4,7c0,0,1.3,1.9,1.9,2.3  c0.5,0.4,1.4-0.4,2.8,0.9C87.7,85,87.1,87,85.6,87z M85.4,84.1c-0.9-1-1.7-0.9-1.9-0.6c-0.2,0.3,0,1.2,0.4,1.7  c0.4,0.5,0.8,0.7,1.4,0.7C85.9,86,86.4,85.2,85.4,84.1z M81.6,79.4l-1.2-1.8l2.9-4.6c0,0,1.2,1.7,0.6,2.7  C83.6,76.1,82.5,78,81.6,79.4z M77,82.3c0.3-0.3,1-1.1,1.4-1.7l0.8,1.2c-0.4,0.6-0.9,1.4-0.9,1.4s-0.9,1.4-1.7,2.5  c-0.8,1.1-1.5,1.3-2.3,1.3c-1.4,0-2.1-2-0.1-3.8C75.6,82,76.5,82.7,77,82.3z M74.6,84.1c-0.9,1-0.4,1.8,0.2,1.8c0.6,0,1-0.2,1.4-0.7  c0.4-0.5,0.6-1.5,0.4-1.7C76.3,83.2,75.5,83.1,74.6,84.1z"
+   id="path4052" />
+<g
+   id="g4054">
+	<g
+   id="g4056">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-243,111c0-0.1-2-2-2-2c-1.7,0-5,0-5,0c-0.6,0-1,0.4-1,1v8    c0,0.6,0.4,1,1,1h4v-6.1c0-0.3,0.2-0.5,0.5-0.5h2.5V111z M-246,112v-2l2,2H-246z M-239,113c-1.7,0-5,0-5,0c-0.6,0-1,0.4-1,1v8    c0,0.6,0.4,1,1,1h6c0.6,0,1-0.4,1-1c0,0,0-4.8,0-7C-237,115-239,113-239,113z M-240,116v-2l2,2H-240z"
+   id="path4058" />
+	</g>
+</g>
+<g
+   id="g4060">
+	<g
+   id="g4062">
+		<path
+   fill="#00A3F2"
+   d="M-204.5,111h-1.3l0,0c0,0-0.2-2-2.2-2c-2,0-2.2,2-2.2,2l0,0h-1.3c-0.8,0-1.5,0.7-1.5,1.5v9    c0,0.8,0.7,1.5,1.5,1.5h7c0.8,0,1.5-0.7,1.5-1.5v-9C-203,111.7-203.7,111-204.5,111z M-210.7,112.1l0.8-0.4l0.4-0.2l0-0.4    c0-0.2,0.2-1.3,1.5-1.3c1.2,0,1.4,1.1,1.5,1.3l0,0.4l0.4,0.2l0.8,0.4l0.3,0.7h-6.1L-210.7,112.1z M-210.1,120.4l-2.8-4.9l3.3-1.9    h4.3l1.8,3.1L-210.1,120.4z"
+   id="path4064" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-177,115h10v2h-10V115z"
+   id="path4066" />
+<g
+   id="g4068">
+	<g
+   id="g4070">
+		<polygon
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   points="-131,115 -135,115 -135,111 -137,111 -137,115 -141,115    -141,117 -137,117 -137,121 -135,121 -135,117 -131,117   "
+   id="polygon4072" />
+	</g>
+</g>
+<g
+   id="g4074">
+	<path
+   fill="#00A3F2"
+   d="M-94,111.7l-3,2.7v-2c0-0.8-0.6-1.4-1.3-1.4h-7.4c-0.7,0-1.3,0.6-1.3,1.4v7.2c0,0.8,0.6,1.4,1.3,1.4h7.4   c0.7,0,1.3-0.6,1.3-1.4v-2.1l3,2.7c0.3,0.3,0.6,0.4,1,0.3v-9.1C-93.3,111.4-93.7,111.5-94,111.7z"
+   id="path4076" />
+</g>
+<g
+   id="g4078">
+	<g
+   id="g4080">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-61.9,112h-3l3.8,4l-3.8,4h3.1l3.8-4L-61.9,112z M-66.9,112h-3    l3.8,4l-3.8,4h3.1l3.8-4L-66.9,112z"
+   id="path4082" />
+	</g>
+</g>
+<g
+   id="g4084">
+	<g
+   id="g4086">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-23,111.5h-2l-3-3l-3,3h-2c-1.1,0-2,0.9-2,2v8c0,1.1,0.9,2,2,2    h10c1.1,0,2-0.9,2-2v-8C-21,112.4-21.9,111.5-23,111.5z M-32,121.5c-0.6,0-1-0.4-1-1s0.4-1,1-1s1,0.4,1,1S-31.4,121.5-32,121.5z     M-32,118.5c-0.6,0-1-0.4-1-1s0.4-1,1-1s1,0.4,1,1S-31.4,118.5-32,118.5z M-32,115.5c-0.6,0-1-0.4-1-1s0.4-1,1-1s1,0.4,1,1    S-31.4,115.5-32,115.5z M-23,121.5h-7v-2h7V121.5z M-23,118.5h-7v-2h7V118.5z M-23,115.5h-7v-2h7V115.5z"
+   id="path4088" />
+	</g>
+</g>
+<g
+   id="g4090">
+	<g
+   id="g4092">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M2,115h7c0.6,0,1-0.4,1-1v-4c0-0.6-0.4-1-1-1H2c-0.6,0-1,0.4-1,1    v4C1,114.6,1.4,115,2,115z M14,109h-2c-0.6,0-1,0.4-1,1v4c0,0.6,0.4,1,1,1h2c0.6,0,1-0.4,1-1v-4C15,109.4,14.6,109,14,109z     M14,116H8c-0.6,0-1,0.4-1,1v5c0,0.6,0.4,1,1,1h6c0.6,0,1-0.4,1-1v-5C15,116.4,14.6,116,14,116z M5,116H2c-0.6,0-1,0.4-1,1v5    c0,0.6,0.4,1,1,1h3c0.6,0,1-0.4,1-1v-5C6,116.4,5.6,116,5,116z"
+   id="path4094" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M45.8,116c0,0-0.6,0.8-1.8,0.8c-1.2,0-1.8-0.8-1.8-0.8l-6-5.1  c0.3-0.5,0.9-0.9,1.6-0.9h12.4c0.7,0,1.3,0.4,1.6,0.9L45.8,116z M42.2,117.7c0,0,0.6,0.8,1.8,0.8c1.2,0,1.8-0.8,1.8-0.8l6.2-5.4v8  c0,0.9-0.8,1.7-1.8,1.7H37.8c-1,0-1.8-0.8-1.8-1.7v-8L42.2,117.7z"
+   id="path4096" />
+<g
+   id="g4098">
+	<g
+   id="g4100">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-237.5,145h-13c-0.8,0-1.5,0.7-1.5,1.5v11    c0,0.8,0.7,1.5,1.5,1.5h13c0.8,0,1.5-0.7,1.5-1.5v-11C-236,145.7-236.7,145-237.5,145z M-245.5,146c0.3,0,0.5,0.2,0.5,0.5    c0,0.3-0.2,0.5-0.5,0.5c-0.3,0-0.5-0.2-0.5-0.5C-246,146.2-245.8,146-245.5,146z M-247.6,146c0.3,0,0.5,0.2,0.5,0.5    c0,0.3-0.2,0.5-0.5,0.5s-0.5-0.2-0.5-0.5C-248.1,146.2-247.8,146-247.6,146z M-249.5,146c0.3,0,0.5,0.2,0.5,0.5    c0,0.3-0.2,0.5-0.5,0.5s-0.5-0.2-0.5-0.5C-250,146.2-249.8,146-249.5,146z M-250,156v-6c0-0.6,0.4-1,1-1h7v8h-7    C-249.6,157-250,156.6-250,156z M-238,156c0,0.6-0.4,1-1,1h-1v-8h1c0.6,0,1,0.4,1,1V156z"
+   id="path4102" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-213.2,156c-1.3-1.2-2.2-2.8-2.2-4.6c0-3.6,3.3-6.5,7.4-6.5  c4.1,0,7.4,2.9,7.4,6.5c0,3.6-3.3,6.5-7.4,6.5c-0.8,0-1.6-0.1-2.4-0.3c-1.8,0.7-4.3,1.7-4.5,1.4C-213.9,157.9-213.5,156.8-213.2,156  z"
+   id="path4104" />
+<rect
+   id="_x3C_Slice_x3E_"
+   fill="none"
+   width="16"
+   height="16" />
+</svg>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/heartbeat-star-off.svg
@@ -0,0 +1,428 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Toolbar"
+   x="0px"
+   y="0px"
+   viewBox="0 0 16 16"
+   enable-background="new 0 0 16 16"
+   xml:space="preserve"
+   inkscape:version="0.48.5 r10040"
+   width="100%"
+   height="100%"
+   sodipodi:docname="star-off.svg"><metadata
+   id="metadata5255"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+   id="defs5253" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="710"
+   inkscape:window-height="480"
+   id="namedview5251"
+   showgrid="false"
+   inkscape:zoom="14.75"
+   inkscape:cx="7.6064963"
+   inkscape:cy="8"
+   inkscape:window-x="0"
+   inkscape:window-y="0"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Toolbar" />
+<g
+   id="g5070">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-238.2,6h-6.1l2.7-2.7c0.4-0.4,0.5-1.1,0.2-1.4l-1.2-1.2   c-0.3-0.3-1-0.3-1.4,0.2l-6.4,6.4c-0.1,0.1-0.1,0.2-0.2,0.2l0,0c0,0,0,0,0,0c0,0.1-0.1,0.1-0.1,0.1c0,0.1-0.1,0.2-0.1,0.3   c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0.1,0,0.2,0.1,0.3c0,0.1,0.1,0.1,0.1,0.1c0,0,0,0,0,0l0,0c0.1,0.1,0.1,0.2,0.2,0.2l6.4,6.4   c0.4,0.4,1.1,0.5,1.4,0.2l1.2-1.2c0.3-0.3,0.3-1-0.2-1.4l-2.8-2.8h6.2c0.6,0,1-0.4,1-1V7C-237.2,6.4-237.6,6-238.2,6z"
+   id="path5072" />
+</g>
+<g
+   id="g5074">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-212.7,6.3l-1,1c-0.2,0.2-0.2,0.4-0.2,0.7c0,0.3,0,0.5,0.2,0.7   l0.8,0.9l4.5,4.5c0.5,0.5,1.2,0.6,1.6,0.2l0.8-0.9c0.4-0.4,0.3-1.1-0.2-1.6l-1.8-1.8h4.7c0.6,0,1-0.5,1-1V7c0-0.6-0.5-1-1-1h-4.8   l1.9-1.9c0.5-0.5,0.6-1.3,0.2-1.6l-0.8-0.9c-0.4-0.4-1.1-0.3-1.6,0.2L-212.7,6.3z"
+   id="path5076" />
+</g>
+<g
+   id="g5078">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-167.3,9.7l1-1c0.2-0.2,0.2-0.4,0.2-0.7c0-0.3,0-0.5-0.2-0.7   l-0.8-0.9l-4.5-4.5c-0.5-0.5-1.2-0.6-1.6-0.2l-0.8,0.9c-0.4,0.4-0.3,1.1,0.2,1.6l1.8,1.8h-4.7c-0.6,0-1,0.5-1,1v2c0,0.6,0.5,1,1,1   h4.8l-1.9,1.9c-0.5,0.5-0.6,1.3-0.2,1.6l0.8,0.9c0.4,0.4,1.1,0.3,1.6-0.2L-167.3,9.7z"
+   id="path5080" />
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-129.5,8h-7l2.8-2.8c-0.7-0.5-1.4-0.7-2.3-0.7c-2.2,0-4,1.8-4,4  c0,2.2,1.8,4,4,4c1.4,0,2.7-0.7,3.4-1.9l2.3,1c-1.1,2-3.2,3.4-5.7,3.4c-3.6,0-6.5-2.9-6.5-6.5c0-3.6,2.9-6.5,6.5-6.5  c1.5,0,2.9,0.5,4.1,1.4l2.4-2.4V8z"
+   id="path5082" />
+<g
+   id="g5084">
+	<g
+   id="g5086">
+		<polygon
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   points="-93.6,3.8 -95.8,1.7 -100,5.9 -104.3,1.6 -106.4,3.8     -102.1,8 -106.3,12.2 -104.1,14.3 -99.9,10.2 -95.7,14.4 -93.6,12.2 -97.8,8.1   "
+   id="polygon5088" />
+	</g>
+</g>
+<g
+   id="g5090">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-64,1l-8,7h2l6-5l6,5h2L-64,1z M-69,8v7h4v-5h2v5h4V8l-5-4L-69,8   z"
+   id="path5092" />
+</g>
+<path
+   fill="#231F20"
+   d="M-28,3.6l1,2l0.5,0.9l1,0.2l2.3,0.4l-1.7,1.8l-0.7,0.7l0.1,1l0.4,2.4l-2-1l-0.9-0.5l-0.9,0.5l-2,1l0.4-2.4  l0.1-1l-0.7-0.7l-1.7-1.8l2.4-0.4l1-0.2l0.5-0.9L-28,3.6 M-28,0c-0.3,0-0.6,0.2-0.8,0.7l-2,4.1l-4.3,0.7c-1,0.2-1.2,0.9-0.5,1.6  l3.1,3.3l-0.7,4.6c-0.1,0.7,0.2,1.1,0.7,1.1c0.2,0,0.4-0.1,0.6-0.2l3.9-2.1l3.9,2.1c0.2,0.1,0.5,0.2,0.6,0.2c0.5,0,0.8-0.4,0.7-1.1  l-0.7-4.6l3.1-3.3c0.7-0.7,0.4-1.4-0.5-1.6l-4.3-0.7l-2-4.1C-27.4,0.2-27.7,0-28,0L-28,0z"
+   id="path5094" />
+<path
+   fill="#231F20"
+   d="M8,0C7.7,0,7.4,0.2,7.2,0.7l-2,4.1L0.9,5.5c-1,0.2-1.2,0.9-0.5,1.6l3.1,3.3l-0.7,4.6C2.7,15.6,3,16,3.4,16  c0.2,0,0.4-0.1,0.6-0.2L8,13.7l3.9,2.1c0.2,0.1,0.5,0.2,0.6,0.2c0.5,0,0.8-0.4,0.7-1.1l-0.7-4.6l3.1-3.3c0.7-0.7,0.4-1.4-0.5-1.6  l-4.3-0.7l-2-4.1C8.6,0.2,8.3,0,8,0L8,0z"
+   id="path5096"
+   style="fill:#c0c0c0;fill-opacity:1" />
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M44,15.2c-4,0-7.2-3.2-7.2-7.2c0-4,3.2-7.2,7.2-7.2  c4,0,7.2,3.2,7.2,7.2C51.2,12,48,15.2,44,15.2z M44,3c-2.8,0-5,2.2-5,5c0,2.7,2.2,5,5,5c2.8,0,5-2.2,5-5C49,5.3,46.8,3,44,3z   M43.7,8.9C43.3,8.8,43,8.4,43,8V5c0-0.6,0.4-1,1-1c0.6,0,1,0.4,1,1v2.8c1.1,1.1,2,3.2,2,3.2S44.8,10,43.7,8.9z"
+   id="path5098" />
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M86.7,9.1l-5.6,5.5C80.8,14.9,80.4,15,80,15  c-0.4,0-0.8-0.1-1.1-0.4l-5.6-5.5C72.7,8.5,72.9,8,73.8,8H77l0-6c0-0.6,0.4-1,1-1h4c0.6,0,1,0.4,1,1v6h3.2C87.1,8,87.3,8.5,86.7,9.1  z"
+   id="path5100" />
+<path
+   fill="#231F20"
+   d="M-241,52c0.5,0,1-0.4,1-1v-4c0,0,0.1-0.8,0.8-0.8c0.6,0,0.6,0.8,1.8,0.8c0.6,0,1.5-0.2,1.5-2  c0-1.8-0.9-2-1.5-2c-1.1,0-1.1,0.8-1.8,0.8c-0.6,0-0.8-0.8-0.8-0.8v-2c0-0.6-0.4-1-1-1h-3c0,0-0.8-0.1-0.8-0.8  c0-0.6,0.8-0.6,0.8-1.8c0-0.6-0.2-1.5-2-1.5c-1.8,0-2,0.9-2,1.5c0,1.1,0.8,1.1,0.8,1.8c0,0.6-0.8,0.8-0.8,0.8h-3c-0.5,0-1,0.4-1,1  l0,2.5c0,0-0.1,1.5,1.1,1.5c0.8,0,0.9-1,1.9-1c0.5,0,1,0.5,1,1.6c0,1-0.5,1.6-1,1.6c-1,0-1.1-1-1.9-1c-1.2,0-1.1,1.5-1.1,1.5l0,3.5  c0,0.6,0.4,1,1,1h3.8c0,0,1.5,0.1,1.5-1.1c0-0.8-1-0.9-1-1.9c0-0.5,0.7-1.2,1.8-1.2c1,0,1.8,0.7,1.8,1.2c0,1-1,1.1-1,1.9  c0,1.2,1.5,1.1,1.5,1.1H-241z"
+   id="path5102" />
+<g
+   id="g5104">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-200.4,41.9h-0.3v0h-14.4v0h-0.3c-0.2,0-0.4,0.2-0.4,0.4c0,0,0.3,2.2,0.5,4.4   c0.2,2.5,0.2,4.2,0.2,4.2c0,0.2,0.2,0.4,0.4,0.4h13.7c0.2,0,0.4-0.2,0.4-0.4c0,0,0.1-2.1,0.2-4.2c0.1-2.2,0.5-4.4,0.5-4.4   C-200.1,42.1-200.2,41.9-200.4,41.9z"
+   id="path5106" />
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-200.8,41.3v-2c0-0.3-0.2-0.5-0.5-0.5l-6.7,0l-0.8-1.1c0,0-0.6-0.9-1.2-0.9h-4.2   c-0.6,0-1,0.5-1,1v1l0,2.5H-200.8z"
+   id="path5108" />
+</g>
+<g
+   id="g5110">
+	<g
+   id="g5112">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-169,37h-7.5c-0.8,0-1.5,0.7-1.5,1.5v11c0,0.8,0.7,1.5,1.5,1.5    h9c0.8,0,1.5-0.7,1.5-1.5V40L-169,37z M-170,41v-3.3l3.3,3.3H-170z"
+   id="path5114" />
+	</g>
+</g>
+<g
+   id="g5116">
+	<g
+   id="g5118">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-132.5,38c-0.4,0.2-0.9,0.6-1.3,1.2c-0.3,0.4-0.5,0.9-0.8,1.3    c0.7,0.3,1.3,0.8,1.7,1.4c0.4,0.7,0.7,1.5,0.6,2.3c-0.1,1.3-1,2.4-2.1,3c-0.5-0.5-1.4-2.1-1.4-2.1c0,0,0,1.8-0.6,3    c-0.4,0.8-0.9,1.5-1.6,2.1c1,0.4,2,0.6,3,0.6l0-0.1c0,0,0.1,0.1,0.1,0.1c2.1,0,4.1-0.3,4.1-0.3s-0.9-0.6-1.3-0.9    c1.3-0.7,2.2-2,2.5-3.3c0.2-0.5,0.3-1.1,0.4-1.7C-128.7,41.9-130.2,39.3-132.5,38z M-139.8,44.2c0-1.4,0.8-2.8,2.2-3.5    c0.5,0.5,1.6,2,1.6,2s0-3.6,1.2-5.6c-4.3-0.5-6.8,0.3-6.8,0.3s1.2,0.4,1.6,0.8c-0.1,0.1-0.2,0.1-0.3,0.2c-1,0.7-1.7,1.6-2.1,2.7    c-0.3,0.6-0.5,1.3-0.5,2c-0.3,2.8,1.1,5.4,3.4,6.7c0.4-0.3,0.8-0.6,1.2-1.1c0.4-0.4,0.7-0.9,1-1.4    C-138.7,46.9-139.7,45.7-139.8,44.2z"
+   id="path5120" />
+	</g>
+</g>
+<g
+   id="g5122">
+	<g
+   id="g5124">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-96.6,41.5c-3.3-4-9.6-4.5-9.6-4.5c-0.4,0-0.8,0.3-0.8,0.7v1.4    c0,0.4,0.3,0.7,0.8,0.7c0,0,4.5,0.1,7.1,3.2c3.5,3.6,3.2,7.2,3.2,7.2c0,0.4,0.3,0.8,0.8,0.8h1.5c0.4,0,0.7-0.3,0.7-0.8    C-93,50.2-93.3,44.5-96.6,41.5z M-106.2,42c-0.4,0-0.8,0.3-0.8,0.7V44c0,0.4,0.3,0.7,0.8,0.7c0,0,2.4,0.1,3.8,1.4    c1.9,1.7,1.8,4.2,1.8,4.2c0,0.4,0.2,0.8,0.6,0.8h1.5c0.4,0,0.5-0.3,0.5-0.8c0,0-0.4-3.9-2.5-5.9C-102.5,42.4-106.2,42-106.2,42z     M-105,47c-1.1,0-2,0.9-2,2c0,1.1,0.9,2,2,2c1.1,0,2-0.9,2-2C-103,47.9-103.9,47-105,47z"
+   id="path5126" />
+	</g>
+</g>
+<path
+   fill="none"
+   d="M-102.6,34.4c0.5,0,1-0.4,1-1v-4c0,0,0.1-0.8,0.8-0.8c0.6,0,0.6,0.8,1.8,0.8c0.6,0,1.5-0.2,1.5-2  c0-1.8-0.9-2-1.5-2c-1.1,0-1.1,0.8-1.8,0.8c-0.6,0-0.8-0.8-0.8-0.8v-2c0-0.6-0.4-1-1-1h-3c0,0-0.8-0.1-0.8-0.8  c0-0.6,0.8-0.6,0.8-1.8c0-0.6-0.2-1.5-2-1.5c-1.8,0-2,0.9-2,1.5c0,1.1,0.8,1.1,0.8,1.8c0,0.6-0.8,0.8-0.8,0.8h-3c-0.5,0-1,0.4-1,1  l0,2.5c0,0-0.1,1.5,1.1,1.5c0.8,0,0.9-1,1.9-1c0.5,0,1,0.5,1,1.6c0,1-0.5,1.6-1,1.6c-1,0-1.1-1-1.9-1c-1.2,0-1.1,1.5-1.1,1.5l0,3.5  c0,0.6,0.4,1,1,1h3.8c0,0,1.5,0.1,1.5-1.1c0-0.8-1-0.9-1-1.9c0-0.5,0.7-1.2,1.8-1.2c1,0,1.8,0.7,1.8,1.2c0,1-1,1.1-1,1.9  c0,1.2,1.5,1.1,1.5,1.1H-102.6z"
+   id="path5128" />
+<g
+   id="g5130">
+	<g
+   id="g5132">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-63,46.7l-0.3-0.1L-63,52l2.6-3.8L-63,46.7z M-63.3,46l0.3,0    l6,2.9V36l-14,11l4.6-0.6L-63,52l-1.9-5.7l-0.3-0.1l0.3,0L-65,46l7-8L-63.3,46L-63.3,46z"
+   id="path5134" />
+	</g>
+</g>
+<g
+   id="g5136">
+	<g
+   id="g5138">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-23,37h-10c-1.1,0-2,0.9-2,2v10c0,1.1,0.9,2,2,2h10    c1.1,0,2-0.9,2-2V39C-21,37.9-21.9,37-23,37z M-23,46c0,1.7-1.3,2-3,2h-4c-1.7,0-3-1.3-3-3v-4c0-1.7,1.3-3,3-3h4c1.7,0,3,1.3,3,3    V46z"
+   id="path5140" />
+	</g>
+</g>
+<path
+   fill="#231F20"
+   d="M-23.7,42.6c0-0.8-0.2-1.5-0.6-2c-0.4-0.5-1-0.7-1.7-0.7c-0.4,0-0.8,0.1-1.1,0.3s-0.6,0.4-0.8,0.8  c-0.2-0.4-0.4-0.6-0.7-0.8c-0.3-0.2-0.7-0.2-1.2-0.2c-0.4,0-0.7,0-1.1,0.1s-0.7,0.2-1,0.4l0.3,0.7c0.6-0.3,1.2-0.5,1.7-0.5  c0.4,0,0.8,0.1,1,0.3c0.2,0.2,0.3,0.6,0.3,1.1v0.4l-1,0c-0.9,0-1.6,0.2-2.1,0.5c-0.5,0.3-0.7,0.8-0.7,1.4c0,0.6,0.2,1,0.5,1.3  c0.3,0.3,0.8,0.5,1.4,0.5c0.5,0,0.9-0.1,1.3-0.3c0.4-0.2,0.7-0.5,1-1c0.5,0.9,1.2,1.3,2.3,1.3c0.4,0,0.7,0,1-0.1  c0.3-0.1,0.6-0.2,0.9-0.3v-0.8c-0.3,0.1-0.6,0.3-0.9,0.3c-0.3,0.1-0.6,0.1-0.9,0.1c-1.2,0-1.8-0.7-1.8-2.2h3.9V42.6z M-28.5,43.6  c0,0.6-0.2,1-0.5,1.3c-0.3,0.3-0.7,0.5-1.3,0.5c-0.3,0-0.6-0.1-0.8-0.2c-0.2-0.2-0.3-0.4-0.3-0.8c0-0.4,0.2-0.7,0.5-0.9  c0.3-0.2,0.8-0.3,1.5-0.3l0.9,0V43.6z M-27.5,42.4c0-0.6,0.2-1,0.4-1.3c0.3-0.3,0.6-0.5,1.1-0.5c0.4,0,0.8,0.2,1,0.5  c0.2,0.3,0.4,0.8,0.4,1.3H-27.5z"
+   id="path5142" />
+<g
+   id="g5144">
+	<g
+   id="g5146">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M14.5,37h-13C0.7,37,0,37.7,0,38.5v11C0,50.3,0.7,51,1.5,51h13    c0.8,0,1.5-0.7,1.5-1.5v-11C16,37.7,15.3,37,14.5,37z M6.5,38C6.8,38,7,38.2,7,38.5C7,38.8,6.8,39,6.5,39C6.2,39,6,38.8,6,38.5    C6,38.2,6.2,38,6.5,38z M4.4,38c0.3,0,0.5,0.2,0.5,0.5c0,0.3-0.2,0.5-0.5,0.5c-0.3,0-0.5-0.2-0.5-0.5C3.9,38.2,4.2,38,4.4,38z     M2.5,38C2.8,38,3,38.2,3,38.5C3,38.8,2.8,39,2.5,39C2.2,39,2,38.8,2,38.5C2,38.2,2.2,38,2.5,38z M14,48c0,0.6-0.4,1-1,1H3    c-0.6,0-1-0.4-1-1v-6c0-0.6,0.4-1,1-1h10c0.6,0,1,0.4,1,1V48z"
+   id="path5148" />
+	</g>
+</g>
+<path
+   fill="#231F20"
+   d="M51.2,45.9L51.2,45.9c-0.4-0.1-0.7-0.3-1-0.6c-0.1-0.1-0.1-0.1-0.2-0.2c-0.1-0.1-0.1-0.1-0.2-0.2  c0-0.1-0.1-0.1-0.1-0.2c0-0.1-0.1-0.1-0.1-0.2c-0.3-0.6-0.4-1.5-0.5-2.4c-0.3-2.2-0.1-3.8-3-3.8h-4.4c-2.9,0-2.6,1.6-2.9,3.8  c-0.1,0.9-0.3,1.8-0.6,2.4c0,0,0,0,0,0c-0.1,0.1-0.1,0.2-0.2,0.3c0,0,0,0-0.1,0.1c-0.1,0.2-0.2,0.3-0.4,0.4  c-0.3,0.2-0.5,0.4-0.9,0.5l0,0C36.4,46,36,46.3,36,46.8v1.9c0,0.5,0.4,0.9,0.9,0.9h14.1c0.5,0,0.9-0.4,0.9-0.9v-1.9  C52,46.3,51.6,46,51.2,45.9z M46.4,43.8c0,0.1-0.1,0.2-0.2,0.2h-1.6v1.6c0,0.1-0.1,0.2-0.2,0.2h-0.5c-0.1,0-0.2-0.1-0.2-0.2V44h-1.6  c-0.1,0-0.2-0.1-0.2-0.2v-0.5c0-0.1,0.1-0.2,0.2-0.2h1.6v-1.6c0-0.1,0.1-0.2,0.2-0.2h0.5c0.1,0,0.2,0.1,0.2,0.2v1.6h1.6  c0.1,0,0.2,0.1,0.2,0.2V43.8z"
+   id="path5150" />
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M87.7,42c-0.1,1.1,0.2,2.5-1.3,4.4c-1.6,2.1-3.1,2.1-3.4,2.1  c-1.8-0.1-2-1.5-3-1.5c-0.9,0-1.6,1.4-3,1.5c-0.3,0-1.9,0-3.4-2c-1.5-1.9-1.2-3.3-1.3-4.4S72,39.7,72,39.7s0.7,0.7,1.6,0.8  c0.9,0.1,1.1-0.3,3-0.9c2.1-0.6,3.4,1.9,3.4,1.9s1.4-2.4,3.4-1.9c1.9,0.6,2,0.9,2.9,0.9c0.9-0.1,1.7-0.8,1.7-0.8S87.8,40.9,87.7,42z   M76.9,42.5c-1.1-0.3-1.6,0.2-2.1,0.4C74.4,43,74,43.1,74,43.1s0.1,0.7,1.2,1.2c1.1,0.6,3.5,0.3,3.5,0.3S78.9,42.9,76.9,42.5z   M85.2,42.9c-0.5-0.2-1-0.6-2.1-0.4c-2,0.4-1.8,2.1-1.8,2.1s2.4,0.3,3.5-0.3c1.1-0.6,1.2-1.2,1.2-1.2S85.6,43,85.2,42.9z"
+   id="path5152" />
+<g
+   id="g5154">
+	<g
+   id="g5156">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-237.5,84.9l-3.3-3.3c0.5-0.9,0.9-1.9,0.9-2.9c0-3-2.4-5.5-5.5-5.5    c-3,0-5.5,2.4-5.5,5.5c0,3,2.4,5.5,5.5,5.5c1.1,0,2.1-0.3,3-0.9l3.3,3.3c0.4,0.4,1,0.4,1.4,0l0.2-0.2    C-237.1,85.9-237.1,85.3-237.5,84.9z M-245.4,82c-1.8,0-3.3-1.5-3.3-3.4c0-1.9,1.5-3.4,3.3-3.4c1.8,0,3.3,1.5,3.3,3.4    C-242,80.5-243.5,82-245.4,82z"
+   id="path5158" />
+	</g>
+</g>
+<g
+   id="g5160">
+	<g
+   id="g5162">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-214.5,86.3l0.2,0.2c0.4,0.4,1,0.4,1.4,0l3.3-3.3c0.9,0.6,1.9,0.9,3,0.9    c3,0,5.5-2.4,5.5-5.5c0-3-2.4-5.5-5.5-5.5c-3,0-5.5,2.4-5.5,5.5c0,1.1,0.3,2.1,0.9,2.9l-3.3,3.3    C-214.9,85.3-214.9,85.9-214.5,86.3z M-210,78.6c0-1.9,1.5-3.4,3.3-3.4c1.8,0,3.3,1.5,3.3,3.4c0,1.9-1.5,3.4-3.3,3.4    C-208.5,82-210,80.5-210,78.6z"
+   id="path5164" />
+	</g>
+</g>
+<g
+   id="g5166">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-165,77.5h-1v-2c0-0.6-0.4-1-1-1v-1c0-0.6-0.4-1-1-1h-8   c-0.6,0-1,0.4-1,1v1c-0.6,0-1,0.4-1,1v2h-1c-0.6,0-1,0.4-1,1v5c0,0.6,0.4,1,1,1h2v-1h0.5l-1.5,3h12l-1.5-3h0.5v1h2c0.6,0,1-0.4,1-1   v-5C-164,77.9-164.4,77.5-165,77.5z M-176.5,80.5h-1c-0.3,0-0.5-0.2-0.5-0.5c0-0.3,0.2-0.5,0.5-0.5h1c0.3,0,0.5,0.2,0.5,0.5   C-176,80.2-176.2,80.5-176.5,80.5z M-176,85.5l0.9-2h6.2l0.9,2H-176z M-168,77.5c0,0.6-0.4,1-1,1h-6c-0.6,0-1-0.4-1-1v-3   c0-0.6,0.4-1,1-1h6c0.6,0,1,0.4,1,1V77.5z"
+   id="path5168" />
+</g>
+<g
+   id="g5170">
+	<path
+   fill="#231F20"
+   d="M-135,73l1.9,1.9l-2.6,2.6c-0.5,0.5-0.5,1.4,0.1,2c0.6,0.6,1.5,0.6,2,0.1l2.6-2.6l1.9,1.9v-6H-135z    M-138.5,80.4l-2.6,2.6L-143,81v6h6l-1.9-1.9l2.6-2.6c0.5-0.5,0.5-1.4-0.1-2C-137.1,79.9-138,79.9-138.5,80.4z"
+   id="path5172" />
+</g>
+<g
+   id="g5174">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-93.5,73.5c-0.6-0.6-1.5-0.6-2-0.1l-2.6,2.6L-100,74v6h6   l-1.9-1.9l2.6-2.6C-92.9,75-92.9,74.1-93.5,73.5z M-104.1,81.9l-2.6,2.6c-0.5,0.5-0.5,1.4,0.1,2c0.6,0.6,1.5,0.6,2,0.1l2.6-2.6   l1.9,1.9v-6h-6L-104.1,81.9z"
+   id="path5176" />
+</g>
+<g
+   id="g5178">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M14.6,75.1l-2.3,2.3c-0.5,0.5-1.3,0.5-1.7,0   c-0.5-0.5-0.5-1.2,0-1.7l2.4-2.3c-0.6-0.3-1.2-0.5-1.9-0.5C8.8,72.9,7,74.7,7,77c0,0.5,0.1,1.1,0.3,1.5l-5.9,5.8   c-0.6,0.6-0.6,1.7,0,2.3c0.6,0.6,1.7,0.6,2.3,0l6-5.9c0.4,0.2,0.9,0.3,1.4,0.3c2.2,0,4.1-1.8,4.1-4.1   C15.1,76.3,14.9,75.7,14.6,75.1z M2.5,86.4c-0.5,0-0.9-0.4-0.9-0.9c0-0.5,0.4-0.9,0.9-0.9c0.5,0,0.9,0.4,0.9,0.9   C3.4,86,3,86.4,2.5,86.4z"
+   id="path5180" />
+</g>
+<path
+   fill="#231F20"
+   d="M-22.7,83.9l-1.1-1.1c0.2-0.3,0.3-0.6,0.5-0.9c0.1-0.3,0.2-0.6,0.3-0.9l1.6,0c0.3,0,0.5-0.2,0.5-0.5l0-1  c0-0.3-0.2-0.5-0.5-0.5l-1.6,0c-0.1-0.6-0.4-1.2-0.7-1.8l1.1-1.1c0.2-0.2,0.2-0.5,0-0.7l-0.7-0.7c-0.2-0.2-0.5-0.2-0.7,0l-1.1,1.1  c-0.3-0.2-0.6-0.3-0.9-0.5c-0.3-0.1-0.6-0.2-0.9-0.3l0-1.6c0-0.3-0.2-0.5-0.5-0.5l-1,0c-0.3,0-0.5,0.2-0.5,0.5l0,1.6  c-0.6,0.1-1.2,0.4-1.8,0.7l-1.1-1.1c-0.2-0.2-0.5-0.2-0.7,0l-0.7,0.7c-0.2,0.2-0.2,0.5,0,0.7l1.1,1.1c-0.2,0.3-0.3,0.6-0.5,0.9  c-0.1,0.3-0.2,0.6-0.3,0.9l-1.6,0c-0.3,0-0.5,0.2-0.5,0.5l0,1c0,0.3,0.2,0.5,0.5,0.5l1.6,0c0.1,0.6,0.4,1.2,0.7,1.8l-1.1,1.1  c-0.2,0.2-0.2,0.5,0,0.7l0.7,0.7c0.2,0.2,0.5,0.2,0.7,0l1.1-1.1c0.3,0.2,0.6,0.3,0.9,0.5c0.3,0.1,0.6,0.2,0.9,0.3l0,1.6  c0,0.3,0.2,0.5,0.5,0.5l1,0c0.3,0,0.5-0.2,0.5-0.5l0-1.6c0.6-0.1,1.2-0.4,1.8-0.7l1.1,1.1c0.2,0.2,0.5,0.2,0.7,0l0.7-0.7  C-22.5,84.4-22.5,84.1-22.7,83.9z M-28.8,82.1c-1.2-0.5-1.7-1.8-1.3-3c0.5-1.2,1.8-1.7,3-1.3c1.2,0.5,1.7,1.8,1.3,3  C-26.4,82-27.7,82.6-28.8,82.1z"
+   id="path5182" />
+<g
+   id="g5184">
+	<g
+   id="g5186">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-62,77h-4c-0.6,0-1,0.4-1,1v4c0,0.6,0.4,1,1,1h4    c0.6,0,1-0.4,1-1v-4C-61,77.4-61.4,77-62,77z M-62,81c0,0.6-0.4,1-1,1h-2c-0.6,0-1-0.4-1-1v-1c0-0.6,0.4-1,1-1h2c0.6,0,1,0.4,1,1    V81z M-64,87l3-3h-6L-64,87z M-67,76h6l-3-3L-67,76z M-60,76.9v6l3-3L-60,76.9z M-68,82.9v-6l-3,3L-68,82.9z"
+   id="path5188" />
+	</g>
+</g>
+<g
+   id="g5190">
+	<g
+   id="g5192">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M37.5,76.5h13c0.8,0,1.5-0.7,1.5-1.5c0-0.8-0.7-1.5-1.5-1.5h-13    c-0.8,0-1.5,0.7-1.5,1.5C36,75.8,36.7,76.5,37.5,76.5z M50.5,78.5h-13c-0.8,0-1.5,0.7-1.5,1.5c0,0.8,0.7,1.5,1.5,1.5h13    c0.8,0,1.5-0.7,1.5-1.5C52,79.2,51.3,78.5,50.5,78.5z M50.5,83.5h-13c-0.8,0-1.5,0.7-1.5,1.5c0,0.8,0.7,1.5,1.5,1.5h13    c0.8,0,1.5-0.7,1.5-1.5C52,84.2,51.3,83.5,50.5,83.5z"
+   id="path5194" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M85.6,87c-0.8,0-1.5-0.2-2.3-1.3c-0.8-1.1-1.7-2.5-1.7-2.5  s-0.7-0.9-1.1-1.6c-0.4-0.7-1.1-0.5-1.1-0.5s-2.8-4.6-3.3-5.4c-0.7-1,0.6-2.7,0.6-2.7l4.4,7c0,0,1.3,1.9,1.9,2.3  c0.5,0.4,1.4-0.4,2.8,0.9C87.7,85,87.1,87,85.6,87z M85.4,84.1c-0.9-1-1.7-0.9-1.9-0.6c-0.2,0.3,0,1.2,0.4,1.7  c0.4,0.5,0.8,0.7,1.4,0.7C85.9,86,86.4,85.2,85.4,84.1z M81.6,79.4l-1.2-1.8l2.9-4.6c0,0,1.2,1.7,0.6,2.7  C83.6,76.1,82.5,78,81.6,79.4z M77,82.3c0.3-0.3,1-1.1,1.4-1.7l0.8,1.2c-0.4,0.6-0.9,1.4-0.9,1.4s-0.9,1.4-1.7,2.5  c-0.8,1.1-1.5,1.3-2.3,1.3c-1.4,0-2.1-2-0.1-3.8C75.6,82,76.5,82.7,77,82.3z M74.6,84.1c-0.9,1-0.4,1.8,0.2,1.8c0.6,0,1-0.2,1.4-0.7  c0.4-0.5,0.6-1.5,0.4-1.7C76.3,83.2,75.5,83.1,74.6,84.1z"
+   id="path5196" />
+<g
+   id="g5198">
+	<g
+   id="g5200">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-243,111c0-0.1-2-2-2-2c-1.7,0-5,0-5,0c-0.6,0-1,0.4-1,1v8    c0,0.6,0.4,1,1,1h4v-6.1c0-0.3,0.2-0.5,0.5-0.5h2.5V111z M-246,112v-2l2,2H-246z M-239,113c-1.7,0-5,0-5,0c-0.6,0-1,0.4-1,1v8    c0,0.6,0.4,1,1,1h6c0.6,0,1-0.4,1-1c0,0,0-4.8,0-7C-237,115-239,113-239,113z M-240,116v-2l2,2H-240z"
+   id="path5202" />
+	</g>
+</g>
+<g
+   id="g5204">
+	<g
+   id="g5206">
+		<path
+   fill="#231F20"
+   d="M-204.5,111h-1.3l0,0c0,0-0.2-2-2.2-2c-2,0-2.2,2-2.2,2l0,0h-1.3c-0.8,0-1.5,0.7-1.5,1.5v9    c0,0.8,0.7,1.5,1.5,1.5h7c0.8,0,1.5-0.7,1.5-1.5v-9C-203,111.7-203.7,111-204.5,111z M-210.7,112.1l0.8-0.4l0.4-0.2l0-0.4    c0-0.2,0.2-1.3,1.5-1.3c1.2,0,1.4,1.1,1.5,1.3l0,0.4l0.4,0.2l0.8,0.4l0.3,0.7h-6.1L-210.7,112.1z M-210.1,120.4l-2.8-4.9l3.3-1.9    h4.3l1.8,3.1L-210.1,120.4z"
+   id="path5208" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-177,115h10v2h-10V115z"
+   id="path5210" />
+<g
+   id="g5212">
+	<g
+   id="g5214">
+		<polygon
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   points="-131,115 -135,115 -135,111 -137,111 -137,115 -141,115     -141,117 -137,117 -137,121 -135,121 -135,117 -131,117   "
+   id="polygon5216" />
+	</g>
+</g>
+<g
+   id="g5218">
+	<path
+   fill="#231F20"
+   d="M-94,111.7l-3,2.7v-2c0-0.8-0.6-1.4-1.3-1.4h-7.4c-0.7,0-1.3,0.6-1.3,1.4v7.2c0,0.8,0.6,1.4,1.3,1.4h7.4   c0.7,0,1.3-0.6,1.3-1.4v-2.1l3,2.7c0.3,0.3,0.6,0.4,1,0.3v-9.1C-93.3,111.4-93.7,111.5-94,111.7z"
+   id="path5220" />
+</g>
+<g
+   id="g5222">
+	<g
+   id="g5224">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-61.9,112h-3l3.8,4l-3.8,4h3.1l3.8-4L-61.9,112z M-66.9,112h-3    l3.8,4l-3.8,4h3.1l3.8-4L-66.9,112z"
+   id="path5226" />
+	</g>
+</g>
+<g
+   id="g5228">
+	<g
+   id="g5230">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-23,111.5h-2l-3-3l-3,3h-2c-1.1,0-2,0.9-2,2v8c0,1.1,0.9,2,2,2    h10c1.1,0,2-0.9,2-2v-8C-21,112.4-21.9,111.5-23,111.5z M-32,121.5c-0.6,0-1-0.4-1-1s0.4-1,1-1s1,0.4,1,1S-31.4,121.5-32,121.5z     M-32,118.5c-0.6,0-1-0.4-1-1s0.4-1,1-1s1,0.4,1,1S-31.4,118.5-32,118.5z M-32,115.5c-0.6,0-1-0.4-1-1s0.4-1,1-1s1,0.4,1,1    S-31.4,115.5-32,115.5z M-23,121.5h-7v-2h7V121.5z M-23,118.5h-7v-2h7V118.5z M-23,115.5h-7v-2h7V115.5z"
+   id="path5232" />
+	</g>
+</g>
+<g
+   id="g5234">
+	<g
+   id="g5236">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M2,115h7c0.6,0,1-0.4,1-1v-4c0-0.6-0.4-1-1-1H2c-0.6,0-1,0.4-1,1    v4C1,114.6,1.4,115,2,115z M14,109h-2c-0.6,0-1,0.4-1,1v4c0,0.6,0.4,1,1,1h2c0.6,0,1-0.4,1-1v-4C15,109.4,14.6,109,14,109z     M14,116H8c-0.6,0-1,0.4-1,1v5c0,0.6,0.4,1,1,1h6c0.6,0,1-0.4,1-1v-5C15,116.4,14.6,116,14,116z M5,116H2c-0.6,0-1,0.4-1,1v5    c0,0.6,0.4,1,1,1h3c0.6,0,1-0.4,1-1v-5C6,116.4,5.6,116,5,116z"
+   id="path5238" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M45.8,116c0,0-0.6,0.8-1.8,0.8c-1.2,0-1.8-0.8-1.8-0.8l-6-5.1  c0.3-0.5,0.9-0.9,1.6-0.9h12.4c0.7,0,1.3,0.4,1.6,0.9L45.8,116z M42.2,117.7c0,0,0.6,0.8,1.8,0.8c1.2,0,1.8-0.8,1.8-0.8l6.2-5.4v8  c0,0.9-0.8,1.7-1.8,1.7H37.8c-1,0-1.8-0.8-1.8-1.7v-8L42.2,117.7z"
+   id="path5240" />
+<g
+   id="g5242">
+	<g
+   id="g5244">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-237.5,145h-13c-0.8,0-1.5,0.7-1.5,1.5v11    c0,0.8,0.7,1.5,1.5,1.5h13c0.8,0,1.5-0.7,1.5-1.5v-11C-236,145.7-236.7,145-237.5,145z M-245.5,146c0.3,0,0.5,0.2,0.5,0.5    c0,0.3-0.2,0.5-0.5,0.5c-0.3,0-0.5-0.2-0.5-0.5C-246,146.2-245.8,146-245.5,146z M-247.6,146c0.3,0,0.5,0.2,0.5,0.5    c0,0.3-0.2,0.5-0.5,0.5s-0.5-0.2-0.5-0.5C-248.1,146.2-247.8,146-247.6,146z M-249.5,146c0.3,0,0.5,0.2,0.5,0.5    c0,0.3-0.2,0.5-0.5,0.5s-0.5-0.2-0.5-0.5C-250,146.2-249.8,146-249.5,146z M-250,156v-6c0-0.6,0.4-1,1-1h7v8h-7    C-249.6,157-250,156.6-250,156z M-238,156c0,0.6-0.4,1-1,1h-1v-8h1c0.6,0,1,0.4,1,1V156z"
+   id="path5246" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-213.2,156c-1.3-1.2-2.2-2.8-2.2-4.6c0-3.6,3.3-6.5,7.4-6.5  c4.1,0,7.4,2.9,7.4,6.5c0,3.6-3.3,6.5-7.4,6.5c-0.8,0-1.6-0.1-2.4-0.3c-1.8,0.7-4.3,1.7-4.5,1.4C-213.9,157.9-213.5,156.8-213.2,156  z"
+   id="path5248" />
+<rect
+   id="_x3C_Slice_x3E_"
+   fill="none"
+   width="16"
+   height="16" />
+</svg>
\ No newline at end of file
--- a/browser/themes/windows/customizableui/panelUIOverlay.css
+++ b/browser/themes/windows/customizableui/panelUIOverlay.css
@@ -135,8 +135,100 @@ menu.subviewbutton > .menu-right:-moz-lo
     #zoom-controls@inAnyPanel@,
     #edit-controls@inAnyPanel@ > toolbarbutton,
     #zoom-controls@inAnyPanel@ > toolbarbutton {
       border-radius: 0;
     }
   }
 }
 %endif
+
+@media not all and (-moz-windows-default-theme) {
+  #edit-controls@inAnyPanel@ > #copy-button,
+  #zoom-controls@inAnyPanel@ > #zoom-reset-button,
+  .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton {
+    border: 1px solid transparent;
+  }
+
+  panelview .toolbarbutton-1@buttonStateHover@,
+  toolbarbutton.subviewbutton@buttonStateHover@,
+  menu.subviewbutton@menuStateHover@,
+  menuitem.subviewbutton@menuStateHover@,
+  .widget-overflow-list .toolbarbutton-1@buttonStateHover@,
+  .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton@buttonStateHover@ {
+    border-color: ThreeDLightShadow !important;
+  }
+
+  panelview:not(#PanelUI-mainView) .toolbarbutton-1@buttonStateHover@,
+  toolbarbutton.subviewbutton@buttonStateHover@,
+  menu.subviewbutton@menuStateHover@,
+  menuitem.subviewbutton@menuStateHover@,
+  .widget-overflow-list .toolbarbutton-1@buttonStateHover@ {
+    background-color: Highlight;
+    color: highlighttext;
+  }
+
+  panelview .toolbarbutton-1:-moz-any(@buttonStateActive@,[checked=true]),
+  toolbarbutton.subviewbutton@buttonStateActive@,
+  menu.subviewbutton@menuStateActive@,
+  menuitem.subviewbutton@menuStateActive@,
+  .widget-overflow-list .toolbarbutton-1@buttonStateActive@,
+  .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton@buttonStateActive@ {
+    background-color: Highlight;
+    border-color: ThreeDLightShadow;
+    color: highlighttext;
+    box-shadow: none;
+  }
+
+  panelview .toolbarbutton-1[disabled],
+  toolbarbutton.subviewbutton[disabled],
+  menu.subviewbutton[disabled],
+  menuitem.subviewbutton[disabled],
+  .widget-overflow-list .toolbarbutton-1[disabled],
+  .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton[disabled] {
+    text-shadow: none;
+  }
+
+  #PanelUI-fxa-status,
+  #PanelUI-help,
+  #PanelUI-customize {
+    border: 1px solid transparent;
+  }
+
+  #PanelUI-fxa-status:not([disabled]):hover,
+  #PanelUI-help:not([disabled]):hover,
+  #PanelUI-customize:hover,
+  #PanelUI-fxa-status:not([disabled]):hover:active,
+  #PanelUI-help:not([disabled]):hover:active,
+  #PanelUI-customize:hover:active {
+    border-color: ThreeDLightShadow;
+    box-shadow: none;
+  }
+
+  #BMB_bookmarksPopup .menu-text,
+  #BMB_bookmarksPopup menupopup {
+    color: -moz-FieldText;
+  }
+
+  #BMB_bookmarksPopup .subviewbutton[disabled=true] > .menu-text {
+    color: GrayText;
+  }
+
+  #BMB_bookmarksPopup menupopup[placespopup=true] > hbox {
+    box-shadow: none;
+    background: -moz-field;
+    border: 1px solid ThreeDShadow;
+  }
+
+  .subviewbutton.panel-subview-footer,
+  #BMB_bookmarksPopup .subviewbutton.panel-subview-footer {
+    color: ButtonText;
+  }
+
+  .subviewbutton@menuStateHover@,
+  menuitem.panel-subview-footer@menuStateHover@,
+  .subviewbutton.panel-subview-footer@buttonStateHover@,
+  .subviewbutton.panel-subview-footer@buttonStateActive@,
+  #BMB_bookmarksPopup .panel-subview-footer@menuStateHover@ > .menu-text {
+    background-color: Highlight;
+    color: highlighttext !important;
+  }
+}
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -31,16 +31,19 @@ browser.jar:
         skin/classic/browser/click-to-play-warning-stripes.png
         skin/classic/browser/content-contextmenu.svg
         skin/classic/browser/dots.png                                (../shared/dots.png)
         skin/classic/browser/dots@2x.png                             (../shared/dots@2x.png)
 *       skin/classic/browser/engineManager.css
         skin/classic/browser/fullscreen-darknoise.png
         skin/classic/browser/Geolocation-16.png
         skin/classic/browser/Geolocation-64.png
+        skin/classic/browser/heartbeat-icon.svg                      (../shared/heartbeat-icon.svg)
+        skin/classic/browser/heartbeat-star-lit.svg                  (../shared/heartbeat-star-lit.svg)
+        skin/classic/browser/heartbeat-star-off.svg                  (../shared/heartbeat-star-off.svg)
         skin/classic/browser/Info.png
         skin/classic/browser/identity.png
         skin/classic/browser/identity-icons-generic.png
         skin/classic/browser/identity-icons-https.png
         skin/classic/browser/identity-icons-https-ev.png
         skin/classic/browser/identity-icons-https-mixed-active.png
         skin/classic/browser/identity-icons-https-mixed-display.png
         skin/classic/browser/keyhole-forward-mask.svg
@@ -489,16 +492,19 @@ browser.jar:
         skin/classic/aero/browser/click-to-play-warning-stripes.png
 *       skin/classic/aero/browser/content-contextmenu.svg
         skin/classic/aero/browser/dots.png                           (../shared/dots.png)
         skin/classic/aero/browser/dots@2x.png                        (../shared/dots@2x.png)
 *       skin/classic/aero/browser/engineManager.css
         skin/classic/aero/browser/fullscreen-darknoise.png
         skin/classic/aero/browser/Geolocation-16.png
         skin/classic/aero/browser/Geolocation-64.png
+        skin/classic/aero/browser/heartbeat-icon.svg                 (../shared/heartbeat-icon.svg)
+        skin/classic/aero/browser/heartbeat-star-lit.svg             (../shared/heartbeat-star-lit.svg)
+        skin/classic/aero/browser/heartbeat-star-off.svg             (../shared/heartbeat-star-off.svg)
         skin/classic/aero/browser/Info.png                           (Info-aero.png)
         skin/classic/aero/browser/identity.png                       (identity-aero.png)
         skin/classic/aero/browser/identity-icons-generic.png
         skin/classic/aero/browser/identity-icons-https.png
         skin/classic/aero/browser/identity-icons-https-ev.png
         skin/classic/aero/browser/identity-icons-https-mixed-active.png
         skin/classic/aero/browser/identity-icons-https-mixed-display.png
         skin/classic/aero/browser/keyhole-forward-mask.svg
--- a/dom/events/MouseEvent.cpp
+++ b/dom/events/MouseEvent.cpp
@@ -461,16 +461,22 @@ MouseEvent::MozPressure() const
 NS_IMETHODIMP
 MouseEvent::GetMozPressure(float* aPressure)
 {
   NS_ENSURE_ARG_POINTER(aPressure);
   *aPressure = MozPressure();
   return NS_OK;
 }
 
+bool
+MouseEvent::HitCluster() const
+{
+  return mEvent->AsMouseEventBase()->hitCluster;
+}
+
 uint16_t
 MouseEvent::MozInputSource() const
 {
   return mEvent->AsMouseEventBase()->inputSource;
 }
 
 NS_IMETHODIMP
 MouseEvent::GetMozInputSource(uint16_t* aInputSource)
--- a/dom/events/MouseEvent.h
+++ b/dom/events/MouseEvent.h
@@ -78,16 +78,17 @@ public:
   {
     return GetMovementPoint().x;
   }
   int32_t MozMovementY()
   {
     return GetMovementPoint().y;
   }
   float MozPressure() const;
+  bool HitCluster() const;
   uint16_t MozInputSource() const;
   void InitNSMouseEvent(const nsAString& aType,
                         bool aCanBubble, bool aCancelable,
                         nsIDOMWindow* aView, int32_t aDetail, int32_t aScreenX,
                         int32_t aScreenY, int32_t aClientX, int32_t aClientY,
                         bool aCtrlKey, bool aAltKey, bool aShiftKey,
                         bool aMetaKey, uint16_t aButton,
                         EventTarget* aRelatedTarget,
--- a/dom/nfc/NfcContentHelper.js
+++ b/dom/nfc/NfcContentHelper.js
@@ -142,16 +142,20 @@ NfcContentHelper.prototype = {
     }
   },
 
   queryRFState: function queryRFState() {
     return this._rfState;
   },
 
   encodeNDEFRecords: function encodeNDEFRecords(records) {
+    if (!Array.isArray(records)) {
+      return null;
+    }
+
     let encodedRecords = [];
     for (let i = 0; i < records.length; i++) {
       let record = records[i];
       encodedRecords.push({
         tnf: record.tnf,
         type: record.type || undefined,
         id: record.id || undefined,
         payload: record.payload || undefined,
@@ -273,16 +277,26 @@ NfcContentHelper.prototype = {
     let requestId = callback.getCallbackId();
     this._requestMap[requestId] = callback;
 
     cpmm.sendAsyncMessage("NFC:ChangeRFState",
                           {requestId: requestId,
                            rfState: rfState});
   },
 
+  callDefaultFoundHandler: function callDefaultFoundHandler(sessionToken,
+                                                            isP2P,
+                                                            records) {
+    let encodedRecords = this.encodeNDEFRecords(records);
+    cpmm.sendAsyncMessage("NFC:CallDefaultFoundHandler",
+                          {sessionToken: sessionToken,
+                           isP2P: isP2P,
+                           records: encodedRecords});
+  },
+
   // nsIObserver
   observe: function observe(subject, topic, data) {
     if (topic == "xpcom-shutdown") {
       this.destroyDOMRequestHelper();
       Services.obs.removeObserver(this, NFC.TOPIC_MOZSETTINGS_CHANGED);
       Services.obs.removeObserver(this, "xpcom-shutdown");
       cpmm = null;
     } else if (topic == NFC.TOPIC_MOZSETTINGS_CHANGED) {
--- a/dom/nfc/gonk/Nfc.js
+++ b/dom/nfc/gonk/Nfc.js
@@ -52,17 +52,18 @@ updateDebug();
 
 const NFC_CONTRACTID = "@mozilla.org/nfc;1";
 const NFC_CID =
   Components.ID("{2ff24790-5e74-11e1-b86c-0800200c9a66}");
 
 const NFC_IPC_MSG_ENTRIES = [
   { permission: null,
     messages: ["NFC:AddEventListener",
-               "NFC:QueryInfo"] },
+               "NFC:QueryInfo",
+               "NFC:CallDefaultFoundHandler"] },
 
   { permission: "nfc",
     messages: ["NFC:ReadNDEF",
                "NFC:WriteNDEF",
                "NFC:MakeReadOnly",
                "NFC:Format",
                "NFC:Transceive"] },
 
@@ -222,16 +223,23 @@ XPCOMUtils.defineLazyGetter(this, "gMess
         debug("Peer already lost or " + appId + " is not a registered PeerReadytarget");
         return;
       }
 
       this.notifyDOMEvent(target, {event: NFC.PEER_EVENT_READY,
                                    sessionToken: sessionToken});
     },
 
+    callDefaultFoundHandler: function callDefaultFoundHandler(message) {
+      let sysMsg = new NfcTechDiscoveredSysMsg(message.sessionToken,
+                                               message.isP2P,
+                                               message.records || null);
+      gSystemMessenger.broadcastMessage("nfc-manager-tech-discovered", sysMsg);
+    },
+
     onTagFound: function onTagFound(message) {
       let target = this.eventListeners[this.focusApp] ||
                    this.eventListeners[NFC.SYSTEM_APP_ID];
 
       message.event = NFC.TAG_EVENT_FOUND;
 
       this.notifyDOMEvent(target, message);
 
@@ -312,16 +320,19 @@ XPCOMUtils.defineLazyGetter(this, "gMess
           // to appropriate content process.
           message.data.type = "NotifySendFileStatusResponse";
           if (message.data.status) {
             message.data.errorMsg =
               this.nfc.getErrorMessage(NFC.NFC_GECKO_ERROR_SEND_FILE_FAILED);
           }
           this.nfc.sendNfcResponse(message.data);
           return null;
+        case "NFC:CallDefaultFoundHandler":
+          this.callDefaultFoundHandler(message.data);
+          return null;
         default:
           return this.nfc.receiveMessage(message);
       }
     },
 
     /**
      * nsIObserver interface methods.
      */
@@ -497,21 +508,16 @@ Nfc.prototype = {
           if (message.records) {
             // TODO: Bug 1082493.
           } else {
             gMessageManager.onPeerEvent(NFC.PEER_EVENT_FOUND, message.sessionToken);
           }
         } else {
           gMessageManager.onTagFound(message);
         }
-
-        let sysMsg = new NfcTechDiscoveredSysMsg(message.sessionToken,
-                                                 message.isP2P,
-                                                 message.records || null);
-        gSystemMessenger.broadcastMessage("nfc-manager-tech-discovered", sysMsg);
         break;
       case "TechLostNotification":
         message.type = "techLost";
 
         // Update the upper layers with a session token (alias)
         message.sessionToken = SessionHelper.getToken(message.sessionId);
         if (SessionHelper.isP2PSession(message.sessionId)) {
           gMessageManager.onPeerEvent(NFC.PEER_EVENT_LOST, message.sessionToken);
--- a/dom/nfc/nsINfcContentHelper.idl
+++ b/dom/nfc/nsINfcContentHelper.idl
@@ -106,17 +106,17 @@ interface nsINfcRequestCallback : nsISup
 interface nsINfcBrowserAPI : nsISupports
 {
   const int32_t SYSTEM_APP_ID = -1;
 
   void setFocusApp(in uint64_t tabId,
                    in boolean isFocus);
 };
 
-[scriptable, uuid(b5194ae8-d5d5-482f-a73f-dd0d755a1972)]
+[scriptable, uuid(b35f4bf5-e1b8-45f4-b5d3-2ae9b6d5871e)]
 interface nsINfcContentHelper : nsISupports
 {
   void init(in nsIDOMWindow window);
 
   /**
    * Read current NDEF data on the tag.
    *
    * @param sessionToken
@@ -273,9 +273,24 @@ interface nsINfcContentHelper : nsISuppo
    *
    * @param rfState. Possible values are 'idle', 'listen' and 'discovery'.
    *
    * @param callback
    *        Called when request is finished
    */
   void changeRFState(in DOMString rfState,
                      in nsINfcRequestCallback callback);
+
+  /**
+   * Notify parent process to call the default tagfound or peerfound event
+   * handler.
+   *
+   * @param sessionToken
+   *        Session token of this event.
+   * @param isP2P
+   *        Is this a P2P Session.
+   * @param records
+   *        NDEF Records.
+   */
+  void callDefaultFoundHandler(in DOMString sessionToken,
+                               in boolean isP2P,
+                               in nsIVariant records);
 };
--- a/dom/nfc/nsNfc.js
+++ b/dom/nfc/nsNfc.js
@@ -426,28 +426,39 @@ MozNFCImpl.prototype = {
       return;
     }
 
     let appId = this._window.document.nodePrincipal.appId;
     this._nfcContentHelper.unregisterTargetForPeerReady(appId);
   },
 
   notifyTagFound: function notifyTagFound(sessionToken, tagInfo, ndefInfo, records) {
+    if (!this.handleTagFound(sessionToken, tagInfo, ndefInfo, records)) {
+      this._nfcContentHelper.callDefaultFoundHandler(sessionToken, false, records);
+    };
+  },
+
+  /**
+   * Handles Tag Found event.
+   *
+   * returns true if the app could process this event, false otherwise.
+   */
+  handleTagFound: function handleTagFound(sessionToken, tagInfo, ndefInfo, records) {
     if (this.hasDeadWrapper()) {
       dump("this._window or this.__DOM_IMPL__ is a dead wrapper.");
-      return;
+      return false;
     }
 
     if (!this.eventService.hasListenersFor(this.__DOM_IMPL__, "tagfound")) {
       debug("ontagfound is not registered.");
-      return;
+      return false;
     }
 
     if (!this.checkPermissions(["nfc"])) {
-      return;
+      return false;
     }
 
     this.eventService.addSystemEventListener(this._window, "visibilitychange",
       this, /* useCapture */false);
 
     let tagImpl = new MozNFCTagImpl(this._window, sessionToken, tagInfo, ndefInfo);
     let tag = this._window.MozNFCTag._create(this._window, tagImpl);
 
@@ -460,23 +471,33 @@ MozNFCImpl.prototype = {
       let record = records[i];
       ndefRecords.push(new this._window.MozNDEFRecord({tnf: record.tnf,
                                                        type: record.type,
                                                        id: record.id,
                                                        payload: record.payload}));
     }
 
     let eventData = {
+      "cancelable": true,
       "tag": tag,
       "ndefRecords": ndefRecords
     };
 
     debug("fire ontagfound " + sessionToken);
     let tagEvent = new this._window.MozNFCTagEvent("tagfound", eventData);
     this.__DOM_IMPL__.dispatchEvent(tagEvent);
+
+    // If defaultPrevented is false, means we need to take the default action
+    // for this event - redirect this event to System app. Before redirecting to
+    // System app, we need revoke the tag object first.
+    if (!tagEvent.defaultPrevented) {
+      this.notifyTagLost(sessionToken);
+    }
+
+    return tagEvent.defaultPrevented;
   },
 
   notifyTagLost: function notifyTagLost(sessionToken) {
     if (this.hasDeadWrapper()) {
       dump("this._window or this.__DOM_IMPL__ is a dead wrapper.");
       return;
     }
 
@@ -499,43 +520,78 @@ MozNFCImpl.prototype = {
     this.nfcTag = null;
 
     debug("fire ontaglost " + sessionToken);
     let event = new this._window.Event("taglost");
     this.__DOM_IMPL__.dispatchEvent(event);
   },
 
   notifyPeerFound: function notifyPeerFound(sessionToken, isPeerReady) {
+    if (!this.handlePeerFound(sessionToken, isPeerReady)) {
+      this._nfcContentHelper.callDefaultFoundHandler(sessionToken, true, null);
+    }
+  },
+
+  /**
+   * Handles Peer Found/Peer Ready event.
+   *
+   * returns true if the app could process this event, false otherwise.
+   */
+  handlePeerFound: function handlePeerFound(sessionToken, isPeerReady) {
     if (this.hasDeadWrapper()) {
       dump("this._window or this.__DOM_IMPL__ is a dead wrapper.");
-      return;
+      return false;
     }
 
     if (!isPeerReady &&
         !this.eventService.hasListenersFor(this.__DOM_IMPL__, "peerfound")) {
       debug("onpeerfound is not registered.");
-      return;
+      return false;
     }
 
     let perm = isPeerReady ? ["nfc-share"] : ["nfc"];
     if (!this.checkPermissions(perm)) {
-      return;
+      return false;
     }
 
     this.eventService.addSystemEventListener(this._window, "visibilitychange",
       this, /* useCapture */false);
 
     let peerImpl = new MozNFCPeerImpl(this._window, sessionToken);
     this.nfcPeer = this._window.MozNFCPeer._create(this._window, peerImpl);
-    let eventData = { "peer": this.nfcPeer };
-    let type = (isPeerReady) ? "peerready" : "peerfound";
+
+    let eventType;
+    let eventData = {
+      "peer": this.nfcPeer
+    };
+
+    if (isPeerReady) {
+      eventType = "peerready";
+    } else {
+      eventData.cancelable = true;
+      eventType = "peerfound";
+    }
 
-    debug("fire on" + type + " " + sessionToken);
-    let event = new this._window.MozNFCPeerEvent(type, eventData);
+    debug("fire on" + eventType + " " + sessionToken);
+    let event = new this._window.MozNFCPeerEvent(eventType, eventData);
     this.__DOM_IMPL__.dispatchEvent(event);
+
+    // For peerready we don't take the default action.
+    if (isPeerReady) {
+      return true;
+    }
+
+    // If defaultPrevented is false, means we need to take the default action
+    // for this event - redirect this event to System app. Before redirecting to
+    // System app, we need revoke the peer object first.
+    if (!event.defaultPrevented) {
+      this.notifyPeerLost(sessionToken);
+    }
+
+    return event.defaultPrevented;
   },
 
   notifyPeerLost: function notifyPeerLost(sessionToken) {
     if (this.hasDeadWrapper()) {
       dump("this._window or this.__DOM_IMPL__ is a dead wrapper.");
       return;
     }
 
--- a/dom/storage/DOMStorageDBThread.cpp
+++ b/dom/storage/DOMStorageDBThread.cpp
@@ -36,17 +36,17 @@ namespace dom {
 
 DOMStorageDBBridge::DOMStorageDBBridge()
 {
 }
 
 
 DOMStorageDBThread::DOMStorageDBThread()
 : mThread(nullptr)
-, mMonitor("DOMStorageThreadMonitor")
+, mThreadObserver(new ThreadObserver())
 , mStopIOThread(false)
 , mWALModeEnabled(false)
 , mDBReady(false)
 , mStatus(NS_OK)
 , mWorkerStatements(mWorkerConnection)
 , mReaderStatements(mReaderConnection)
 , mDirtyEpoch(0)
 , mFlushImmediately(false)
@@ -71,17 +71,17 @@ DOMStorageDBThread::Init()
 
   // Ensure mozIStorageService init on the main thread first.
   nsCOMPtr<mozIStorageService> service =
     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Need to keep the lock to avoid setting mThread later then
   // the thread body executes.
-  MonitorAutoLock monitor(mMonitor);
+  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 
   mThread = PR_CreateThread(PR_USER_THREAD, &DOMStorageDBThread::ThreadFunc, this,
                             PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD,
                             262144);
   if (!mThread) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
@@ -93,17 +93,17 @@ DOMStorageDBThread::Shutdown()
 {
   if (!mThread) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS> timer;
 
   {
-    MonitorAutoLock monitor(mMonitor);
+    MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 
     // After we stop, no other operations can be accepted
     mFlushImmediately = true;
     mStopIOThread = true;
     monitor.Notify();
   }
 
   PR_JoinThread(mThread);
@@ -125,17 +125,17 @@ DOMStorageDBThread::SyncPreload(DOMStora
   }
 
   // Bypass sync load when an update is pending in the queue to write, we would
   // get incosistent data in the cache.  Also don't allow sync main-thread preload
   // when DB open and init is still pending on the background thread.
   if (mDBReady && mWALModeEnabled) {
     bool pendingTasks;
     {
-      MonitorAutoLock monitor(mMonitor);
+      MonitorAutoLock monitor(mThreadObserver->GetMonitor());
       pendingTasks = mPendingTasks.IsScopeUpdatePending(aCache->Scope()) ||
                      mPendingTasks.IsScopeClearPending(aCache->Scope());
     }
 
     if (!pendingTasks) {
       // WAL is enabled, thus do the load synchronously on the main thread.
       DBOperation preload(DBOperation::opPreload, aCache);
       preload.PerformAndFinalize(this);
@@ -152,25 +152,25 @@ DOMStorageDBThread::SyncPreload(DOMStora
   if (NS_SUCCEEDED(rv)) {
     aCache->LoadWait();
   }
 }
 
 void
 DOMStorageDBThread::AsyncFlush()
 {
-  MonitorAutoLock monitor(mMonitor);
+  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
   mFlushImmediately = true;
   monitor.Notify();
 }
 
 bool
 DOMStorageDBThread::ShouldPreloadScope(const nsACString& aScope)
 {
-  MonitorAutoLock monitor(mMonitor);
+  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
   return mScopesHavingData.Contains(aScope);
 }
 
 namespace { // anon
 
 PLDHashOperator
 GetScopesHavingDataEnum(nsCStringHashKey* aKey, void* aArg)
 {
@@ -180,36 +180,36 @@ GetScopesHavingDataEnum(nsCStringHashKey
   return PL_DHASH_NEXT;
 }
 
 } // anon
 
 void
 DOMStorageDBThread::GetScopesHavingData(InfallibleTArray<nsCString>* aScopes)
 {
-  MonitorAutoLock monitor(mMonitor);
+  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
   mScopesHavingData.EnumerateEntries(GetScopesHavingDataEnum, aScopes);
 }
 
 nsresult
 DOMStorageDBThread::InsertDBOp(DOMStorageDBThread::DBOperation* aOperation)
 {
-  MonitorAutoLock monitor(mMonitor);
+  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 
   // Sentinel to don't forget to delete the operation when we exit early.
   nsAutoPtr<DOMStorageDBThread::DBOperation> opScope(aOperation);
 
   if (mStopIOThread) {
     // Thread use after shutdown demanded.
     MOZ_ASSERT(false);
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (NS_FAILED(mStatus)) {
-    MonitorAutoUnlock unlock(mMonitor);
+    MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
     aOperation->Finalize(mStatus);
     return mStatus;
   }
 
   switch (aOperation->Type()) {
   case DBOperation::opPreload:
   case DBOperation::opPreloadUrgent:
     if (mPendingTasks.IsScopeUpdatePending(aOperation->Scope())) {
@@ -220,17 +220,17 @@ DOMStorageDBThread::InsertDBOp(DOMStorag
       // before the pending flush, we would have got an inconsistent cache content.
       mFlushImmediately = true;
     } else if (mPendingTasks.IsScopeClearPending(aOperation->Scope())) {
       // The scope is scheduled to be cleared, so just quickly load as empty.
       // We need to do this to prevent load of the DB data before the scope has
       // actually been cleared from the database.  Preloads are processed
       // immediately before update and clear operations on the database that
       // are flushed periodically in batches.
-      MonitorAutoUnlock unlock(mMonitor);
+      MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
       aOperation->Finalize(NS_OK);
       return NS_OK;
     }
     // NO BREAK
 
   case DBOperation::opGetUsage:
     if (aOperation->Type() == DBOperation::opPreloadUrgent) {
       SetHigherPriority(); // Dropped back after urgent preload execution
@@ -287,59 +287,111 @@ DOMStorageDBThread::ThreadFunc(void* aAr
   mozilla::IOInterposer::UnregisterCurrentThread();
 }
 
 void
 DOMStorageDBThread::ThreadFunc()
 {
   nsresult rv = InitDatabase();
 
-  MonitorAutoLock lockMonitor(mMonitor);
+  MonitorAutoLock lockMonitor(mThreadObserver->GetMonitor());
 
   if (NS_FAILED(rv)) {
     mStatus = rv;
     mStopIOThread = true;
     return;
   }
 
-  while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() || mPendingTasks.HasTasks())) {
+  // Create an nsIThread for the current PRThread, so we can observe runnables
+  // dispatched to it.
+  nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
+  nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread);
+  MOZ_ASSERT(threadInternal); // Should always succeed.
+  threadInternal->SetObserver(mThreadObserver);
+
+  while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() ||
+                    mPendingTasks.HasTasks() ||
+                    mThreadObserver->HasPendingEvents())) {
+    // Process xpcom events first.
+    while (MOZ_UNLIKELY(mThreadObserver->HasPendingEvents())) {
+      mThreadObserver->ClearPendingEvents();
+      MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
+      bool processedEvent;
+      do {
+        rv = thread->ProcessNextEvent(false, &processedEvent);
+      } while (NS_SUCCEEDED(rv) && processedEvent);
+    }
+
     if (MOZ_UNLIKELY(TimeUntilFlush() == 0)) {
       // Flush time is up or flush has been forced, do it now.
       UnscheduleFlush();
       if (mPendingTasks.Prepare()) {
         {
-          MonitorAutoUnlock unlockMonitor(mMonitor);
+          MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
           rv = mPendingTasks.Execute(this);
         }
 
         if (!mPendingTasks.Finalize(rv)) {
           mStatus = rv;
           NS_WARNING("localStorage DB access broken");
         }
       }
       NotifyFlushCompletion();
     } else if (MOZ_LIKELY(mPreloads.Length())) {
       nsAutoPtr<DBOperation> op(mPreloads[0]);
       mPreloads.RemoveElementAt(0);
       {
-        MonitorAutoUnlock unlockMonitor(mMonitor);
+        MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
         op->PerformAndFinalize(this);
       }
 
       if (op->Type() == DBOperation::opPreloadUrgent) {
         SetDefaultPriority(); // urgent preload unscheduled
       }
     } else if (MOZ_UNLIKELY(!mStopIOThread)) {
       lockMonitor.Wait(TimeUntilFlush());
     }
   } // thread loop
 
   mStatus = ShutdownDatabase();
+
+  if (threadInternal) {
+    threadInternal->SetObserver(nullptr);
+  }
 }
 
+
+NS_IMPL_ISUPPORTS(DOMStorageDBThread::ThreadObserver, nsIThreadObserver)
+
+NS_IMETHODIMP
+DOMStorageDBThread::ThreadObserver::OnDispatchedEvent(nsIThreadInternal *thread)
+{
+  MonitorAutoLock lock(mMonitor);
+  mHasPendingEvents = true;
+  lock.Notify();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMStorageDBThread::ThreadObserver::OnProcessNextEvent(nsIThreadInternal *thread,
+                                       bool mayWait,
+                                       uint32_t recursionDepth)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMStorageDBThread::ThreadObserver::AfterProcessNextEvent(nsIThreadInternal *thread,
+                                          uint32_t recursionDepth,
+                                          bool eventWasProcessed)
+{
+  return NS_OK;
+}
+
+
 extern void
 ReverseString(const nsCSubstring& aSource, nsCSubstring& aResult);
 
 namespace { // anon
 
 class nsReverseStringSQLFunction MOZ_FINAL : public mozIStorageFunction
 {
   ~nsReverseStringSQLFunction() {}
@@ -503,17 +555,17 @@ DOMStorageDBThread::InitDatabase()
   NS_ENSURE_SUCCESS(rv, rv);
   mozStorageStatementScoper scope(stmt);
 
   while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
     nsAutoCString foundScope;
     rv = stmt->GetUTF8String(0, foundScope);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    MonitorAutoLock monitor(mMonitor);
+    MonitorAutoLock monitor(mThreadObserver->GetMonitor());
     mScopesHavingData.PutEntry(foundScope);
   }
 
   return NS_OK;
 }
 
 nsresult
 DOMStorageDBThread::SetJournalMode(bool aIsWal)
@@ -643,17 +695,17 @@ DOMStorageDBThread::ScheduleFlush()
 {
   if (mDirtyEpoch) {
     return; // Already scheduled
   }
 
   mDirtyEpoch = PR_IntervalNow() | 1; // Must be non-zero to indicate we are scheduled
 
   // Wake the monitor from indefinite sleep...
-  mMonitor.Notify();
+  (mThreadObserver->GetMonitor()).Notify();
 }
 
 void
 DOMStorageDBThread::UnscheduleFlush()
 {
   // We are just about to do the flush, drop flags
   mFlushImmediately = false;
   mDirtyEpoch = 0;
--- a/dom/storage/DOMStorageDBThread.h
+++ b/dom/storage/DOMStorageDBThread.h
@@ -10,16 +10,17 @@
 #include "prinrval.h"
 #include "nsTArray.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/storage/StatementCache.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsClassHashtable.h"
 #include "nsIFile.h"
+#include "nsIThreadInternal.h"
 
 class mozIStorageConnection;
 
 namespace mozilla {
 namespace dom {
 
 class DOMStorageCacheBridge;
 class DOMStorageUsageBridge;
@@ -203,16 +204,44 @@ public:
 
     // Collection of all tasks, valid only between Prepare() and Execute()
     nsTArray<nsAutoPtr<DBOperation> > mExecList;
 
     // Number of failing flush attempts
     uint32_t mFlushFailureCount;
   };
 
+  class ThreadObserver MOZ_FINAL : public nsIThreadObserver
+  {
+    NS_DECL_THREADSAFE_ISUPPORTS
+    NS_DECL_NSITHREADOBSERVER
+
+    ThreadObserver()
+      : mHasPendingEvents(false)
+      , mMonitor("DOMStorageThreadMonitor")
+    {
+    }
+
+    bool HasPendingEvents() {
+      mMonitor.AssertCurrentThreadOwns();
+      return mHasPendingEvents;
+    }
+    void ClearPendingEvents() {
+      mMonitor.AssertCurrentThreadOwns();
+      mHasPendingEvents = false;
+    }
+    Monitor& GetMonitor() { return mMonitor; }
+
+  private:
+    virtual ~ThreadObserver() {}
+    bool mHasPendingEvents;
+    // The monitor we drive the thread with
+    Monitor mMonitor;
+  };
+
 public:
   DOMStorageDBThread();
   virtual ~DOMStorageDBThread() {}
 
   virtual nsresult Init();
   virtual nsresult Shutdown();
 
   virtual void AsyncPreload(DOMStorageCacheBridge* aCache, bool aPriority = false)
@@ -245,20 +274,21 @@ public:
 
   virtual bool ShouldPreloadScope(const nsACString& aScope);
   virtual void GetScopesHavingData(InfallibleTArray<nsCString>* aScopes);
 
 private:
   nsCOMPtr<nsIFile> mDatabaseFile;
   PRThread* mThread;
 
-  // The monitor we drive the thread with
-  Monitor mMonitor;
+  // Used to observe runnables dispatched to our thread and to monitor it.
+  nsRefPtr<ThreadObserver> mThreadObserver;
 
-  // Flag to stop, protected by the monitor
+  // Flag to stop, protected by the monitor returned by
+  // mThreadObserver->GetMonitor().
   bool mStopIOThread;
 
   // Whether WAL is enabled
   bool mWALModeEnabled;
 
   // Whether DB has already been open, avoid races between main thread reads
   // and pending DB init in the background I/O thread
   bool mDBReady;
--- a/dom/webidl/MouseEvent.webidl
+++ b/dom/webidl/MouseEvent.webidl
@@ -101,11 +101,13 @@ partial interface MouseEvent
                                        boolean ctrlKeyArg,
                                        boolean altKeyArg,
                                        boolean shiftKeyArg,
                                        boolean metaKeyArg,
                                        short buttonArg,
                                        EventTarget? relatedTargetArg,
                                        float pressure,
                                        unsigned short inputSourceArg);
+  [ChromeOnly]
+  readonly attribute boolean hitCluster; // True when touch occurs in a cluster of links
 
 };
 
--- a/dom/webidl/MozNFC.webidl
+++ b/dom/webidl/MozNFC.webidl
@@ -85,28 +85,43 @@ interface MozNFC : EventTarget {
    * This event will be fired when another NFCPeer is detected, and user confirms
    * to share data to the NFCPeer object by calling mozNFC.notifyUserAcceptedP2P.
    * The event will be type of NFCPeerEvent.
    */
   [CheckPermissions="nfc-share", AvailableIn=CertifiedApps]
   attribute EventHandler onpeerready;
 
   /**
-   * This event will be fired when a NFCPeer is detected.
+   * This event will be fired when a NFCPeer is detected. The application has to
+   * be running on the foreground (decided by System app) to receive this event.
+   *
+   * The default action of this event is to dispatch the event in System app
+   * again, and System app will run the default UX behavior (like vibration).
+   * So if the application would like to cancel the event, the application
+   * should call event.preventDefault() or return false in this event handler.
    */
   attribute EventHandler onpeerfound;
 
   /**
    * This event will be fired when NFCPeer, earlier detected in onpeerready
    * or onpeerfound, moves out of range.
    */
   attribute EventHandler onpeerlost;
 
   /**
-   * Ths event will be fired when a NFCTag is detected.
+   * This event will be fired when a NFCTag is detected. The application has to
+   * be running on the foreground (decided by System app) to receive this event.
+   *
+   * The default action of this event is to dispatch the event in System app
+   * again, and System app will run the default UX behavior (like vibration) and
+   * launch MozActivity to handle the content of the tag. (For example, System
+   * app will launch Browser if the tag contains URL). So if the application
+   * would like to cancel the event, i.e. in the above example, the application
+   * would process the URL by itself without launching Browser, the application
+   * should call event.preventDefault() or return false in this event handler.
    */
   attribute EventHandler ontagfound;
 
   /**
    * This event will be fired if the tag detected in ontagfound has been removed.
    */
   attribute EventHandler ontaglost;
 };
--- a/layout/base/PositionedEventTargeting.cpp
+++ b/layout/base/PositionedEventTargeting.cpp
@@ -70,16 +70,17 @@ namespace mozilla {
 struct EventRadiusPrefs
 {
   uint32_t mVisitedWeight; // in percent, i.e. default is 100
   uint32_t mSideRadii[4]; // TRBL order, in millimetres
   bool mEnabled;
   bool mRegistered;
   bool mTouchOnly;
   bool mRepositionEventCoords;
+  bool mTouchClusterDetection;
 };
 
 static EventRadiusPrefs sMouseEventRadiusPrefs;
 static EventRadiusPrefs sTouchEventRadiusPrefs;
 
 static const EventRadiusPrefs*
 GetPrefsFor(EventClassID aEventClassID)
 {
@@ -116,16 +117,19 @@ GetPrefsFor(EventClassID aEventClassID)
       Preferences::AddBoolVarCache(&prefs->mTouchOnly,
           "ui.mouse.radius.inputSource.touchOnly", true);
     } else {
       prefs->mTouchOnly = false;
     }
 
     nsPrintfCString repositionPref("ui.%s.radius.reposition", prefBranch);
     Preferences::AddBoolVarCache(&prefs->mRepositionEventCoords, repositionPref.get(), false);
+
+    nsPrintfCString touchClusterPref("ui.zoomedview.enabled", prefBranch);
+    Preferences::AddBoolVarCache(&prefs->mTouchClusterDetection, touchClusterPref.get(), false);
   }
 
   return prefs;
 }
 
 static bool
 HasMouseListener(nsIContent* aContent)
 {
@@ -311,17 +315,18 @@ SubtractFromExposedRegion(nsRegion* aExp
   if (tmp.GetNumRects() <= 15 || tmp.Area() <= aExposedRegion->Area()/2) {
     *aExposedRegion = tmp;
   }
 }
 
 static nsIFrame*
 GetClosest(nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame,
            const nsRect& aTargetRect, const EventRadiusPrefs* aPrefs,
-           nsIFrame* aRestrictToDescendants, nsTArray<nsIFrame*>& aCandidates)
+           nsIFrame* aRestrictToDescendants, nsTArray<nsIFrame*>& aCandidates,
+           int32_t* aElementsInCluster)
 {
   nsIFrame* bestTarget = nullptr;
   // Lower is better; distance is in appunits
   float bestDistance = 1e6f;
   nsRegion exposedRegion(aTargetRect);
   for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
     nsIFrame* f = aCandidates[i];
     PET_LOG("Checking candidate %p\n", f);
@@ -353,16 +358,18 @@ GetClosest(nsIFrame* aRoot, const nsPoin
       PET_LOG("  candidate %p was ancestor for bestTarget %p\n", f, bestTarget);
       continue;
     }
     if (!nsLayoutUtils::IsAncestorFrameCrossDoc(aRestrictToDescendants, f, aRoot)) {
       PET_LOG("  candidate %p was not descendant of restrictroot %p\n", f, aRestrictToDescendants);
       continue;
     }
 
+    (*aElementsInCluster)++;
+
     // distance is in appunits
     float distance = ComputeDistanceFromRegion(aPointRelativeToRootFrame, region);
     nsIContent* content = f->GetContent();
     if (content && content->IsElement() &&
         content->AsElement()->State().HasState(
                                         EventStates(NS_EVENT_STATE_VISITED))) {
       distance *= aPrefs->mVisitedWeight / 100.0f;
     }
@@ -419,20 +426,28 @@ FindFrameTargetedByInputEvent(WidgetGUIE
   PET_LOG("Expanded point to target rect %s\n",
     mozilla::layers::Stringify(targetRect).c_str());
   nsAutoTArray<nsIFrame*,8> candidates;
   nsresult rv = nsLayoutUtils::GetFramesForArea(aRootFrame, targetRect, candidates, flags);
   if (NS_FAILED(rv)) {
     return target;
   }
 
+  int32_t elementsInCluster = 0;
+
   nsIFrame* closestClickable =
     GetClosest(aRootFrame, aPointRelativeToRootFrame, targetRect, prefs,
-               restrictToDescendants, candidates);
+               restrictToDescendants, candidates, &elementsInCluster);
   if (closestClickable) {
+    if (prefs->mTouchClusterDetection && elementsInCluster > 1) {
+      if (aEvent->mClass == eMouseEventClass) {
+        WidgetMouseEventBase* mouseEventBase = aEvent->AsMouseEventBase();
+        mouseEventBase->hitCluster = true;
+      }
+    }
     target = closestClickable;
   }
   PET_LOG("Final target is %p\n", target);
 
   // Uncomment this to dump the frame tree to help with debugging.
   // Note that dumping the frame tree at the top of the function may flood
   // logcat on Android devices and cause the PET_LOGs to get dropped.
   // aRootFrame->DumpFrameTree();
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -390,16 +390,18 @@ pref("devtools.errorconsole.enabled", fa
 // to communicate with a usb cable via adb forward.
 pref("devtools.debugger.unix-domain-socket", "/data/data/@ANDROID_PACKAGE_NAME@/firefox-debugger-socket");
 
 pref("font.size.inflation.minTwips", 120);
 
 // When true, zooming will be enabled on all sites, even ones that declare user-scalable=no.
 pref("browser.ui.zoom.force-user-scalable", false);
 
+pref("ui.zoomedview.enabled", false);
+
 pref("ui.touch.radius.enabled", false);
 pref("ui.touch.radius.leftmm", 3);
 pref("ui.touch.radius.topmm", 5);
 pref("ui.touch.radius.rightmm", 3);
 pref("ui.touch.radius.bottommm", 2);
 pref("ui.touch.radius.visitedWeight", 120);
 
 pref("ui.mouse.radius.enabled", true);
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -950,25 +950,25 @@ public class BrowserApp extends GeckoApp
         ps.show("", "", items, ListView.CHOICE_MODE_NONE);
     }
 
     private void setDynamicToolbarEnabled(boolean enabled) {
         ThreadUtils.assertOnUiThread();
 
         if (enabled) {
             if (mLayerView != null) {
-                mLayerView.setOnMetricsChangedListener(this);
+                mLayerView.setOnMetricsChangedDynamicToolbarViewportListener(this);
             }
             setToolbarMargin(0);
             mHomePagerContainer.setPadding(0, mBrowserChrome.getHeight(), 0, 0);
         } else {
             // Immediately show the toolbar when disabling the dynamic
             // toolbar.
             if (mLayerView != null) {
-                mLayerView.setOnMetricsChangedListener(null);
+               mLayerView.setOnMetricsChangedDynamicToolbarViewportListener(null);
             }
             mHomePagerContainer.setPadding(0, 0, 0, 0);
             if (mBrowserChrome != null) {
                 ViewHelper.setTranslationY(mBrowserChrome, 0);
             }
         }
 
         refreshToolbarHeight();
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -125,16 +125,18 @@ public abstract class GeckoApp
     LocationListener,
     NativeEventListener,
     SensorEventListener,
     Tabs.OnTabsChangedListener {
 
     private static final String LOGTAG = "GeckoApp";
     private static final int ONE_DAY_MS = 1000*60*60*24;
 
+    private static final boolean ZOOMED_VIEW_ENABLED = AppConstants.NIGHTLY_BUILD;
+
     private static enum StartupAction {
         NORMAL,     /* normal application start */
         URL,        /* launched with a passed URL */
         PREFETCH    /* launched with a passed URL that we prefetch */
     }
 
     public static final String ACTION_ALERT_CALLBACK       = "org.mozilla.gecko.ACTION_ALERT_CALLBACK";
     public static final String ACTION_HOMESCREEN_SHORTCUT  = "org.mozilla.gecko.BOOKMARK";
@@ -168,16 +170,17 @@ public abstract class GeckoApp
     protected MenuPanel mMenuPanel;
     protected Menu mMenu;
     protected GeckoProfile mProfile;
     protected boolean mIsRestoringActivity;
 
     private ContactService mContactService;
     private PromptService mPromptService;
     private TextSelection mTextSelection;
+    private ZoomedView mZoomedView;
 
     protected DoorHangerPopup mDoorHangerPopup;
     protected FormAssistPopup mFormAssistPopup;
     protected ButtonToast mToast;
 
     protected LayerView mLayerView;
     private AbsoluteLayout mPluginContainer;
 
@@ -1573,16 +1576,21 @@ public abstract class GeckoApp
         mContactService = new ContactService(EventDispatcher.getInstance(), this);
 
         mPromptService = new PromptService(this);
 
         mTextSelection = new TextSelection((TextSelectionHandle) findViewById(R.id.anchor_handle),
                                            (TextSelectionHandle) findViewById(R.id.caret_handle),
                                            (TextSelectionHandle) findViewById(R.id.focus_handle));
 
+        if (ZOOMED_VIEW_ENABLED) {
+            ViewStub stub = (ViewStub) findViewById(R.id.zoomed_view_stub);
+            mZoomedView = (ZoomedView) stub.inflate();
+        }
+
         PrefsHelper.getPref("app.update.autodownload", new PrefsHelper.PrefHandlerBase() {
             @Override public void prefValue(String pref, String value) {
                 UpdateServiceHelper.registerForUpdates(GeckoApp.this, value);
             }
         });
 
         // Trigger the completion of the telemetry timer that wraps activity startup,
         // then grab the duration to give to FHR.
@@ -2043,16 +2051,19 @@ public abstract class GeckoApp
         if (mFormAssistPopup != null)
             mFormAssistPopup.destroy();
         if (mContactService != null)
             mContactService.destroy();
         if (mPromptService != null)
             mPromptService.destroy();
         if (mTextSelection != null)
             mTextSelection.destroy();
+        if (mZoomedView != null) {
+            mZoomedView.destroy();
+        }
         NotificationHelper.destroy();
         IntentHelper.destroy();
         GeckoNetworkManager.destroy();
 
         if (SmsManager.isEnabled()) {
             SmsManager.getInstance().stop();
             if (isFinishing()) {
                 SmsManager.getInstance().shutdown();
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -100,17 +100,18 @@ public class GeckoEvent {
         PREFERENCES_OBSERVE(39),
         PREFERENCES_GET(40),
         PREFERENCES_REMOVE_OBSERVERS(41),
         TELEMETRY_UI_SESSION_START(42),
         TELEMETRY_UI_SESSION_STOP(43),
         TELEMETRY_UI_EVENT(44),
         GAMEPAD_ADDREMOVE(45),
         GAMEPAD_DATA(46),
-        LONG_PRESS(47);
+        LONG_PRESS(47),
+        ZOOMEDVIEW(48);
 
         public final int value;
 
         private NativeGeckoEvent(int value) {
             this.value = value;
         }
     }
 
@@ -744,16 +745,27 @@ public class GeckoEvent {
         GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.THUMBNAIL);
         event.mPoints = new Point[1];
         event.mPoints[0] = new Point(bufw, bufh);
         event.mMetaState = tabId;
         event.mBuffer = buffer;
         return event;
     }
 
+    public static GeckoEvent createZoomedViewEvent(int tabId, int x, int y, int bufw, int bufh, float scaleFactor, ByteBuffer buffer) {
+        GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.ZOOMEDVIEW);
+        event.mPoints = new Point[2];
+        event.mPoints[0] = new Point(x, y);
+        event.mPoints[1] = new Point(bufw, bufh);
+        event.mX = (double) scaleFactor;
+        event.mMetaState = tabId;
+        event.mBuffer = buffer;
+        return event;
+    }
+
     public static GeckoEvent createScreenOrientationEvent(short aScreenOrientation) {
         GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.SCREENORIENTATION_CHANGED);
         event.mScreenOrientation = aScreenOrientation;
         return event;
     }
 
     public static GeckoEvent createCallObserverEvent(String observerKey, String topic, String data) {
         GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.CALL_OBSERVER);
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/ZoomedView.java
@@ -0,0 +1,488 @@
+package org.mozilla.gecko;
+
+import java.text.DecimalFormat;
+
+import java.nio.ByteBuffer;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
+import org.mozilla.gecko.gfx.LayerView;
+import org.mozilla.gecko.util.GeckoEventListener;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.mozglue.DirectBufferAllocator;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.BitmapFactory;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChangedListener,
+        LayerView.OnZoomedViewListener, GeckoEventListener {
+    private static final String LOGTAG = "Gecko" + ZoomedView.class.getSimpleName();
+
+    private static final int ZOOM_FACTOR = 2;
+    private static final int W_CAPTURED_VIEW_IN_PERCENT = 80;
+    private static final int H_CAPTURED_VIEW_IN_PERCENT = 50;
+    private static final int MINIMUM_DELAY_BETWEEN_TWO_RENDER_CALLS_NS = 1000000;
+    private static final int DELAY_BEFORE_NEXT_RENDER_REQUEST_MS = 2000;
+
+    private ImageView zoomedImageView;
+    private LayerView layerView;
+    private MotionEvent actionDownEvent;
+    private int viewWidth;
+    private int viewHeight;
+    private int xLastPosition;
+    private int yLastPosition;
+    private boolean shouldSetVisibleOnUpdate;
+    private PointF returnValue;
+
+    private boolean stopUpdateView;
+
+    private int lastOrientation = 0;
+
+    private ByteBuffer buffer;
+    private Runnable requestRenderRunnable;
+    private long startTimeReRender = 0;
+    private long lastStartTimeReRender = 0;
+
+    private class ZoomedViewTouchListener implements View.OnTouchListener {
+        private float originRawX;
+        private float originRawY;
+        private int touchState;
+
+        @Override
+        public boolean onTouch(View view, MotionEvent event) {
+            if (layerView == null) {
+                return false;
+            }
+
+            switch (event.getAction()) {
+            case MotionEvent.ACTION_MOVE:
+                if (moveZoomedView(event)) {
+                    touchState = MotionEvent.ACTION_MOVE;
+                }
+                break;
+
+            case MotionEvent.ACTION_UP:
+                if (touchState == MotionEvent.ACTION_MOVE) {
+                    touchState = -1;
+                } else {
+                    layerView.dispatchTouchEvent(actionDownEvent);
+                    actionDownEvent.recycle();
+                    PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(event.getX(), event.getY());
+                    MotionEvent e = MotionEvent.obtain(event.getDownTime(), event.getEventTime(),
+                            MotionEvent.ACTION_UP, convertedPosition.x, convertedPosition.y,
+                            event.getMetaState());
+                    layerView.dispatchTouchEvent(e);
+                    e.recycle();
+                }
+                break;
+
+            case MotionEvent.ACTION_DOWN:
+                touchState = -1;
+                originRawX = event.getRawX();
+                originRawY = event.getRawY();
+                PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(event.getX(), event.getY());
+                actionDownEvent = MotionEvent.obtain(event.getDownTime(), event.getEventTime(),
+                        MotionEvent.ACTION_DOWN, convertedPosition.x, convertedPosition.y,
+                        event.getMetaState());
+                break;
+            }
+            return true;
+        }
+
+        private boolean moveZoomedView(MotionEvent event) {
+            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) ZoomedView.this.getLayoutParams();
+            if ((touchState != MotionEvent.ACTION_MOVE) && (Math.abs((int) (event.getRawX() - originRawX)) < 1)
+                    && (Math.abs((int) (event.getRawY() - originRawY)) < 1)) {
+                // When the user just touches the screen ACTION_MOVE can be detected for a very small delta on position.
+                // In this case, the move is ignored if the delta is lower than 1 unit.
+                return false;
+            }
+
+            float newLeftMargin = params.leftMargin + event.getRawX() - originRawX;
+            float newTopMargin = params.topMargin + event.getRawY() - originRawY;
+            ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
+            ZoomedView.this.moveZoomedView(metrics, newLeftMargin, newTopMargin);
+            originRawX = event.getRawX();
+            originRawY = event.getRawY();
+            return true;
+        }
+    }
+
+    public ZoomedView(Context context) {
+        this(context, null, 0);
+    }
+
+    public ZoomedView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ZoomedView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        returnValue = new PointF();
+        requestRenderRunnable = new Runnable() {
+            @Override
+            public void run() {
+                requestZoomedViewRender();
+            }
+        };
+        EventDispatcher.getInstance().registerGeckoThreadListener(this, "Gesture:nothingDoneOnLongPress",
+                "Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange");
+    }
+
+    void destroy() {
+        ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Gesture:nothingDoneOnLongPress",
+                "Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange");
+    }
+
+    // This method (onFinishInflate) is called only when the zoomed view class is used inside
+    // an xml structure <org.mozilla.gecko.ZoomedView ...
+    // It won't be called if the class is used from java code like "new  ZoomedView(context);"
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        ImageView closeButton = (ImageView) findViewById(R.id.dialog_close);
+        closeButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View view) {
+                stopZoomDisplay();
+            }
+        });
+
+        zoomedImageView = (ImageView) findViewById(R.id.zoomed_image_view);
+        zoomedImageView.setOnTouchListener(new ZoomedViewTouchListener());
+    }
+
+    /*
+     * Convert a click from ZoomedView. Return the position of the click in the
+     * LayerView
+     */
+    private PointF getUnzoomedPositionFromPointInZoomedView(float x, float y) {
+        ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
+        PointF offset = metrics.getMarginOffset();
+        final float parentWidth = metrics.getWidth();
+        final float parentHeight = metrics.getHeight();
+        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
+
+        returnValue.x = (int) ((x / ZOOM_FACTOR) +     // Conversion of the x offset inside the zoomed view (using the scale factor)
+
+                        offset.x +               // The offset of the layerView
+
+                        /* Conversion of the left side position of the zoomed view
+                         *   Minimum value for the left side of the zoomed view is 0
+                         *     and we return 0 after conversion
+                         *   Maximum value for the left side of the zoomed view is (parentWidth - offset.x - viewWidth)
+                         *     and we return (parentWidth - offset.x - (viewWidth / ZOOM_FACTOR)) after conversion.
+                         */
+                        (((float) params.leftMargin) - offset.x) *
+                            ((parentWidth - offset.x - (viewWidth / ZOOM_FACTOR)) /
+                            (parentWidth - offset.x - viewWidth)));
+
+        // Same comments here vertically
+        returnValue.y = (int) ((y / ZOOM_FACTOR) +
+                        offset.y +
+                        (((float) params.topMargin) - offset.y) *
+                            ((parentHeight - offset.y - (viewHeight / ZOOM_FACTOR)) /
+                            (parentHeight - offset.y - viewHeight)));
+
+        return returnValue;
+    }
+
+    /*
+     * A touch point (x,y) occurs in LayerView, this point should be displayed
+     * in the center of the zoomed view. The returned point is the position of
+     * the Top-Left zoomed view point on the screen device
+     */
+    private PointF getZoomedViewTopLeftPositionFromTouchPosition(float x, float y) {
+        ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
+        PointF offset = metrics.getMarginOffset();
+        final float parentWidth = metrics.getWidth();
+        final float parentHeight = metrics.getHeight();
+
+        returnValue.x = (int) ((((x - (viewWidth / (2 * ZOOM_FACTOR)))) /   // Translation to get the left side position of the zoomed view
+                                                                        // centered on x (the value 2 to get the middle).
+
+                        /* Conversion of the left side position of the zoomed view.
+                         * See the comment in getUnzoomedPositionFromPointInZoomedView.
+                         * The proportional factor is the same. It is used in a division
+                         * and not in a multiplication to convert the position from
+                         * the LayerView to the ZoomedView.
+                         */
+                        ((parentWidth - offset.x - (viewWidth / ZOOM_FACTOR)) /
+                        (parentWidth - offset.x - viewWidth)))
+
+                + offset.x);     // The offset of the layerView
+
+        // Same comments here vertically
+        returnValue.y = (int) ((((y - (viewHeight / (2 * ZOOM_FACTOR)))) /
+                        ((parentHeight - offset.y - (viewHeight / ZOOM_FACTOR)) /
+                        (parentHeight - offset.y - viewHeight)))
+                + offset.y);
+
+        return returnValue;
+    }
+
+    private void moveZoomedView(ImmutableViewportMetrics metrics, float newLeftMargin, float newTopMargin) {
+        if (layerView == null) {
+            return;
+        }
+
+        final float parentWidth = metrics.getWidth();
+        final float parentHeight = metrics.getHeight();
+        RelativeLayout.LayoutParams newLayoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
+        newLayoutParams.leftMargin = (int) newLeftMargin;
+        newLayoutParams.topMargin = (int) newTopMargin;
+        int topMarginMin;
+        int leftMarginMin;
+        PointF offset = metrics.getMarginOffset();
+        topMarginMin = (int) offset.y;
+        leftMarginMin = (int) offset.x;
+
+        if (newTopMargin < topMarginMin) {
+            newLayoutParams.topMargin = topMarginMin;
+        } else if (newTopMargin + viewHeight >= parentHeight) {
+            newLayoutParams.topMargin = (int) (parentHeight - viewHeight);
+        }
+
+        if (newLeftMargin < leftMarginMin) {
+            newLayoutParams.leftMargin = leftMarginMin;
+        } else if (newLeftMargin + viewWidth > parentWidth) {
+            newLayoutParams.leftMargin = (int) (parentWidth - viewWidth);
+        }
+
+        setLayoutParams(newLayoutParams);
+        PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(0, 0);
+        xLastPosition = Math.round(convertedPosition.x);
+        yLastPosition = Math.round(convertedPosition.y);
+        requestZoomedViewRender();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        // In case of orientation change, the zoomed view update is stopped until the orientation change
+        // is completed. At this time, the function onMetricsChanged is called and the
+        // zoomed view update is restarted again.
+        if (lastOrientation != newConfig.orientation) {
+            shouldBlockUpdate(true);
+            lastOrientation = newConfig.orientation;
+        }
+    }
+
+    public void refreshZoomedViewSize(ImmutableViewportMetrics viewport) {
+        if (layerView == null) {
+            return;
+        }
+
+        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
+        setCapturedSize(viewport);
+        moveZoomedView(viewport, params.leftMargin, params.topMargin);
+    }
+
+    public void setCapturedSize(ImmutableViewportMetrics metrics) {
+        if (layerView == null) {
+            return;
+        }
+        float parentMinSize = Math.min(metrics.getWidth(), metrics.getHeight());
+        viewWidth = (int) (parentMinSize * W_CAPTURED_VIEW_IN_PERCENT / (ZOOM_FACTOR * 100.0)) * ZOOM_FACTOR;
+        viewHeight = (int) (parentMinSize * H_CAPTURED_VIEW_IN_PERCENT / (ZOOM_FACTOR * 100.0)) * ZOOM_FACTOR;
+    }
+
+    public void shouldBlockUpdate(boolean shouldBlockUpdate) {
+        stopUpdateView = shouldBlockUpdate;
+    }
+
+    public Bitmap.Config getBitmapConfig() {
+        return (GeckoAppShell.getScreenDepth() == 24) ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
+    }
+
+    public void startZoomDisplay(LayerView aLayerView, final int leftFromGecko, final int topFromGecko) {
+        if (layerView == null) {
+            layerView = aLayerView;
+            layerView.addOnZoomedViewListener(this);
+            layerView.setOnMetricsChangedZoomedViewportListener(this);
+            ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
+            setCapturedSize(metrics);
+        }
+        startTimeReRender = 0;
+        shouldSetVisibleOnUpdate = true;
+        moveUsingGeckoPosition(leftFromGecko, topFromGecko);
+    }
+
+    public void stopZoomDisplay() {
+        shouldSetVisibleOnUpdate = false;
+        this.setVisibility(View.GONE);
+        ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
+        if (layerView != null) {
+            layerView.setOnMetricsChangedZoomedViewportListener(null);
+            layerView.removeOnZoomedViewListener(this);
+            layerView = null;
+        }
+    }
+
+    @Override
+    public void handleMessage(final String event, final JSONObject message) {
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    if (event.equals("Gesture:nothingDoneOnLongPress") || event.equals("Gesture:clusteredLinksClicked")) {
+                        final JSONObject clickPosition = message.getJSONObject("clickPosition");
+                        int left = clickPosition.getInt("x");
+                        int top = clickPosition.getInt("y");
+                        // Start to display inside the zoomedView
+                        LayerView geckoAppLayerView = GeckoAppShell.getLayerView();
+                        if (geckoAppLayerView != null) {
+                            startZoomDisplay(geckoAppLayerView, left, top);
+                        }
+                    } else if (event.equals("Window:Resize")) {
+                        ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
+                        refreshZoomedViewSize(metrics);
+                    } else if (event.equals("Content:LocationChange")) {
+                        stopZoomDisplay();
+                    }
+                } catch (JSONException e) {
+                    Log.e(LOGTAG, "JSON exception", e);
+                }
+            }
+        });
+    }
+
+    private void moveUsingGeckoPosition(int leftFromGecko, int topFromGecko) {
+        if (layerView == null) {
+            return;
+        }
+        ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
+        PointF convertedPosition = getZoomedViewTopLeftPositionFromTouchPosition((leftFromGecko * metrics.zoomFactor),
+                (topFromGecko * metrics.zoomFactor));
+        moveZoomedView(metrics, convertedPosition.x, convertedPosition.y);
+    }
+
+    @Override
+    public void onMetricsChanged(final ImmutableViewportMetrics viewport) {
+        // It can be called from a Gecko thread (forceViewportMetrics in GeckoLayerClient).
+        // Post to UI Thread to avoid Exception:
+        //    "Only the original thread that created a view hierarchy can touch its views."
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (layerView == null) {
+                    return;
+                }
+                shouldBlockUpdate(false);
+                refreshZoomedViewSize(viewport);
+            }
+        });
+    }
+
+    @Override
+    public void onPanZoomStopped() {
+    }
+
+    @Override
+    public void updateView(ByteBuffer data) {
+        final Bitmap sb3 = Bitmap.createBitmap(viewWidth, viewHeight, getBitmapConfig());
+        if (sb3 != null) {
+            data.rewind();
+            try {
+                sb3.copyPixelsFromBuffer(data);
+            } catch (Exception iae) {
+                Log.w(LOGTAG, iae.toString());
+            }
+            BitmapDrawable ob3 = new BitmapDrawable(getResources(), sb3);
+            if (zoomedImageView != null) {
+                zoomedImageView.setImageDrawable(ob3);
+            }
+        }
+        if (shouldSetVisibleOnUpdate) {
+            this.setVisibility(View.VISIBLE);
+            shouldSetVisibleOnUpdate = false;
+        }
+        lastStartTimeReRender = startTimeReRender;
+        startTimeReRender = 0;
+    }
+
+    private void updateBufferSize() {
+        int pixelSize = (GeckoAppShell.getScreenDepth() == 24) ? 4 : 2;
+        int capacity = viewWidth * viewHeight * pixelSize;
+        if (buffer == null || buffer.capacity() != capacity) {
+            buffer = DirectBufferAllocator.free(buffer);
+            buffer = DirectBufferAllocator.allocate(capacity);
+        }
+    }
+
+    private boolean isRendering() {
+        return (startTimeReRender != 0);
+    }
+
+    private boolean renderFrequencyTooHigh() {
+        return ((System.nanoTime() - lastStartTimeReRender) < MINIMUM_DELAY_BETWEEN_TWO_RENDER_CALLS_NS);
+    }
+
+    @Override
+    public void requestZoomedViewRender() {
+        if (stopUpdateView) {
+            return;
+        }
+        // remove pending runnable
+        ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
+
+        // "requestZoomedViewRender" can be called very often by Gecko (endDrawing in LayerRender) without
+        // any thing changed in the zoomed area (useless calls from the "zoomed area" point of view).
+        // "requestZoomedViewRender" can take time to re-render the zoomed view, it depends of the complexity
+        // of the html on this area.
+        // To avoid to slow down the application, the 2 following cases are tested:
+
+        // 1- Last render is still running, plan another render later.
+        if (isRendering()) {
+            // post a new runnable DELAY_BEFORE_NEXT_RENDER_REQUEST_MS later
+            // We need to post with a delay to be sure that the last call to requestZoomedViewRender will be done.
+            // For a static html page WITHOUT any animation/video, there is a last call to endDrawing and we need to make
+            // the zoomed render on this last call.
+            ThreadUtils.postDelayedToUiThread(requestRenderRunnable, DELAY_BEFORE_NEXT_RENDER_REQUEST_MS);
+            return;
+        }
+
+        // 2- Current render occurs too early, plan another render later.
+        if (renderFrequencyTooHigh()) {
+            // post a new runnable DELAY_BEFORE_NEXT_RENDER_REQUEST_MS later
+            // We need to post with a delay to be sure that the last call to requestZoomedViewRender will be done.
+            // For a page WITH animation/video, the animation/video can be stopped, and we need to make
+            // the zoomed render on this last call.
+            ThreadUtils.postDelayedToUiThread(requestRenderRunnable, DELAY_BEFORE_NEXT_RENDER_REQUEST_MS);
+            return;
+        }
+
+        startTimeReRender = System.nanoTime();
+        // Allocate the buffer if it's the first call.
+        // Change the buffer size if it's not the right size.
+        updateBufferSize();
+
+        int tabId = Tabs.getInstance().getSelectedTab().getId();
+
+        ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
+        PointF origin = metrics.getOrigin();
+        PointF offset = metrics.getMarginOffset();
+
+        final int xPos = (int) (origin.x - offset.x) + xLastPosition;
+        final int yPos = (int) (origin.y - offset.y) + yLastPosition;
+
+        GeckoEvent e = GeckoEvent.createZoomedViewEvent(tabId, xPos, yPos, viewWidth,
+                viewHeight, (float) (2.0 * metrics.zoomFactor), buffer);
+        GeckoAppShell.sendEventToGecko(e);
+    }
+
+}
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -81,17 +81,18 @@ class GeckoLayerClient implements LayerV
      * Specifically:
      * 1) reading mViewportMetrics from any thread is fine without synchronization
      * 2) writing to mViewportMetrics requires synchronizing on the layer controller object
      * 3) whenever reading multiple fields from mViewportMetrics without synchronization (i.e. in
      *    case 1 above) you should always first grab a local copy of the reference, and then use
      *    that because mViewportMetrics might get reassigned in between reading the different
      *    fields. */
     private volatile ImmutableViewportMetrics mViewportMetrics;
-    private LayerView.OnMetricsChangedListener mViewportChangeListener;
+    private LayerView.OnMetricsChangedListener mDynamicToolbarViewportChangeListener;
+    private LayerView.OnMetricsChangedListener mZoomedViewViewportChangeListener;
 
     private ZoomConstraints mZoomConstraints;
 
     private boolean mGeckoIsReady;
 
     private final PanZoomController mPanZoomController;
     private final LayerMarginsAnimator mMarginsAnimator;
     private final LayerView mView;
@@ -848,18 +849,21 @@ class GeckoLayerClient implements LayerV
 
         viewportMetricsChanged(notifyGecko);
     }
 
     /*
      * You must hold the monitor while calling this.
      */
     private void viewportMetricsChanged(boolean notifyGecko) {
-        if (mViewportChangeListener != null) {
-            mViewportChangeListener.onMetricsChanged(mViewportMetrics);
+        if (mDynamicToolbarViewportChangeListener != null) {
+            mDynamicToolbarViewportChangeListener.onMetricsChanged(mViewportMetrics);
+        }
+        if (mZoomedViewViewportChangeListener != null) {
+            mZoomedViewViewportChangeListener.onMetricsChanged(mViewportMetrics);
         }
 
         mView.requestRender();
         if (notifyGecko && mGeckoIsReady) {
             geometryChanged(null);
         }
     }
 
@@ -905,18 +909,21 @@ class GeckoLayerClient implements LayerV
             mMarginsAnimator.scrollBy(mViewportMetrics, dx, dy);
         mViewportMetrics = mViewportMetrics.setMarginsFrom(newMarginsMetrics);
         viewportMetricsChanged(true);
     }
 
     /** Implementation of PanZoomTarget */
     @Override
     public void panZoomStopped() {
-        if (mViewportChangeListener != null) {
-            mViewportChangeListener.onPanZoomStopped();
+        if (mDynamicToolbarViewportChangeListener != null) {
+        	mDynamicToolbarViewportChangeListener.onPanZoomStopped();
+        }
+        if (mZoomedViewViewportChangeListener != null) {
+        	mZoomedViewViewportChangeListener.onPanZoomStopped();
         }
     }
 
     /** Implementation of PanZoomTarget */
     @Override
     public void forceRedraw(DisplayPortMetrics displayPort) {
         mForceRedraw = true;
         if (mGeckoIsReady) {
@@ -977,18 +984,22 @@ class GeckoLayerClient implements LayerV
         // the current Gecko coordinate in CSS pixels.
         PointF layerPoint = new PointF(
                 ((viewPoint.x + origin.x) / zoom) - (geckoOrigin.x / geckoZoom),
                 ((viewPoint.y + origin.y) / zoom) - (geckoOrigin.y / geckoZoom));
 
         return layerPoint;
     }
 
-    void setOnMetricsChangedListener(LayerView.OnMetricsChangedListener listener) {
-        mViewportChangeListener = listener;
+    void setOnMetricsChangedDynamicToolbarViewportListener(LayerView.OnMetricsChangedListener listener) {
+        mDynamicToolbarViewportChangeListener = listener;
+    }
+
+    void setOnMetricsChangedZoomedViewportListener(LayerView.OnMetricsChangedListener listener) {
+    	mZoomedViewViewportChangeListener = listener;
     }
 
     public void addDrawListener(DrawListener listener) {
         mDrawListeners.add(listener);
     }
 
     public void removeDrawListener(DrawListener listener) {
         mDrawListeners.remove(listener);
--- a/mobile/android/base/gfx/LayerRenderer.java
+++ b/mobile/android/base/gfx/LayerRenderer.java
@@ -21,23 +21,27 @@ import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.opengl.GLES20;
 import android.os.SystemClock;
 import android.util.Log;
+
 import org.mozilla.gecko.mozglue.JNITarget;
+import org.mozilla.gecko.util.ThreadUtils;
 
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
 import java.nio.IntBuffer;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.ArrayList;
+import java.util.List;
 
 import javax.microedition.khronos.egl.EGLConfig;
 
 /**
  * The layer renderer implements the rendering logic for a layer view.
  */
 public class LayerRenderer implements Tabs.OnTabsChangedListener {
     private static final String LOGTAG = "GeckoLayerRenderer";
@@ -50,16 +54,18 @@ public class LayerRenderer implements Ta
     private static final int MAX_FRAME_TIME = 16;   /* 1000 ms / 60 FPS */
 
     private static final int FRAME_RATE_METER_WIDTH = 128;
     private static final int FRAME_RATE_METER_HEIGHT = 32;
 
     private static final long NANOS_PER_MS = 1000000;
     private static final int NANOS_PER_SECOND = 1000000000;
 
+    private static final int MAX_SCROLL_SPEED_TO_REQUEST_ZOOM_RENDER = 5;
+
     private final LayerView mView;
     private final ScrollbarLayer mHorizScrollLayer;
     private final ScrollbarLayer mVertScrollLayer;
     private final FadeRunnable mFadeRunnable;
     private ByteBuffer mCoordByteBuffer;
     private FloatBuffer mCoordBuffer;
     private RenderContext mLastPageContext;
     private int mMaxTextureSize;
@@ -85,16 +91,20 @@ public class LayerRenderer implements Ta
 
     // Used by GLES 2.0
     private int mProgram;
     private int mPositionHandle;
     private int mTextureHandle;
     private int mSampleHandle;
     private int mTMatrixHandle;
 
+    private List<LayerView.OnZoomedViewListener> mZoomedViewListeners;
+    private float mViewLeft = 0.0f;
+    private float mViewTop = 0.0f;
+
     // column-major matrix applied to each vertex to shift the viewport from
     // one ranging from (-1, -1),(1,1) to (0,0),(1,1) and to scale all sizes by
     // a factor of 2 to fill up the screen
     public static final float[] DEFAULT_TEXTURE_MATRIX = {
         2.0f, 0.0f, 0.0f, 0.0f,
         0.0f, 2.0f, 0.0f, 0.0f,
         0.0f, 0.0f, 2.0f, 0.0f,
         -1.0f, -1.0f, 0.0f, 1.0f
@@ -153,16 +163,17 @@ public class LayerRenderer implements Ta
 
         // Initialize the FloatBuffer that will be used to store all vertices and texture
         // coordinates in draw() commands.
         mCoordByteBuffer = DirectBufferAllocator.allocate(COORD_BUFFER_SIZE * 4);
         mCoordByteBuffer.order(ByteOrder.nativeOrder());
         mCoordBuffer = mCoordByteBuffer.asFloatBuffer();
 
         Tabs.registerOnTabsChangedListener(this);
+        mZoomedViewListeners = new ArrayList<LayerView.OnZoomedViewListener>();
     }
 
     private Bitmap expandCanvasToPowerOfTwo(Bitmap image, IntSize size) {
         IntSize potSize = size.nextPowerOfTwo();
         if (size.equals(potSize)) {
             return image;
         }
         // make the bitmap size a power-of-two in both dimensions if it's not already.
@@ -180,16 +191,17 @@ public class LayerRenderer implements Ta
 
     public void destroy() {
         DirectBufferAllocator.free(mCoordByteBuffer);
         mCoordByteBuffer = null;
         mCoordBuffer = null;
         mHorizScrollLayer.destroy();
         mVertScrollLayer.destroy();
         Tabs.unregisterOnTabsChangedListener(this);
+        mZoomedViewListeners.clear();
     }
 
     void onSurfaceCreated(EGLConfig config) {
         checkMonitoringEnabled();
         createDefaultProgram();
         activateDefaultProgram();
     }
 
@@ -581,25 +593,63 @@ public class LayerRenderer implements Ta
                     printCheckerboardStats();
                 }
             }
 
             runRenderTasks(mTasks, true, mFrameStartTime);
 
         }
 
+        public void maybeRequestZoomedViewRender(RenderContext context){
+            // Concurrently update of mZoomedViewListeners should not be an issue here
+            // because the following line is just a short-circuit
+            if (mZoomedViewListeners.size() == 0) {
+                return;
+            }
+
+            // When scrolling fast, do not request zoomed view render to avoid to slow down
+            // the scroll in the main view.
+            // Speed is estimated using the offset changes between 2 display frame calls
+            final float viewLeft = context.viewport.left - context.offset.x;
+            final float viewTop = context.viewport.top - context.offset.y;
+            boolean shouldWaitToRender = false;
+
+            if (Math.abs(mViewLeft - viewLeft) > MAX_SCROLL_SPEED_TO_REQUEST_ZOOM_RENDER ||
+                Math.abs(mViewTop - viewTop) > MAX_SCROLL_SPEED_TO_REQUEST_ZOOM_RENDER) {
+                shouldWaitToRender = true;
+            }
+
+            mViewLeft = viewLeft;
+            mViewTop = viewTop;
+
+            if (shouldWaitToRender) {
+                return;
+            }
+
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    for (LayerView.OnZoomedViewListener listener : mZoomedViewListeners) {
+                        listener.requestZoomedViewRender();
+                    }
+                }
+            });
+        }
+
         /** This function is invoked via JNI; be careful when modifying signature. */
         @JNITarget
         public void endDrawing() {
             // If a layer update requires further work, schedule another redraw
             if (!mUpdated)
                 mView.requestRender();
 
             PanningPerfAPI.recordFrameTime();
 
+            maybeRequestZoomedViewRender(mPageContext);
+
             /* Used by robocop for testing purposes */
             IntBuffer pixelBuffer = mPixelBuffer;
             if (mUpdated && pixelBuffer != null) {
                 synchronized (pixelBuffer) {
                     pixelBuffer.position(0);
                     GLES20.glReadPixels(0, 0, (int)mScreenContext.viewport.width(),
                                         (int)mScreenContext.viewport.height(), GLES20.GL_RGBA,
                                         GLES20.GL_UNSIGNED_BYTE, pixelBuffer);
@@ -637,9 +687,30 @@ public class LayerRenderer implements Ta
 
                 if (mView.getChildAt(0) != null) {
                     mView.getChildAt(0).setBackgroundColor(tab.getBackgroundColor());
                 }
                 mView.setPaintState(LayerView.PAINT_START);
             }
         }
     }
+
+    public void updateZoomedView(final ByteBuffer data) {
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                for (LayerView.OnZoomedViewListener listener : mZoomedViewListeners) {
+                    listener.updateView(data);
+                }
+            }
+        });
+    }
+
+    public void addOnZoomedViewListener(LayerView.OnZoomedViewListener listener) {
+        ThreadUtils.assertOnUiThread();
+        mZoomedViewListeners.add(listener);
+    }
+
+    public void removeOnZoomedViewListener(LayerView.OnZoomedViewListener listener) {
+        ThreadUtils.assertOnUiThread();
+        mZoomedViewListeners.remove(listener);
+    }
 }
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -1,16 +1,18 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.gfx;
 
+import java.nio.ByteBuffer;
 import java.nio.IntBuffer;
+import java.util.ArrayList;
 
 import org.mozilla.gecko.AndroidGamepadManager;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAccessibility;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.PrefsHelper;
@@ -525,16 +527,29 @@ public class LayerView extends FrameLayo
             controller.compositorCreated();
             return controller;
         } catch (Exception e) {
             Log.e(LOGTAG, "Error registering compositor!", e);
             return null;
         }
     }
 
+    @WrapElementForJNI(allowMultithread = true, stubName = "updateZoomedView")
+    public static void updateZoomedView(ByteBuffer data) {
+        data.position(0);
+        LayerView layerView = GeckoAppShell.getLayerView();
+        if (layerView != null) {
+            LayerRenderer layerRenderer = layerView.getRenderer();
+            if (layerRenderer != null){
+                layerRenderer.updateZoomedView(data);
+            }
+        }
+        return;
+    }
+
     public interface Listener {
         void renderRequested();
         void sizeChanged(int width, int height);
         void surfaceChanged(int width, int height);
     }
 
     private class SurfaceListener implements SurfaceHolder.Callback {
         @Override
@@ -657,12 +672,32 @@ public class LayerView extends FrameLayo
 
     // Public hooks for listening to metrics changing
 
     public interface OnMetricsChangedListener {
         public void onMetricsChanged(ImmutableViewportMetrics viewport);
         public void onPanZoomStopped();
     }
 
-    public void setOnMetricsChangedListener(OnMetricsChangedListener listener) {
-        mLayerClient.setOnMetricsChangedListener(listener);
+    public void setOnMetricsChangedDynamicToolbarViewportListener(OnMetricsChangedListener listener) {
+        mLayerClient.setOnMetricsChangedDynamicToolbarViewportListener(listener);
+    }
+
+    public void setOnMetricsChangedZoomedViewportListener(OnMetricsChangedListener listener) {
+        mLayerClient.setOnMetricsChangedZoomedViewportListener(listener);
     }
+
+    // Public hooks for zoomed view
+
+    public interface OnZoomedViewListener {
+        public void requestZoomedViewRender();
+        public void updateView(ByteBuffer data);
+    }
+
+    public void addOnZoomedViewListener(OnZoomedViewListener listener) {
+        mRenderer.addOnZoomedViewListener(listener);
+    }
+
+    public void removeOnZoomedViewListener(OnZoomedViewListener listener) {
+        mRenderer.removeOnZoomedViewListener(listener);
+    }
+
 }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -495,16 +495,17 @@ gbjar.sources += [
     'widget/IconTabWidget.java',
     'widget/ResizablePathDrawable.java',
     'widget/SquaredImageView.java',
     'widget/SwipeDismissListViewTouchListener.java',
     'widget/TabThumbnailWrapper.java',
     'widget/ThumbnailView.java',
     'widget/TwoWayView.java',
     'ZoomConstraints.java',
+    'ZoomedView.java',
 ]
 # The following sources are checked in to version control but
 # generated by a script (widget/generate_themed_views.py).  If you're
 # editing this list, make sure to edit that script.
 gbjar.sources += [
     'widget/ThemedEditText.java',
     'widget/ThemedImageButton.java',
     'widget/ThemedImageView.java',
--- a/mobile/android/base/resources/layout/shared_ui_components.xml
+++ b/mobile/android/base/resources/layout/shared_ui_components.xml
@@ -21,16 +21,22 @@
 
     <org.mozilla.gecko.FormAssistPopup android:id="@+id/form_assist_popup"
                                        android:layout_width="match_parent"
                                        android:layout_height="match_parent"
                                        android:visibility="gone"/>
 
     <include layout="@layout/text_selection_handles"/>
 
+    <ViewStub android:id="@+id/zoomed_view_stub"
+               android:inflatedId="@+id/zoomed_view"
+               android:layout="@layout/zoomed_view"
+               android:layout_width="wrap_content"
+               android:layout_height="wrap_content" />
+
     <FrameLayout android:id="@+id/camera_layout"
                  android:layout_height="wrap_content"
                  android:layout_width="wrap_content"
                  android:layout_alignParentRight="true"
                  android:layout_alignParentBottom="true">
     </FrameLayout>
 
 </merge>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/zoomed_view.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/.
+-->
+
+<org.mozilla.gecko.ZoomedView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:gecko="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/zoomed_view_container"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_alignParentLeft="true"
+    android:layout_alignParentTop="true"
+    android:background="@android:color/white"
+    android:visibility="gone" >
+
+
+    <ImageView
+        android:id="@+id/zoomed_image_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="#000000"
+        android:padding="1dip" />
+
+    <ImageView
+        android:id="@+id/dialog_close"
+        android:background="@drawable/close"
+        android:layout_height="20dp"
+        android:layout_width="20dp"
+        android:layout_gravity ="top|right" />
+
+</org.mozilla.gecko.ZoomedView>
\ No newline at end of file
--- a/mobile/android/base/util/ThreadUtils.java
+++ b/mobile/android/base/util/ThreadUtils.java
@@ -91,16 +91,24 @@ public final class ThreadUtils {
     public static Handler getUiHandler() {
         return sUiHandler;
     }
 
     public static void postToUiThread(Runnable runnable) {
         sUiHandler.post(runnable);
     }
 
+    public static void postDelayedToUiThread(Runnable runnable, long timeout) {
+        sUiHandler.postDelayed(runnable, timeout);
+    }
+
+    public static void removeCallbacksFromUiThread(Runnable runnable) {
+        sUiHandler.removeCallbacks(runnable);
+    }
+
     public static Thread getBackgroundThread() {
         return sBackgroundThread;
     }
 
     public static Handler getBackgroundHandler() {
         return GeckoBackgroundThread.getHandler();
     }
 
--- a/mobile/android/base/widget/FadedMultiColorTextView.java
+++ b/mobile/android/base/widget/FadedMultiColorTextView.java
@@ -52,18 +52,18 @@ public class FadedMultiColorTextView ext
             final int right = getWidth() - getCompoundPaddingRight();
             final float left = right - fadeWidth;
 
             updateGradientShader(needsEllipsis, right);
 
             final float center = getHeight() / 2;
 
             // Shrink height of gradient to prevent it overlaying parent view border.
-            final float top = center - getTextSize() + 1;
-            final float bottom = center + getTextSize() - 1;
+            final float top = center - getTextSize() + 2;
+            final float bottom = center + getTextSize() - 2;
 
             canvas.drawRect(left, top, right, bottom, fadePaint);
         }
     }
 
     private void updateGradientShader(final boolean needsEllipsis, final int gradientEndRight) {
         final int backgroundColor =
                 fadeBackgroundColorList.getColorForState(getDrawableState(), Color.RED);
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -4976,16 +4976,21 @@ var BrowserEventHandler = {
       return;
     }
 
     let target = aEvent.target;
     if (!target) {
       return;
     }
 
+    this._inCluster = aEvent.hitCluster;
+    if (this._inCluster) {
+      return;  // No highlight for a cluster of links
+    }
+
     let uri = this._getLinkURI(target);
     if (uri) {
       try {
         Services.io.QueryInterface(Ci.nsISpeculativeConnect).speculativeConnect(uri, null);
       } catch (e) {}
     }
     this._doTapHighlight(target);
   },
@@ -5090,26 +5095,29 @@ var BrowserEventHandler = {
             if (result !== SelectionHandler.ERROR_NONE) {
               dump("Unexpected failure during caret attach: " + result);
             }
           }
         } catch(e) {
           Cu.reportError(e);
         }
 
-        // The _highlightElement was chosen after fluffing the touch events
-        // that led to this SingleTap, so by fluffing the mouse events, they
-        // should find the same target since we fluff them again below.
         let data = JSON.parse(aData);
         let {x, y} = data;
 
-        this._sendMouseEvent("mousemove", x, y);
-        this._sendMouseEvent("mousedown", x, y);
-        this._sendMouseEvent("mouseup",   x, y);
-
+        if (this._inCluster) {
+          this._clusterClicked(x, y);
+        } else {
+          // The _highlightElement was chosen after fluffing the touch events
+          // that led to this SingleTap, so by fluffing the mouse events, they
+          // should find the same target since we fluff them again below.
+          this._sendMouseEvent("mousemove", x, y);
+          this._sendMouseEvent("mousedown", x, y);
+          this._sendMouseEvent("mouseup",   x, y);
+        }
         // scrollToFocusedInput does its own checks to find out if an element should be zoomed into
         BrowserApp.scrollToFocusedInput(BrowserApp.selectedBrowser);
 
         this._cancelTapHighlight();
         break;
       }
 
       case"Gesture:DoubleTap":
@@ -5122,16 +5130,26 @@ var BrowserEventHandler = {
         break;
 
       default:
         dump('BrowserEventHandler.handleUserEvent: unexpected topic "' + aTopic + '"');
         break;
     }
   },
 
+  _clusterClicked: function(aX, aY) {
+    Messaging.sendRequest({
+      type: "Gesture:clusteredLinksClicked",
+      clickPosition: {
+        x: aX,
+        y: aY
+      }
+    });
+  },
+
   onDoubleTap: function(aData) {
     let metadata = BrowserApp.selectedTab.metadata;
     if (!metadata.allowDoubleTapZoom) {
       return;
     }
 
     let data = JSON.parse(aData);
     let element = ElementTouchHelper.anyElementFromPoint(data.x, data.y);
--- a/mobile/android/components/HelperAppDialog.js
+++ b/mobile/android/components/HelperAppDialog.js
@@ -231,21 +231,16 @@ HelperAppLauncherDialog.prototype = {
       return;
 
     if (app)
       Services.prefs.setCharPref(this._getPrefName(mime), app.packageName);
     else
       Services.prefs.clearUserPref(this._getPrefName(mime));
   },
 
-  promptForSaveToFile: function () {
-    throw new Components.Exception("Async version must be used",
-                                   Cr.NS_ERROR_NOT_AVAILABLE);
-  },
-
   promptForSaveToFileAsync: function (aLauncher, aContext, aDefaultFile,
                                       aSuggestedFileExt, aForcePrompt) {
     Task.spawn(function* () {
       let file = null;
       try {
         let preferredDir = yield Downloads.getPreferredDownloadsDirectory();
         file = this.validateLeafName(new FileUtils.File(preferredDir),
                                      aDefaultFile, aSuggestedFileExt);
--- a/mobile/android/themes/core/aboutReader.css
+++ b/mobile/android/themes/core/aboutReader.css
@@ -504,24 +504,16 @@ body {
   background-image: url('chrome://browser/skin/images/reader-style-icon-mdpi.png');
 }
 
 @media screen and (min-resolution: 1.25dppx) {
   .dropdown-arrow {
     background-image: url('chrome://browser/skin/images/reader-dropdown-arrow-hdpi.png');
   }
 
-  .step-control > .plus-button {
-    background-image: url('chrome://browser/skin/images/reader-plus-icon-hdpi.png');
-  }
-
-  .step-control > .minus-button {
-    background-image: url('chrome://browser/skin/images/reader-minus-icon-hdpi.png');
-  }
-
   .toggle-button.on {
     background-image: url('chrome://browser/skin/images/reader-toggle-on-icon-hdpi.png');
   }
 
   .toggle-button {
     background-image: url('chrome://browser/skin/images/reader-toggle-off-icon-hdpi.png');
   }
 
@@ -534,24 +526,16 @@ body {
   }
 }
 
 @media screen and (min-resolution: 2dppx) {
   .dropdown-arrow {
     background-image: url('chrome://browser/skin/images/reader-dropdown-arrow-xhdpi.png');
   }
 
-  .step-control > .plus-button {
-    background-image: url('chrome://browser/skin/images/reader-plus-icon-xhdpi.png');
-  }
-
-  .step-control > .minus-button {
-    background-image: url('chrome://browser/skin/images/reader-minus-icon-xhdpi.png');
-  }
-
   .toggle-button.on {
     background-image: url('chrome://browser/skin/images/reader-toggle-on-icon-xhdpi.png');
   }
 
   .toggle-button {
     background-image: url('chrome://browser/skin/images/reader-toggle-off-icon-xhdpi.png');
   }
 
--- a/mobile/android/themes/core/config.css
+++ b/mobile/android/themes/core/config.css
@@ -69,17 +69,17 @@ body {
     height: 100%;
     min-width: 3em;
     box-sizing: border-box;
     opacity: 0.75;
 }
 
 #new-pref-toggle-button {
     background-position: center center;
-    background-image: url("chrome://browser/skin/images/reader-plus-icon-xhdpi.png");
+    background-image: url("chrome://browser/skin/images/config-plus.png");
     background-size: 48px 48px;
     height: 48px;
     width: 48px;
     display: inline-block;
     outline-style: none;
 }
 
 #filter-search-button {
rename from mobile/android/themes/core/images/reader-plus-icon-xhdpi.png
rename to mobile/android/themes/core/images/config-plus.png
deleted file mode 100644
index 30f099e0a769f8198840770b48c754dddcb18cba..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 04fa20d59bf54399776d97ba63905eef4191a5db..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index a6e12b7c2e2f9130f32b6f6fc7416f7eaf73e1d1..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index d3cd2e32ed6b6b6dc472d5795dac649b4f58ea88..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 193dab3fcb5179e54ee033470fddcf97f00ecb11..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/mobile/android/themes/core/jar.mn
+++ b/mobile/android/themes/core/jar.mn
@@ -80,22 +80,17 @@ chrome.jar:
   skin/images/cast-ready-hdpi.png           (images/cast-ready-hdpi.png)
   skin/images/cast-active-hdpi.png          (images/cast-active-hdpi.png)
   skin/images/mute-hdpi.png                 (images/mute-hdpi.png)
   skin/images/unmute-hdpi.png               (images/unmute-hdpi.png)
   skin/images/scrubber-hdpi.png             (images/scrubber-hdpi.png)
   skin/images/about-btn-darkgrey.png        (images/about-btn-darkgrey.png)
   skin/images/logo-hdpi.png                 (images/logo-hdpi.png)
   skin/images/wordmark-hdpi.png             (images/wordmark-hdpi.png)
-  skin/images/reader-plus-icon-mdpi.png     (images/reader-plus-icon-mdpi.png)
-  skin/images/reader-plus-icon-hdpi.png     (images/reader-plus-icon-hdpi.png)
-  skin/images/reader-plus-icon-xhdpi.png    (images/reader-plus-icon-xhdpi.png)
-  skin/images/reader-minus-icon-mdpi.png    (images/reader-minus-icon-mdpi.png)
-  skin/images/reader-minus-icon-hdpi.png    (images/reader-minus-icon-hdpi.png)
-  skin/images/reader-minus-icon-xhdpi.png   (images/reader-minus-icon-xhdpi.png)
+  skin/images/config-plus.png               (images/config-plus.png)
   skin/images/reader-dropdown-arrow-mdpi.png     (images/reader-dropdown-arrow-mdpi.png)
   skin/images/reader-dropdown-arrow-hdpi.png     (images/reader-dropdown-arrow-hdpi.png)
   skin/images/reader-dropdown-arrow-xhdpi.png    (images/reader-dropdown-arrow-xhdpi.png)
   skin/images/reader-toggle-on-icon-mdpi.png     (images/reader-toggle-on-icon-mdpi.png)
   skin/images/reader-toggle-on-icon-hdpi.png     (images/reader-toggle-on-icon-hdpi.png)
   skin/images/reader-toggle-on-icon-xhdpi.png    (images/reader-toggle-on-icon-xhdpi.png)
   skin/images/reader-toggle-off-icon-mdpi.png    (images/reader-toggle-off-icon-mdpi.png)
   skin/images/reader-toggle-off-icon-hdpi.png    (images/reader-toggle-off-icon-hdpi.png)
--- a/services/common/hawkclient.js
+++ b/services/common/hawkclient.js
@@ -32,34 +32,42 @@ Cu.import("resource://services-common/ut
 Cu.import("resource://services-crypto/utils.js");
 Cu.import("resource://services-common/hawkrequest.js");
 Cu.import("resource://services-common/observers.js");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
-// loglevel should be one of "Fatal", "Error", "Warn", "Info", "Config",
+// log.appender.dump should be one of "Fatal", "Error", "Warn", "Info", "Config",
 // "Debug", "Trace" or "All". If none is specified, "Error" will be used by
 // default.
-const PREF_LOG_LEVEL = "services.hawk.loglevel";
+// Note however that Sync will also add this log to *its* DumpAppender, so
+// in a Sync context it shouldn't be necessary to adjust this - however, that
+// also means error logs are likely to be dump'd twice but that's OK.
+const PREF_LOG_LEVEL = "services.common.hawk.log.appender.dump";
 
 // A pref that can be set so "sensitive" information (eg, personally
 // identifiable info, credentials, etc) will be logged.
-const PREF_LOG_SENSITIVE_DETAILS = "services.hawk.log.sensitive";
+const PREF_LOG_SENSITIVE_DETAILS = "services.common.hawk.log.sensitive";
 
 XPCOMUtils.defineLazyGetter(this, "log", function() {
   let log = Log.repository.getLogger("Hawk");
-  log.addAppender(new Log.DumpAppender());
-  log.level = Log.Level.Error;
+  // We set the log itself to "debug" and set the level from the preference to
+  // the appender.  This allows other things to send the logs to different
+  // appenders, while still allowing the pref to control what is seen via dump()
+  log.level = Log.Level.Debug;
+  let appender = new Log.DumpAppender();
+  log.addAppender(appender);
+  appender.level = Log.Level.Error;
   try {
     let level =
       Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
       && Services.prefs.getCharPref(PREF_LOG_LEVEL);
-    log.level = Log.Level[level] || Log.Level.Error;
+    appender.level = Log.Level[level] || Log.Level.Error;
   } catch (e) {
     log.error(e);
   }
 
   return log;
 });
 
 // A boolean to indicate if personally identifiable information (or anything
@@ -94,22 +102,27 @@ this.HawkClient = function(host) {
 this.HawkClient.prototype = {
 
   /*
    * Construct an error message for a response.  Private.
    *
    * @param restResponse
    *        A RESTResponse object from a RESTRequest
    *
-   * @param errorString
-   *        A string describing the error
+   * @param error
+   *        A string or object describing the error
    */
-  _constructError: function(restResponse, errorString) {
+  _constructError: function(restResponse, error) {
     let errorObj = {
-      error: errorString,
+      error: error,
+      // This object is likely to be JSON.stringify'd, but neither Error()
+      // objects nor Components.Exception objects do the right thing there,
+      // so we add a new element which is simply the .toString() version of
+      // the error object, so it does appear in JSON'd values.
+      errorString: error.toString(),
       message: restResponse.statusText,
       code: restResponse.status,
       errno: restResponse.status
     };
     errorObj.toString = function() this.code + ": " + this.message;
     let retryAfter = restResponse.headers && restResponse.headers["retry-after"];
     retryAfter = retryAfter ? parseInt(retryAfter) : retryAfter;
     if (retryAfter) {
@@ -185,16 +198,22 @@ this.HawkClient.prototype = {
   request: function(path, method, credentials=null, payloadObj={}, retryOK=true) {
     method = method.toLowerCase();
 
     let deferred = Promise.defer();
     let uri = this.host + path;
     let self = this;
 
     function _onComplete(error) {
+      // |error| can be either a normal caught error or an explicitly created
+      // Components.Exception() error. Log it now as it might not end up
+      // correctly in the logs by the time it's passed through _constructError.
+      if (error) {
+        log.warn("hawk request error", error);
+      }
       let restResponse = this.response;
       let status = restResponse.status;
 
       log.debug("(Response) " + path + ": code: " + status +
                 " - Status text: " + restResponse.statusText);
       if (logPII) {
         log.debug("Response text: " + restResponse.body);
       }
@@ -257,20 +276,25 @@ this.HawkClient.prototype = {
     }
 
     let extra = {
       now: this.now(),
       localtimeOffsetMsec: this.localtimeOffsetMsec,
     };
 
     let request = this.newHAWKAuthenticatedRESTRequest(uri, credentials, extra);
-    if (method == "post" || method == "put" || method == "patch") {
-      request[method](payloadObj, onComplete);
-    } else {
-      request[method](onComplete);
+    try {
+      if (method == "post" || method == "put" || method == "patch") {
+        request[method](payloadObj, onComplete);
+      } else {
+        request[method](onComplete);
+      }
+    } catch (ex) {
+      log.error("Failed to make hawk request", ex);
+      deferred.reject(ex);
     }
 
     return deferred.promise;
   },
 
   /*
    * The prefix used for all notifications sent by this module.  This
    * allows the handler of notifications to be sure they are handling
--- a/services/common/rest.js
+++ b/services/common/rest.js
@@ -453,16 +453,17 @@ RESTRequest.prototype = {
     }
 
     // Throw the failure code and stop execution.  Use Components.Exception()
     // instead of Error() so the exception is QI-able and can be passed across
     // XPCOM borders while preserving the status code.
     if (!statusSuccess) {
       let message = Components.Exception("", statusCode).name;
       let error = Components.Exception(message, statusCode);
+      this._log.debug(this.method + " " + uri + " failed: " + statusCode + " - " + message);
       this.onComplete(error);
       this.onComplete = this.onProgress = null;
       return;
     }
 
     this._log.debug(this.method + " " + uri + " " + this.response.status);
 
     // Additionally give the full response body when Trace logging.
--- a/services/common/services-common.js
+++ b/services/common/services-common.js
@@ -4,9 +4,9 @@
 
 // This file contains default preference values for components in
 // services-common.
 
 pref("services.common.log.logger.rest.request", "Debug");
 pref("services.common.log.logger.rest.response", "Debug");
 
 pref("services.common.storageservice.sendVersionInfo", true);
-pref("services.common.tokenserverclient.logger.level", "Info");
+pref("services.common.log.logger.tokenserverclient", "Debug");
--- a/services/common/tokenserverclient.js
+++ b/services/common/tokenserverclient.js
@@ -8,23 +8,23 @@ this.EXPORTED_SYMBOLS = [
   "TokenServerClient",
   "TokenServerClientError",
   "TokenServerClientNetworkError",
   "TokenServerClientServerError",
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
-Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/rest.js");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-common/observers.js");
 
-const Prefs = new Preferences("services.common.tokenserverclient.");
+const PREF_LOG_LEVEL = "services.common.log.logger.tokenserverclient";
 
 /**
  * Represents a TokenServerClient error that occurred on the client.
  *
  * This is the base type for all errors raised by client operations.
  *
  * @param message
  *        (string) Error message.
@@ -135,17 +135,21 @@ TokenServerClientServerError.prototype._
  *  - The server sends a JSON response on error. The client does not currently
  *    parse this. It might be convenient if it did.
  *  - Currently most non-200 status codes are rolled into one error type. It
  *    might be helpful if callers had a richer API that communicated who was
  *    at fault (e.g. differentiating a 503 from a 401).
  */
 this.TokenServerClient = function TokenServerClient() {
   this._log = Log.repository.getLogger("Common.TokenServerClient");
-  this._log.level = Log.Level[Prefs.get("logger.level")];
+  let level = "Debug";
+  try {
+    level = Services.prefs.getCharPref(PREF_LOG_LEVEL);
+  } catch (ex) {}
+  this._log.level = Log.Level[level];
 }
 TokenServerClient.prototype = {
   /**
    * Logger instance.
    */
   _log: null,
 
   /**
@@ -399,17 +403,17 @@ TokenServerClient.prototype = {
                                                      k);
         error.cause = "malformed-response";
         error.response = response;
         cb(error, null);
         return;
       }
     }
 
-    this._log.debug("Successful token response: " + result.id);
+    this._log.debug("Successful token response");
     cb(null, {
       id:       result.id,
       key:      result.key,
       endpoint: result.api_endpoint,
       uid:      result.uid,
       duration: result.duration,
     });
   },
--- a/services/fxaccounts/FxAccountsClient.jsm
+++ b/services/fxaccounts/FxAccountsClient.jsm
@@ -365,17 +365,17 @@ this.FxAccountsClient.prototype = {
           this.backoffError = error;
           // Schedule clearing of cached-error-as-flag.
           CommonUtils.namedTimer(
             this._clearBackoff,
             error.retryAfter * 1000,
             this,
             "fxaBackoffTimer"
            );
-	}
+        }
         deferred.reject(error);
       }
     );
 
     return deferred.promise;
   },
 };
 
--- a/services/healthreport/healthreporter.jsm
+++ b/services/healthreport/healthreporter.jsm
@@ -280,17 +280,17 @@ HealthReporterState.prototype = Object.f
       if (lastPingDate && lastPingDate.getTime() > ourLast.getTime()) {
         this._log.info("Migrating last ping time: " + lastPingDate);
         this._s.lastPingTime = lastPingDate.getTime();
       }
 
       yield this.save();
       prefs.reset(["lastSubmitID", "lastPingTime"]);
     } else {
-      this._log.warn("No prefs data found.");
+      this._log.debug("No prefs data found.");
     }
   },
 });
 
 /**
  * This is the abstract base class of `HealthReporter`. It exists so that
  * we can sanely divide work on platforms where control of Firefox Health
  * Report is outside of Gecko (e.g., Android).
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -180,17 +180,17 @@ this.BrowserIDManager.prototype = {
     // While this function returns a promise that resolves once we've started
     // the auth process, that process is complete when
     // this.whenReadyToAuthenticate.promise resolves.
     this._log.trace("initializeWithCurrentIdentity");
 
     // Reset the world before we do anything async.
     this.whenReadyToAuthenticate = Promise.defer();
     this.whenReadyToAuthenticate.promise.then(null, (err) => {
-      this._log.error("Could not authenticate: " + err);
+      this._log.error("Could not authenticate", err);
     });
 
     // initializeWithCurrentIdentity() can be called after the
     // identity module was first initialized, e.g., after the
     // user completes a force authentication, so we should make
     // sure all credentials are reset before proceeding.
     this.resetCredentials();
     this._authFailureReason = null;
@@ -239,21 +239,21 @@ this.BrowserIDManager.prototype = {
           Svc.Prefs.set("firstSync", "resetClient");
           Services.obs.notifyObservers(null, "weave:service:setup-complete", null);
           Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
         }
       }).then(null, err => {
         this._shouldHaveSyncKeyBundle = true; // but we probably don't have one...
         this.whenReadyToAuthenticate.reject(err);
         // report what failed...
-        this._log.error("Background fetch for key bundle failed: " + err);
+        this._log.error("Background fetch for key bundle failed", err);
       });
       // and we are done - the fetch continues on in the background...
     }).then(null, err => {
-      this._log.error("Processing logged in account: " + err);
+      this._log.error("Processing logged in account", err);
     });
   },
 
   _updateSignedInUser: function(userData) {
     // This object should only ever be used for a single user.  It is an
     // error to update the data if the user changes (but updates are still
     // necessary, as each call may add more attributes to the user).
     // We start with no user, so an initial update is always ok.
@@ -507,43 +507,43 @@ this.BrowserIDManager.prototype = {
       return false;
     }
     if (this._token.expiration < this._now()) {
       return false;
     }
     return true;
   },
 
-  // Refresh the sync token for our user.
+  // Refresh the sync token for our user. Returns a promise that resolves
+  // with a token (which may be null in one sad edge-case), or rejects with an
+  // error.
   _fetchTokenForUser: function() {
     let tokenServerURI = Svc.Prefs.get("tokenServerURI");
     if (tokenServerURI.endsWith("/")) { // trailing slashes cause problems...
       tokenServerURI = tokenServerURI.slice(0, -1);
     }
     let log = this._log;
     let client = this._tokenServerClient;
     let fxa = this._fxaService;
     let userData = this._signedInUser;
 
     // We need kA and kB for things to work.  If we don't have them, just
     // return null for the token - sync calling unlockAndVerifyAuthState()
     // before actually syncing will setup the error states if necessary.
     if (!this._canFetchKeys()) {
-      log.info("_fetchTokenForUser has no keys to use.");
-      return null;
+      return Promise.resolve(null);
     }
 
-    log.info("Fetching assertion and token from: " + tokenServerURI);
-
     let maybeFetchKeys = () => {
       // This is called at login time and every time we need a new token - in
       // the latter case we already have kA and kB, so optimise that case.
       if (userData.kA && userData.kB) {
         return;
       }
+      log.info("Fetching new keys");
       return this._fxaService.getKeys().then(
         newUserData => {
           userData = newUserData;
           this._updateSignedInUser(userData); // throws if the user changed.
         }
       );
     }
 
@@ -560,17 +560,17 @@ this.BrowserIDManager.prototype = {
 
       let kBbytes = CommonUtils.hexToBytes(userData.kB);
       let headers = {"X-Client-State": this._computeXClientState(kBbytes)};
       client.getTokenFromBrowserIDAssertion(tokenServerURI, assertion, cb, headers);
       return deferred.promise;
     }
 
     let getAssertion = () => {
-      log.debug("Getting an assertion");
+      log.info("Getting an assertion from", tokenServerURI);
       let audience = Services.io.newURI(tokenServerURI, null, null).prePath;
       return fxa.getAssertion(audience);
     };
 
     // wait until the account email is verified and we know that
     // getAssertion() will return a real assertion (not null).
     return fxa.whenVerified(this._signedInUser)
       .then(() => maybeFetchKeys())
@@ -589,31 +589,31 @@ this.BrowserIDManager.prototype = {
       })
       .then(null, err => {
         // TODO: unify these errors - we need to handle errors thrown by
         // both tokenserverclient and hawkclient.
         // A tokenserver error thrown based on a bad response.
         if (err.response && err.response.status === 401) {
           err = new AuthenticationError(err);
         // A hawkclient error.
-        } else if (err.code === 401) {
+        } else if (err.code && err.code === 401) {
           err = new AuthenticationError(err);
         }
 
         // TODO: write tests to make sure that different auth error cases are handled here
         // properly: auth error getting assertion, auth error getting token (invalid generation
         // and client-state error)
         if (err instanceof AuthenticationError) {
-          this._log.error("Authentication error in _fetchTokenForUser: " + err);
+          this._log.error("Authentication error in _fetchTokenForUser", err);
           // set it to the "fatal" LOGIN_FAILED_LOGIN_REJECTED reason.
           this._authFailureReason = LOGIN_FAILED_LOGIN_REJECTED;
         } else {
-          this._log.error("Non-authentication error in _fetchTokenForUser: "
-                          + (err.message || err));
-          // for now assume it is just a transient network related problem.
+          this._log.error("Non-authentication error in _fetchTokenForUser", err);
+          // for now assume it is just a transient network related problem
+          // (although sadly, it might also be a regular unhandled exception)
           this._authFailureReason = LOGIN_FAILED_NETWORK_ERROR;
         }
         // this._authFailureReason being set to be non-null in the above if clause
         // ensures we are in the correct currentAuthState, and
         // this._shouldHaveSyncKeyBundle being true ensures everything that cares knows
         // that there is no authentication dance still under way.
         this._shouldHaveSyncKeyBundle = true;
         Weave.Status.login = this._authFailureReason;
@@ -624,16 +624,19 @@ this.BrowserIDManager.prototype = {
 
   // Returns a promise that is resolved when we have a valid token for the
   // current user stored in this._token.  When resolved, this._token is valid.
   _ensureValidToken: function() {
     if (this.hasValidToken()) {
       this._log.debug("_ensureValidToken already has one");
       return Promise.resolve();
     }
+    // reset this._token as a safety net to reduce the possibility of us
+    // repeatedly attempting to use an invalid token if _fetchTokenForUser throws.
+    this._token = null;
     return this._fetchTokenForUser().then(
       token => {
         this._token = token;
       }
     );
   },
 
   getResourceAuthenticator: function () {
@@ -652,17 +655,17 @@ this.BrowserIDManager.prototype = {
    * of a RESTRequest or AsyncResponse object.
    */
   _getAuthenticationHeader: function(httpObject, method) {
     let cb = Async.makeSpinningCallback();
     this._ensureValidToken().then(cb, cb);
     try {
       cb.wait();
     } catch (ex) {
-      this._log.error("Failed to fetch a token for authentication: " + ex);
+      this._log.error("Failed to fetch a token for authentication", ex);
       return null;
     }
     if (!this._token) {
       return null;
     }
     let credentials = {algorithm: "sha256",
                        id: this._token.id,
                        key: this._token.key,
@@ -703,16 +706,27 @@ function BrowserIDClusterManager(service
   ClusterManager.call(this, service);
 }
 
 BrowserIDClusterManager.prototype = {
   __proto__: ClusterManager.prototype,
 
   _findCluster: function() {
     let endPointFromIdentityToken = function() {
+      // The only reason (in theory ;) that we can end up with a null token
+      // is when this.identity._canFetchKeys() returned false.  In turn, this
+      // should only happen if the master-password is locked or the credentials
+      // storage is screwed, and in those cases we shouldn't have started
+      // syncing so shouldn't get here anyway.
+      // But better safe than sorry! To keep things clearer, throw an explicit
+      // exception - the message will appear in the logs and the error will be
+      // treated as transient.
+      if (!this.identity._token) {
+        throw new Error("Can't get a cluster URL as we can't fetch keys.");
+      }
       let endpoint = this.identity._token.endpoint;
       // For Sync 1.5 storage endpoints, we use the base endpoint verbatim.
       // However, it should end in "/" because we will extend it with
       // well known path components. So we add a "/" if it's missing.
       if (!endpoint.endsWith("/")) {
         endpoint += "/";
       }
       log.debug("_findCluster returning " + endpoint);
@@ -737,16 +751,17 @@ BrowserIDClusterManager.prototype = {
       );
     }.bind(this);
 
     let cb = Async.makeSpinningCallback();
     promiseClusterURL().then(function (clusterURL) {
       cb(null, clusterURL);
     }).then(
       null, err => {
+      log.info("Failed to fetch the cluster URL", err);
       // service.js's verifyLogin() method will attempt to fetch a cluster
       // URL when it sees a 401.  If it gets null, it treats it as a "real"
       // auth error and sets Status.login to LOGIN_FAILED_LOGIN_REJECTED, which
       // in turn causes a notification bar to appear informing the user they
       // need to re-authenticate.
       // On the other hand, if fetching the cluster URL fails with an exception,
       // verifyLogin() assumes it is a transient error, and thus doesn't show
       // the notification bar under the assumption the issue will resolve
--- a/services/sync/modules/policies.js
+++ b/services/sync/modules/policies.js
@@ -587,18 +587,23 @@ ErrorHandler.prototype = {
     let dapp = new Log.DumpAppender(formatter);
     dapp.level = Log.Level[Svc.Prefs.get("log.appender.dump")];
     root.addAppender(dapp);
 
     let fapp = this._logAppender = new Log.StorageStreamAppender(formatter);
     fapp.level = Log.Level[Svc.Prefs.get("log.appender.file.level")];
     root.addAppender(fapp);
 
-    // Arrange for the FxA logs to also go to our file.
-    Log.repository.getLogger("FirefoxAccounts").addAppender(fapp);
+    // Arrange for the FxA, Hawk and TokenServer logs to also go to our appenders.
+    for (let extra of ["FirefoxAccounts", "Hawk", "Common.TokenServerClient"]) {
+      let log = Log.repository.getLogger(extra);
+      for (let appender of [fapp, dapp, capp]) {
+        log.addAppender(appender);
+      }
+    }
   },
 
   observe: function observe(subject, topic, data) {
     this._log.trace("Handling " + topic);
     switch(topic) {
       case "weave:engine:sync:applied":
         if (subject.newFailed) {
           // An engine isn't able to apply one or more incoming records.
--- a/services/sync/tests/unit/test_browserid_identity.js
+++ b/services/sync/tests/unit/test_browserid_identity.js
@@ -90,16 +90,17 @@ add_task(function test_initialializeWith
     delete identityConfig.fxaccount.user.kB;
     // there's no keyFetchToken by default, so the initialize should fail.
     configureFxAccountIdentity(browseridManager, identityConfig);
 
     yield browseridManager.initializeWithCurrentIdentity();
     yield browseridManager.whenReadyToAuthenticate.promise;
     do_check_eq(Status.login, LOGIN_SUCCEEDED, "login succeeded even without keys");
     do_check_false(browseridManager._canFetchKeys(), "_canFetchKeys reflects lack of keys");
+    do_check_eq(browseridManager._token, null, "we don't have a token");
 });
 
 add_test(function test_getResourceAuthenticator() {
     _("BrowserIDManager supplies a Resource Authenticator callback which returns a Hawk header.");
     configureFxAccountIdentity(browseridManager);
     let authenticator = browseridManager.getResourceAuthenticator();
     do_check_true(!!authenticator);
     let req = {uri: CommonUtils.makeURI(
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -46,16 +46,29 @@
 
 // Maximum size of the pages cache per connection.
 #define MAX_CACHE_SIZE_KIBIBYTES 2048 // 2 MiB
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gStorageLog = nullptr;
 #endif
 
+// Checks that the protected code is running on the main-thread only if the
+// connection was also opened on it.
+#ifdef DEBUG
+#define CHECK_MAINTHREAD_ABUSE() \
+  do { \
+    nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); \
+    NS_WARN_IF_FALSE(threadOpenedOn == mainThread || !NS_IsMainThread(), \
+               "Using Storage synchronous API on main-thread, but the connection was opened on another thread."); \
+  } while(0)
+#else
+#define CHECK_MAINTHREAD_ABUSE() do { /* Nothing */ } while(0)
+#endif
+
 namespace mozilla {
 namespace storage {
 
 namespace {
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Variant Specialization Functions (variantToSQLiteT)
 
@@ -1417,16 +1430,17 @@ Connection::CreateAsyncStatement(const n
   statement.forget(&rawPtr);
   *_stmt = rawPtr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::ExecuteSimpleSQL(const nsACString &aSQLStatement)
 {
+  CHECK_MAINTHREAD_ABUSE();
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
   int srv = executeSql(mDBConn, PromiseFlatCString(aSQLStatement).get());
   return convertResultCode(srv);
 }
 
 NS_IMETHODIMP
 Connection::ExecuteAsync(mozIStorageBaseStatement **aStatements,
--- a/storage/src/mozStorageService.cpp
+++ b/storage/src/mozStorageService.cpp
@@ -342,32 +342,42 @@ Service::getConnections(/* inout */ nsTA
 void
 Service::minimizeMemory()
 {
   nsTArray<nsRefPtr<Connection> > connections;
   getConnections(connections);
 
   for (uint32_t i = 0; i < connections.Length(); i++) {
     nsRefPtr<Connection> conn = connections[i];
-    if (conn->connectionReady()) {
-      NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory");
-      nsCOMPtr<mozIStorageConnection> syncConn = do_QueryInterface(
-        NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, conn));
-      DebugOnly<nsresult> rv;
+    if (!conn->connectionReady())
+      continue;
+
+    NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory");
+    nsCOMPtr<mozIStorageConnection> syncConn = do_QueryInterface(
+      NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, conn));
+    bool onOpenedThread = false;
 
-      if (!syncConn) {
-        nsCOMPtr<mozIStoragePendingStatement> ps;
-        rv = connections[i]->ExecuteSimpleSQLAsync(shrinkPragma, nullptr,
-          getter_AddRefs(ps));
-      } else {
-        rv = connections[i]->ExecuteSimpleSQL(shrinkPragma);
-      }
-
-      MOZ_ASSERT(NS_SUCCEEDED(rv),
-        "Should have been able to purge sqlite caches");
+    if (!syncConn) {
+      // This is a mozIStorageAsyncConnection, it can only be used on the main
+      // thread, so we can do a straight API call.
+      nsCOMPtr<mozIStoragePendingStatement> ps;
+      DebugOnly<nsresult> rv =
+        conn->ExecuteSimpleSQLAsync(shrinkPragma, nullptr, getter_AddRefs(ps));
+      MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
+    } else if (NS_SUCCEEDED(conn->threadOpenedOn->IsOnCurrentThread(&onOpenedThread)) &&
+               onOpenedThread) {
+      // We are on the opener thread, so we can just proceed.
+      conn->ExecuteSimpleSQL(shrinkPragma);
+    } else {
+      // We are on the wrong thread, the query should be executed on the
+      // opener thread, so we must dispatch to it.
+      nsCOMPtr<nsIRunnable> event =
+        NS_NewRunnableMethodWithArg<const nsCString>(
+          conn, &Connection::ExecuteSimpleSQL, shrinkPragma);
+      conn->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL);
     }
   }
 }
 
 void
 Service::shutdown()
 {
   NS_IF_RELEASE(sXPConnect);
--- a/toolkit/components/downloads/test/unit/downloads_manifest.js
+++ b/toolkit/components/downloads/test/unit/downloads_manifest.js
@@ -10,15 +10,13 @@ const Ci = Components.interfaces;
 
 function HelperAppDlg() { }
 HelperAppDlg.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]),
   classID: Components.ID("49456eda-4dc4-4d1a-b8e8-0b94749bf99e"),
   show: function (launcher, ctx, reason) {
     launcher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.saveToDisk;
     launcher.launchWithApplication(null, false);
-  },
-
-  promptForSaveToFile: function (launcher, ctx, defaultFile, suggestedExtension, forcePrompt) { }
+  }
 }
 
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HelperAppDlg]);
--- a/toolkit/components/jsdownloads/test/unit/head.js
+++ b/toolkit/components/jsdownloads/test/unit/head.js
@@ -788,25 +788,16 @@ add_task(function test_common_initialize
   do_register_cleanup(() => registrar = null);
 
   // Make sure that downloads started using nsIExternalHelperAppService are
   // saved to disk without asking for a destination interactively.
   let mockFactory = {
     createInstance: function (aOuter, aIid) {
       return {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]),
-        promptForSaveToFile: function (aLauncher, aWindowContext,
-                                       aDefaultFileName,
-                                       aSuggestedFileExtension,
-                                       aForcePrompt)
-        {
-          throw new Components.Exception(
-                             "Synchronous promptForSaveToFile not implemented.",
-                             Cr.NS_ERROR_NOT_AVAILABLE);
-        },
         promptForSaveToFileAsync: function (aLauncher, aWindowContext,
                                             aDefaultFileName,
                                             aSuggestedFileExtension,
                                             aForcePrompt)
         {
           // The dialog should create the empty placeholder file.
           let file = getTempFile(TEST_TARGET_FILE_NAME);
           file.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
--- a/toolkit/mozapps/downloads/nsHelperAppDlg.js
+++ b/toolkit/mozapps/downloads/nsHelperAppDlg.js
@@ -190,32 +190,16 @@ nsUnknownContentTypeDialog.prototype = {
     let bundle =
       Services.strings.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
 
     Services.prompt.alert(this.dialog,
                    bundle.GetStringFromName("badPermissions.title"),
                    bundle.GetStringFromName("badPermissions"));
   },
 
-  // promptForSaveToFile:  Display file picker dialog and return selected file.
-  //                       This is called by the External Helper App Service
-  //                       after the ucth dialog calls |saveToDisk| with a null
-  //                       target filename (no target, therefore user must pick).
-  //
-  //                       Alternatively, if the user has selected to have all
-  //                       files download to a specific location, return that
-  //                       location and don't ask via the dialog.
-  //
-  // Note - this function is called without a dialog, so it cannot access any part
-  // of the dialog XUL as other functions on this object do.
-
-  promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) {
-    throw new Components.Exception("Async version must be used", Components.results.NS_ERROR_NOT_AVAILABLE);
-  },
-
   promptForSaveToFileAsync: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) {
     var result = null;
 
     this.mLauncher = aLauncher;
 
     let prefs = Components.classes["@mozilla.org/preferences-service;1"]
                           .getService(Components.interfaces.nsIPrefBranch);
     let bundle =
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -2232,34 +2232,26 @@ void nsExternalAppHandler::RequestSaveDe
   // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we can't unescape
   // it because the dialog is implemented by a JS component which doesn't have a window so no unescape routine is defined...
 
   // Now, be sure to keep |this| alive, and the dialog
   // If we don't do this, users that close the helper app dialog while the file
   // picker is up would cause Cancel() to be called, and the dialog would be
   // released, which would release this object too, which would crash.
   // See Bug 249143
-  nsIFile* fileToUse;
   nsRefPtr<nsExternalAppHandler> kungFuDeathGrip(this);
   nsCOMPtr<nsIHelperAppLauncherDialog> dlg(mDialog);
-  rv = mDialog->PromptForSaveToFile(this,
-                                    GetDialogParent(),
-                                    aDefaultFile.get(),
-                                    aFileExtension.get(),
-                                    mForceSave, &fileToUse);
-
-  if (rv == NS_ERROR_NOT_AVAILABLE) {
-    // we need to use the async version -> nsIHelperAppLauncherDialog.promptForSaveToFileAsync.
-    rv = mDialog->PromptForSaveToFileAsync(this, 
-                                           GetDialogParent(),
-                                           aDefaultFile.get(),
-                                           aFileExtension.get(),
-                                           mForceSave);
-  } else {
-    SaveDestinationAvailable(rv == NS_OK ? fileToUse : nullptr);
+
+  rv = mDialog->PromptForSaveToFileAsync(this,
+                                         GetDialogParent(),
+                                         aDefaultFile.get(),
+                                         aFileExtension.get(),
+                                         mForceSave);
+  if (NS_FAILED(rv)) {
+    Cancel(NS_BINDING_ABORTED);
   }
 }
 
 // SaveToDisk should only be called by the helper app dialog which allows
 // the user to say launch with application or save to disk. It doesn't actually
 // perform the save, it just prompts for the destination file name.
 NS_IMETHODIMP nsExternalAppHandler::SaveToDisk(nsIFile * aNewFileLocation, bool aRememberThisPreference)
 {
--- a/uriloader/exthandler/nsIHelperAppLauncherDialog.idl
+++ b/uriloader/exthandler/nsIHelperAppLauncherDialog.idl
@@ -16,17 +16,17 @@ interface nsIFile;
  * Usage:  Clients (of which there is one: the nsIExternalHelperAppService
  * implementation in mozilla/uriloader/exthandler) create an instance of
  * this interface (using the contract ID) and then call the show() method.
  *
  * The dialog is shown non-modally.  The implementation of the dialog
  * will access methods of the nsIHelperAppLauncher passed in to show()
  * in order to cause a "save to disk" or "open using" action.
  */
-[scriptable, uuid(3ae4dca8-ac91-4891-adcf-3fbebed6170e)]
+[scriptable, uuid(bfc739f3-8d75-4034-a6f8-1039a5996bad)]
 interface nsIHelperAppLauncherDialog : nsISupports {
   /**
    * This request is passed to the helper app dialog because Gecko can not
    * handle content of this type.
    */
   const unsigned long REASON_CANTHANDLE = 0;
 
   /**
@@ -53,42 +53,16 @@ interface nsIHelperAppLauncherDialog : n
    *        shown. Implementors should treat unknown reasons like
    *        REASON_CANTHANDLE.
    */
   void show(in nsIHelperAppLauncher aLauncher,
             in nsISupports aWindowContext,
             in unsigned long aReason);
 
   /**
-   * Invoke a save-to-file dialog instead of the full fledged helper app dialog.
-   * Returns the a nsIFile for the file name/location selected.
-   *
-   * @param aLauncher
-   *        A nsIHelperAppLauncher to be invoked when a file is selected.
-   * @param aWindowContext
-   *        Window associated with action.
-   * @param aDefaultFileName
-   *        Default file name to provide (can be null)
-   * @param aSuggestedFileExtension
-   *        Sugested file extension
-   * @param aForcePrompt
-   *        Set to true to force prompting the user for thet file
-   *        name/location, otherwise perferences may control if the user is
-   *        prompted.
-   *
-   * @throws NS_ERROR_NOT_AVAILABLE if the async version of this function
-   *                                should be used.
-   */
-  nsIFile promptForSaveToFile(in nsIHelperAppLauncher aLauncher,
-                              in nsISupports aWindowContext,
-                              in wstring aDefaultFileName,
-                              in wstring aSuggestedFileExtension,
-                              in boolean aForcePrompt);
-
-  /**
    * Async invoke a save-to-file dialog instead of the full fledged helper app
    * dialog. When the file is chosen (or the dialog is closed), the callback
    * in aLauncher (aLauncher.saveDestinationAvailable) is called with the
    * selected file.
    *
    * @param aLauncher
    *        A nsIHelperAppLauncher to be invoked when a file is selected.
    * @param aWindowContext
--- a/uriloader/exthandler/tests/mochitest/test_unsafeBidiChars.xhtml
+++ b/uriloader/exthandler/tests/mochitest/test_unsafeBidiChars.xhtml
@@ -79,19 +79,16 @@ function load() {
     REASON_TYPESNIFFED: 2,
     show: function(aLauncher, aWindowContext, aReason) {
       netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
       var test = gTests[gCounter];
       is(aLauncher.suggestedFileName, test.expected,
          "The filename should be correctly sanitized");
       gCallback();
     },
-    promptForSaveToFile: function(aLauncher, aWindowContext, aDefaultFileName, aSuggestedFileExtension, aForcePrompt) {
-      return null;
-    },
     QueryInterface: function(aIID) {
       netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
       if (aIID.equals(SpecialPowers.Ci.nsISupports) ||
           aIID.equals(SpecialPowers.Ci.nsIHelperAppLauncherDialog))
         return this;
       throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
     }
   };
--- a/uriloader/exthandler/tests/unit_ipc/test_encoding.js
+++ b/uriloader/exthandler/tests/unit_ipc/test_encoding.js
@@ -56,19 +56,17 @@ DownloadListener.init();
 
 function HelperAppDlg() { }
 HelperAppDlg.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]),
   contractID: "@mozilla.org/helperapplauncherdialog;1",
   show: function (launcher, ctx, reason, usePrivateUI) {
     launcher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.saveToFile;
     launcher.launchWithApplication(null, false);
-  },
-
-  promptForSaveToFile: function (launcher, ctx, defaultFile, suggestedExtension, forcePrompt) { }
+  }
 }
 
 // Stolen from XPCOMUtils, since this handy function is not public there
 function getFactory(comp)
 {
   return {
     createInstance: function (outer, iid) {
       if (outer)
--- a/widget/MouseEvents.h
+++ b/widget/MouseEvents.h
@@ -78,16 +78,17 @@ protected:
   }
 
   WidgetMouseEventBase(bool aIsTrusted, uint32_t aMessage, nsIWidget* aWidget,
                        EventClassID aEventClassID)
     : WidgetInputEvent(aIsTrusted, aMessage, aWidget, aEventClassID)
     , button(0)
     , buttons(0)
     , pressure(0)
+    , hitCluster(false)
     , inputSource(nsIDOMMouseEvent::MOZ_SOURCE_MOUSE)
  {
  }
 
 public:
   virtual WidgetMouseEventBase* AsMouseEventBase() MOZ_OVERRIDE { return this; }
 
   virtual WidgetEvent* Duplicate() const MOZ_OVERRIDE
@@ -122,32 +123,35 @@ public:
   };
 
   // Flags of all pressed buttons at the event fired.
   // This is set at any mouse event, don't be confused with |button|.
   int16_t buttons;
 
   // Finger or touch pressure of event. It ranges between 0.0 and 1.0.
   float pressure;
+  // Touch near a cluster of links (true)
+  bool hitCluster;
 
   // Possible values at nsIDOMMouseEvent
   uint16_t inputSource;
 
   // ID of the canvas HitRegion
   nsString region;
 
   void AssignMouseEventBaseData(const WidgetMouseEventBase& aEvent,
                                 bool aCopyTargets)
   {
     AssignInputEventData(aEvent, aCopyTargets);
 
     relatedTarget = aCopyTargets ? aEvent.relatedTarget : nullptr;
     button = aEvent.button;
     buttons = aEvent.buttons;
     pressure = aEvent.pressure;
+    hitCluster = aEvent.hitCluster;
     inputSource = aEvent.inputSource;
   }
 
   /**
    * Returns true if left click event.
    */
   bool IsLeftClickEvent() const
   {
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -1699,16 +1699,101 @@ AndroidBridge::GetFrameNameJavaProfiling
 
     if (!jstrSampleName)
         return false;
 
     aResult = nsCString(jstrSampleName);
     return true;
 }
 
+static float
+GetScaleFactor(nsPresContext* mPresContext) {
+  nsIPresShell* presShell = mPresContext->PresShell();
+  LayoutDeviceToLayerScale cumulativeResolution(presShell->GetCumulativeResolution().width);
+  return cumulativeResolution.scale;
+}
+
+nsresult
+AndroidBridge::CaptureZoomedView (nsIDOMWindow *window, nsIntRect zoomedViewRect, Object::Param buffer,
+                                  float zoomFactor) {
+  nsresult rv;
+  struct timeval        timeStart;
+  gettimeofday (&timeStart, NULL);
+
+  if (!buffer)
+    return NS_ERROR_FAILURE;
+
+  nsCOMPtr < nsIDOMWindowUtils > utils = do_GetInterface (window);
+  if (!utils)
+    return NS_ERROR_FAILURE;
+
+  JNIEnv* env = GetJNIEnv ();
+
+  AutoLocalJNIFrame jniFrame (env, 0);
+
+  nsCOMPtr < nsPIDOMWindow > win = do_QueryInterface (window);
+  if (!win) {
+    return NS_ERROR_FAILURE;
+  }
+  nsRefPtr < nsPresContext > presContext;
+
+  nsIDocShell* docshell = win->GetDocShell ();
+
+  if (docshell) {
+    docshell->GetPresContext (getter_AddRefs (presContext));
+  }
+
+  if (!presContext) {
+    return NS_ERROR_FAILURE;
+  }
+  nsCOMPtr < nsIPresShell > presShell = presContext->PresShell ();
+
+  float scaleFactor = GetScaleFactor(presContext) ;
+
+      nscolor bgColor = NS_RGB (255, 255, 255);
+  uint32_t renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | nsIPresShell::RENDER_DOCUMENT_RELATIVE);
+  nsRect r (presContext->DevPixelsToAppUnits(zoomedViewRect.x / scaleFactor),
+            presContext->DevPixelsToAppUnits(zoomedViewRect.y / scaleFactor ),
+            presContext->DevPixelsToAppUnits(zoomedViewRect.width / scaleFactor ),
+            presContext->DevPixelsToAppUnits(zoomedViewRect.height / scaleFactor ));
+
+  bool is24bit = (GetScreenDepth () == 24);
+  SurfaceFormat format = is24bit ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::R5G6B5;
+  gfxImageFormat iFormat = gfx::SurfaceFormatToImageFormat(format);
+  uint32_t stride = gfxASurface::FormatStrideForWidth(iFormat, zoomedViewRect.width);
+
+  uint8_t* data = static_cast<uint8_t*> (env->GetDirectBufferAddress (buffer.Get()));
+  if (!data) {
+    return NS_ERROR_FAILURE;
+  }
+
+  MOZ_ASSERT (gfxPlatform::GetPlatform ()->SupportsAzureContentForType (BackendType::CAIRO),
+              "Need BackendType::CAIRO support");
+  RefPtr < DrawTarget > dt = Factory::CreateDrawTargetForData (
+      BackendType::CAIRO, data, IntSize (zoomedViewRect.width, zoomedViewRect.height), stride,
+      format);
+  if (!dt) {
+    ALOG_BRIDGE ("Error creating DrawTarget");
+    return NS_ERROR_FAILURE;
+  }
+  nsRefPtr < gfxContext > context = new gfxContext (dt);
+  context->SetMatrix (context->CurrentMatrix ().Scale(zoomFactor, zoomFactor));
+
+  rv = presShell->RenderDocument (r, renderDocFlags, bgColor, context);
+
+  if (is24bit) {
+    gfxUtils::ConvertBGRAtoRGBA (data, stride * zoomedViewRect.height);
+  }
+
+  LayerView::updateZoomedView(buffer);
+
+  NS_ENSURE_SUCCESS (rv, rv);
+  return NS_OK;
+}
+
 nsresult AndroidBridge::CaptureThumbnail(nsIDOMWindow *window, int32_t bufW, int32_t bufH, int32_t tabId, Object::Param buffer, bool &shouldStore)
 {
     nsresult rv;
     float scale = 1.0;
 
     if (!buffer)
         return NS_ERROR_FAILURE;
 
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -183,16 +183,17 @@ public:
     // us to use.  toolkit/xre/nsAndroidStartup.cpp calls
     // SetMainThread.
     bool SetMainThread(pthread_t thr);
 
     /* These are all implemented in Java */
     bool GetThreadNameJavaProfiling(uint32_t aThreadId, nsCString & aResult);
     bool GetFrameNameJavaProfiling(uint32_t aThreadId, uint32_t aSampleId, uint32_t aFrameId, nsCString & aResult);
 
+    nsresult CaptureZoomedView(nsIDOMWindow *window, nsIntRect zoomedViewRect, jni::Object::Param buffer, float zoomFactor);
     nsresult CaptureThumbnail(nsIDOMWindow *window, int32_t bufW, int32_t bufH, int32_t tabId, jni::Object::Param buffer, bool &shouldStore);
     void GetDisplayPort(bool aPageSizeUpdate, bool aIsBrowserContentDisplayed, int32_t tabId, nsIAndroidViewport* metrics, nsIAndroidDisplayport** displayPort);
     void ContentDocumentChanged();
     bool IsContentDocumentDisplayed();
 
     bool ProgressiveUpdateCallback(bool aHasPendingNewThebesContent, const LayerRect& aDisplayPort, float aDisplayResolution, bool aDrawingCritical,
                                    mozilla::ParentLayerPoint& aScrollOffset, mozilla::CSSToParentLayerScale& aZoom);
 
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -533,16 +533,24 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jo
 
         case THUMBNAIL: {
             mMetaState = jenv->GetIntField(jobj, jMetaStateField);
             ReadPointArray(mPoints, jenv, jPoints, 1);
             mByteBuffer = new RefCountedJavaObject(jenv, jenv->GetObjectField(jobj, jByteBufferField));
             break;
         }
 
+        case ZOOMEDVIEW: {
+            mX = jenv->GetDoubleField(jobj, jXField);
+            mMetaState = jenv->GetIntField(jobj, jMetaStateField);
+            ReadPointArray(mPoints, jenv, jPoints, 2);
+            mByteBuffer = new RefCountedJavaObject(jenv, jenv->GetObjectField(jobj, jByteBufferField));
+            break;
+        }
+
         case SCREENORIENTATION_CHANGED: {
             mScreenOrientation = jenv->GetShortField(jobj, jScreenOrientationField);
             break;
         }
 
         case COMPOSITOR_CREATE: {
             mWidth = jenv->GetIntField(jobj, jWidthField);
             mHeight = jenv->GetIntField(jobj, jHeightField);
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -741,16 +741,17 @@ public:
         PREFERENCES_GET = 40,
         PREFERENCES_REMOVE_OBSERVERS = 41,
         TELEMETRY_UI_SESSION_START = 42,
         TELEMETRY_UI_SESSION_STOP = 43,
         TELEMETRY_UI_EVENT = 44,
         GAMEPAD_ADDREMOVE = 45,
         GAMEPAD_DATA = 46,
         LONG_PRESS = 47,
+        ZOOMEDVIEW = 48,
         dummy_java_enum_list_end
     };
 
     enum {
         // Memory pressure levels. Keep these in sync with those in MemoryMonitor.java.
         MEMORY_PRESSURE_NONE = 0,
         MEMORY_PRESSURE_CLEANUP = 1,
         MEMORY_PRESSURE_LOW = 2,
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -975,16 +975,24 @@ constexpr char LayerView::name[];
 constexpr char LayerView::RegisterCompositorWrapper_t::name[];
 constexpr char LayerView::RegisterCompositorWrapper_t::signature[];
 
 mozilla::jni::Object::LocalRef LayerView::RegisterCompositorWrapper()
 {
     return mozilla::jni::Method<RegisterCompositorWrapper_t>::Call(nullptr, nullptr);
 }
 
+constexpr char LayerView::updateZoomedView_t::name[];
+constexpr char LayerView::updateZoomedView_t::signature[];
+
+void LayerView::updateZoomedView(mozilla::jni::Object::Param a0)
+{
+    return mozilla::jni::Method<updateZoomedView_t>::Call(nullptr, nullptr, a0);
+}
+
 constexpr char NativePanZoomController::name[];
 
 constexpr char NativePanZoomController::RequestContentRepaintWrapper_t::name[];
 constexpr char NativePanZoomController::RequestContentRepaintWrapper_t::signature[];
 
 void NativePanZoomController::RequestContentRepaintWrapper(float a0, float a1, float a2, float a3, float a4) const
 {
     return mozilla::jni::Method<RequestContentRepaintWrapper_t>::Call(this, nullptr, a0, a1, a2, a3, a4);
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -1929,16 +1929,31 @@ public:
                 "()Lorg/mozilla/gecko/gfx/GLController;";
         static const bool isStatic = true;
         static const bool isMultithreaded = true;
         static const mozilla::jni::ExceptionMode exceptionMode = mozilla::jni::ExceptionMode::ABORT;
     };
 
     static mozilla::jni::Object::LocalRef RegisterCompositorWrapper();
 
+public:
+    struct updateZoomedView_t {
+        typedef LayerView Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        static constexpr char name[] = "updateZoomedView";
+        static constexpr char signature[] =
+                "(Ljava/nio/ByteBuffer;)V";
+        static const bool isStatic = true;
+        static const bool isMultithreaded = true;
+        static const mozilla::jni::ExceptionMode exceptionMode = mozilla::jni::ExceptionMode::ABORT;
+    };
+
+    static void updateZoomedView(mozilla::jni::Object::Param);
+
 };
 
 class NativePanZoomController : public mozilla::jni::Class<NativePanZoomController> {
 
 public:
     typedef mozilla::jni::Ref<NativePanZoomController> Ref;
     typedef mozilla::jni::LocalRef<NativePanZoomController> LocalRef;
     typedef mozilla::jni::GlobalRef<NativePanZoomController> GlobalRef;
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -388,16 +388,43 @@ nsAppShell::ProcessNextNativeEvent(bool 
         int32_t tabId = curEvent->MetaState();
         const nsTArray<nsIntPoint>& points = curEvent->Points();
         RefCountedJavaObject* buffer = curEvent->ByteBuffer();
         nsRefPtr<ThumbnailRunnable> sr = new ThumbnailRunnable(mBrowserApp, tabId, points, buffer);
         MessageLoop::current()->PostIdleTask(FROM_HERE, NewRunnableMethod(sr.get(), &ThumbnailRunnable::Run));
         break;
     }
 
+    case AndroidGeckoEvent::ZOOMEDVIEW: {
+        if (!mBrowserApp)
+            break;
+        int32_t tabId = curEvent->MetaState();
+        const nsTArray<nsIntPoint>& points = curEvent->Points();
+        float scaleFactor = (float) curEvent->X();
+        nsRefPtr<RefCountedJavaObject> javaBuffer = curEvent->ByteBuffer();
+        const auto& mBuffer = jni::Object::Ref::From(javaBuffer->GetObject());
+
+        nsCOMPtr<nsIDOMWindow> domWindow;
+        nsCOMPtr<nsIBrowserTab> tab;
+        mBrowserApp->GetBrowserTab(tabId, getter_AddRefs(tab));
+        if (!tab) {
+            NS_ERROR("Can't find tab!");
+            break;
+        }
+        tab->GetWindow(getter_AddRefs(domWindow));
+        if (!domWindow) {
+            NS_ERROR("Can't find dom window!");
+            break;
+        }
+        NS_ASSERTION(points.Length() == 2, "ZoomedView event does not have enough coordinates");
+        nsIntRect r(points[0].x, points[0].y, points[1].x, points[1].y);
+        AndroidBridge::Bridge()->CaptureZoomedView(domWindow, r, mBuffer, scaleFactor);
+        break;
+    }
+
     case AndroidGeckoEvent::VIEWPORT:
     case AndroidGeckoEvent::BROADCAST: {
         if (curEvent->Characters().Length() == 0)
             break;
 
         nsCOMPtr<nsIObserverService> obsServ =
             mozilla::services::GetObserverService();