Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 14 Mar 2014 12:39:37 +0100
changeset 190848 e4880e7935a6a5df46e6b9cb7ede1b608c034498
parent 190847 e673315c346f9800b7f4c52b1fe2665e0d142e9c (current diff)
parent 190760 142911d6d9870866166926abbdc799db3a7d891b (diff)
child 190849 3b11907b6a972e53b90f16fc6362bcbdfbb3d16f
push idunknown
push userunknown
push dateunknown
milestone30.0a1
Merge mozilla-central to mozilla-inbound
mobile/android/base/resources/drawable-hdpi/fxaccount_icon.png
mobile/android/base/resources/drawable-mdpi/fxaccount_icon.png
--- 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="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="29a990ac0347c057ef6998cb42e4548768a18559"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="456499c44d1ef39b602ea02e9ed460b6aab85b44"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- 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="15d69a6789c638709911507f74d25c0425963636">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="29a990ac0347c057ef6998cb42e4548768a18559"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <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="a9e08b91e9cd1f0930f16cfc49ec72f63575d5fe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="29a990ac0347c057ef6998cb42e4548768a18559"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
--- 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="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="29a990ac0347c057ef6998cb42e4548768a18559"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="456499c44d1ef39b602ea02e9ed460b6aab85b44"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "remote": "", 
         "branch": "", 
         "revision": ""
     }, 
-    "revision": "04a13c212e02a36e590e5fd166af2f8e0abfbad7", 
+    "revision": "37b9db0c5bc096893c78468b1a3cf3d02962e231", 
     "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="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="29a990ac0347c057ef6998cb42e4548768a18559"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="29a990ac0347c057ef6998cb42e4548768a18559"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -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="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="29a990ac0347c057ef6998cb42e4548768a18559"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -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="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="29a990ac0347c057ef6998cb42e4548768a18559"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -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="15d69a6789c638709911507f74d25c0425963636">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="29a990ac0347c057ef6998cb42e4548768a18559"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <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="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="29a990ac0347c057ef6998cb42e4548768a18559"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1cef557f9e9a865d1bf49d99a8f1cca1f0f4f5c4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="b5151b89ff31e92dc44b466f15ad4909e73db248"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <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/browser.css
+++ b/browser/base/content/browser.css
@@ -1,14 +1,15 @@
 /* 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/. */
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
+@namespace svg url("http://www.w3.org/2000/svg");
 
 #main-window:not([chromehidden~="toolbar"]) {
 %ifdef XP_MACOSX
   min-width: 335px;
 %else
   min-width: 300px;
 %endif
 }
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -399,24 +399,19 @@
     <!-- Sync Panel -->
     <panel id="sync-start-panel" class="sync-panel" type="arrow" hidden="true"
            noautofocus="true" onclick="this.hidePopup();"
            flip="slide">
       <hbox class="sync-panel-outer">
         <image class="sync-panel-icon"/>
         <vbox class="sync-panel-inner">
           <description id="sync-start-panel-title"
-                       value="&syncStartPanel.heading;"/>
-          <description id="sync-start-panel-subtitle">
-#ifdef XP_UNIX
-            &syncStartPanel.subTitleUnix;
-#else
-            &syncStartPanel.subTitle;
-#endif
-          </description>
+                       value="&syncStartPanel2.heading;"/>
+          <description id="sync-start-panel-subtitle"
+                       value="&syncStartPanel2.subTitle;"/>
         </vbox>
       </hbox>
     </panel>
 
     <!-- Sync Error Panel -->
     <panel id="sync-error-panel" class="sync-panel" type="arrow" hidden="true"
            noautofocus="true" onclick="this.hidePopup();"
            flip="slide">
--- a/browser/base/content/highlighter.css
+++ b/browser/base/content/highlighter.css
@@ -1,35 +1,25 @@
 /* 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/. */
 
 .highlighter-container {
   pointer-events: none;
 }
 
-.highlighter-outline-container {
-  overflow: hidden;
-  position: relative;
-}
-
-.highlighter-outline {
-  position: absolute;
-}
-
-.highlighter-outline[hidden] {
-  opacity: 0;
-  pointer-events: none;
-  display: -moz-box;
-}
-
-.highlighter-outline:not([disable-transitions]) {
-  transition-property: opacity, top, left, width, height;
-  transition-duration: 0.1s;
-  transition-timing-function: linear;
+/*
+ * Box model highlighter
+ */
+svg|svg.box-model-root[hidden],
+svg|line.box-model-guide-top[hidden],
+svg|line.box-model-guide-right[hidden],
+svg|line.box-model-guide-left[hidden],
+svg|line.box-model-guide-bottom[hidden] {
+  display: none;
 }
 
 /*
  * Node Infobar
  */
 .highlighter-nodeinfobar-container {
   position: relative;
 }
@@ -40,23 +30,16 @@
 }
 
 .highlighter-nodeinfobar-positioner[hidden] {
   opacity: 0;
   pointer-events: none;
   display: -moz-box;
 }
 
-.highlighter-nodeinfobar-positioner:not([disable-transitions]),
-.highlighter-nodeinfobar-positioner[disable-transitions][force-transitions] {
-  transition-property: transform, opacity, top, left;
-  transition-duration: 0.1s;
-  transition-timing-function: linear;
-}
-
 .highlighter-nodeinfobar-text {
   overflow: hidden;
   white-space: nowrap;
   text-overflow: ellipsis;
   direction: ltr;
 }
 
 html|*.highlighter-nodeinfobar-id,
--- a/browser/base/content/socialmarks.xml
+++ b/browser/base/content/socialmarks.xml
@@ -215,20 +215,21 @@
                               CustomizableUI.AREA_PANEL);
         } else {
           let anchor = document.getAnonymousElementByAttribute(this, "class", "toolbarbutton-icon");
           panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
           this.setAttribute("open", "true");
         }
         if (aResetOnClose) {
           let evName = inMenuPanel ? "ViewHiding": "popuphidden";
-          panel.addEventListener(evName, function _hidden() {
+          let _hidden = () => {
             panel.removeEventListener(evName, _hidden);
             this.update();
-          }.bind(this), false);
+          };
+          panel.addEventListener(evName, _hidden, false);
         }
          ]]></body>
        </method>
 
       <method name="markCurrentPage">
         <parameter name="aOpenPanel"/>
         <body><![CDATA[
         // we always set the src on click if it has not been set for this tab,
@@ -277,22 +278,23 @@
         // showing as well as after load
         let sizeSocialPanelToContent = Cu.import("resource:///modules/Social.jsm", {}).sizeSocialPanelToContent;
         if (!this._loading && this.contentDocument &&
             this.contentDocument.readyState == "complete") {
           this.dispatchPanelEvent("socialFrameShow");
           if (!this.inMenuPanel)
             sizeSocialPanelToContent(this.panel, this.content);
         } else {
-          this.content.addEventListener("load", function panelBrowserOnload(e) {
+          let panelBrowserOnload = (e) => {
             this.content.removeEventListener("load", panelBrowserOnload, true);
             this.dispatchPanelEvent("socialFrameShow");
             if (!this.inMenuPanel)
               sizeSocialPanelToContent(this.panel, this.content);
-          }.bind(this), true);
+          };
+          this.content.addEventListener("load", panelBrowserOnload, true);
         }
         ]]></body>
       </method>
 
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body><![CDATA[
         if (aEvent.eventPhase != aEvent.BUBBLING_PHASE)
--- a/browser/base/content/test/general/domplate_test.js
+++ b/browser/base/content/test/general/domplate_test.js
@@ -41,11 +41,11 @@ function test()
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
 
-  content.location = "data:text/html,basic domplate tests";
+  content.location = "data:text/html;charset=utf-8,domplate_test.js";
 }
 
--- a/browser/devtools/app-manager/test/head.js
+++ b/browser/devtools/app-manager/test/head.js
@@ -14,16 +14,21 @@ const {AppProjects} = require("devtools/
 
 const APP_MANAGER_URL = "about:app-manager";
 const TEST_BASE =
   "chrome://mochitests/content/browser/browser/devtools/app-manager/test/";
 const HOSTED_APP_MANIFEST = TEST_BASE + "hosted_app.manifest";
 
 const PACKAGED_APP_DIR_PATH = getTestFilePath(".");
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
 function addTab(url, targetWindow = window) {
   info("Adding tab: " + url);
 
   let deferred = promise.defer();
   let targetBrowser = targetWindow.gBrowser;
 
   targetWindow.focus();
   let tab = targetBrowser.selectedTab = targetBrowser.addTab(url);
--- a/browser/devtools/commandline/test/head.js
+++ b/browser/devtools/commandline/test/head.js
@@ -5,16 +5,21 @@
 const TEST_BASE_HTTP = "http://example.com/browser/browser/devtools/commandline/test/";
 const TEST_BASE_HTTPS = "https://example.com/browser/browser/devtools/commandline/test/";
 
 // Import the GCLI test helper
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "/helpers.js", this);
 Services.scriptloader.loadSubScript(testDir + "/mockCommands.js", this);
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
 function whenDelayedStartupFinished(aWindow, aCallback) {
   Services.obs.addObserver(function observer(aSubject, aTopic) {
     if (aWindow == aSubject) {
       Services.obs.removeObserver(observer, aTopic);
       executeSoon(aCallback);
     }
   }, "browser-delayed-startup-finished", false);
 }
--- a/browser/devtools/debugger/test/browser_dbg_listtabs-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_listtabs-02.js
@@ -40,17 +40,17 @@ function test() {
     .then(addTabC)
     .then(testTabC)
     .then(removeTabC)
     .then(testNewWindow)
     .then(removeNewWindow)
     .then(testWindowClosed)
     .then(removeTabB)
     .then(checkSingleTab)
-    .then(finish);
+    .then(finishUp);
 }
 
 function checkSingleTab() {
   return gTabList.getList().then(aTabActors => {
     is(aTabActors.length, 1, "initial tab list: contains initial tab");
     gFirstActor = aTabActors[0];
     is(gFirstActor.url, "about:blank", "initial tab list: initial tab URL is 'about:blank'");
     is(gFirstActor.title, "New Tab", "initial tab list: initial tab title is 'New Tab'");
@@ -203,8 +203,13 @@ function removeTabB() {
 
     // Let the actor's TabClose handler finish first.
     executeSoon(deferred.resolve);
   }, false);
 
   removeTab(gTabB);
   return deferred.promise;
 }
+
+function finishUp() {
+  gTabList = gFirstActor = gActorA = gTabA = gTabB = gTabC = gNewWindow = null;
+  finish();
+}
--- a/browser/devtools/debugger/test/browser_dbg_variables-view-frame-with.js
+++ b/browser/devtools/debugger/test/browser_dbg_variables-view-frame-with.js
@@ -202,9 +202,10 @@ function testFunctionScope() {
     "Should have the right token class for 'foo'.");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
+  gVariables = null;
 });
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -22,16 +22,21 @@ let { BrowserToolboxProcess } = Cu.impor
 let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
 let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
 let { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
 let TargetFactory = devtools.TargetFactory;
 let Toolbox = devtools.Toolbox;
 
 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/";
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 registerCleanupFunction(function() {
   info("finish() was called, cleaning up...");
   Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
 
   // Properly shut down the server to avoid memory leaks.
--- a/browser/devtools/fontinspector/test/browser_fontinspector.js
+++ b/browser/devtools/fontinspector/test/browser_fontinspector.js
@@ -11,16 +11,21 @@ let DOMUtils = Cc["@mozilla.org/inspecto
 function test() {
   waitForExplicitFinish();
 
   let doc;
   let view;
   let viewDoc;
   let inspector;
 
+  gDevTools.testing = true;
+  SimpleTest.registerCleanupFunction(() => {
+    gDevTools.testing = false;
+  });
+
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     doc = content.document;
     waitForFocus(setupTest, content);
   }, true);
 
   content.location = "http://mochi.test:8888/browser/browser/devtools/fontinspector/test/browser_fontinspector.html";
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -27,24 +27,49 @@ const MAX_ORDINAL = 99;
 this.DevTools = function DevTools() {
   this._tools = new Map();     // Map<toolId, tool>
   this._toolboxes = new Map(); // Map<target, toolbox>
 
   // destroy() is an observer's handler so we need to preserve context.
   this.destroy = this.destroy.bind(this);
   this._teardown = this._teardown.bind(this);
 
+  this._testing = false;
+
   EventEmitter.decorate(this);
 
   Services.obs.addObserver(this._teardown, "devtools-unloaded", false);
   Services.obs.addObserver(this.destroy, "quit-application", false);
 }
 
 DevTools.prototype = {
   /**
+   * When the testing flag is set we take appropriate action to prevent race
+   * conditions in our testing environment. This means setting
+   * dom.send_after_paint_to_content to false to prevent infinite MozAfterPaint
+   * loops and not autohiding the highlighter.
+   */
+  get testing() {
+    return this._testing;
+  },
+
+  set testing(state) {
+    this._testing = state;
+
+    if (state) {
+      // dom.send_after_paint_to_content is set to true (non-default) in
+      // testing/profiles/prefs_general.js so lets set it to the same as it is
+      // in a default browser profile for the duration of the test.
+      Services.prefs.setBoolPref("dom.send_after_paint_to_content", false);
+    } else {
+      Services.prefs.setBoolPref("dom.send_after_paint_to_content", true);
+    }
+  },
+
+  /**
    * Register a new developer tool.
    *
    * A definition is a light object that holds different information about a
    * developer tool. This object is not supposed to have any operational code.
    * See it as a "manifest".
    * The only actual code lives in the build() function, which will be used to
    * start an instance of this tool.
    *
--- a/browser/devtools/framework/selection.js
+++ b/browser/devtools/framework/selection.js
@@ -155,30 +155,32 @@ Selection.prototype = {
     if (this.isNode()) {
       return this.node.ownerDocument;
     }
     return null;
   },
 
   setNodeFront: function(value, reason="unknown") {
     this.reason = reason;
-    if (value !== this._nodeFront) {
-      let rawValue = null;
-      if (value && value.isLocal_toBeDeprecated()) {
-        rawValue = value.rawNode();
-      }
-      this.emit("before-new-node", rawValue, reason);
-      this.emit("before-new-node-front", value, reason);
-      let previousNode = this._node;
-      let previousFront = this._nodeFront;
-      this._node = rawValue;
-      this._nodeFront = value;
-      this.emit("new-node", previousNode, this.reason);
-      this.emit("new-node-front", value, this.reason);
+
+    // We used to return here if the node had not changed but we now need to
+    // set the node even if it is already set otherwise it is not possible to
+    // e.g. highlight the same node twice.
+    let rawValue = null;
+    if (value && value.isLocal_toBeDeprecated()) {
+      rawValue = value.rawNode();
     }
+    this.emit("before-new-node", rawValue, reason);
+    this.emit("before-new-node-front", value, reason);
+    let previousNode = this._node;
+    let previousFront = this._nodeFront;
+    this._node = rawValue;
+    this._nodeFront = value;
+    this.emit("new-node", previousNode, this.reason);
+    this.emit("new-node-front", value, this.reason);
   },
 
   get documentFront() {
     return this._walker.document(this._nodeFront);
   },
 
   get nodeFront() {
     return this._nodeFront;
--- a/browser/devtools/framework/test/head.js
+++ b/browser/devtools/framework/test/head.js
@@ -8,16 +8,21 @@ let tempScope = {};
 Components.utils.import("resource://gre/modules/devtools/Console.jsm", tempScope);
 let console = tempScope.console;
 Components.utils.import("resource://gre/modules/commonjs/sdk/core/promise.js", tempScope);
 let promise = tempScope.Promise;
 
 let {devtools} = Components.utils.import("resource://gre/modules/devtools/Loader.jsm", {});
 let TargetFactory = devtools.TargetFactory;
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
 /**
  * Open a new tab at a URL and call a callback on load
  */
 function addTab(aURL, aCallback)
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -68,16 +68,18 @@ function Toolbox(target, selectedTool, h
   this._telemetry = new Telemetry();
 
   this._toolRegistered = this._toolRegistered.bind(this);
   this._toolUnregistered = this._toolUnregistered.bind(this);
   this._refreshHostTitle = this._refreshHostTitle.bind(this);
   this._splitConsoleOnKeypress = this._splitConsoleOnKeypress.bind(this)
   this.destroy = this.destroy.bind(this);
   this.highlighterUtils = new ToolboxHighlighterUtils(this);
+  this._highlighterReady = this._highlighterReady.bind(this);
+  this._highlighterHidden = this._highlighterHidden.bind(this);
 
   this._target.on("close", this.destroy);
 
   if (!hostType) {
     hostType = Services.prefs.getCharPref(this._prefs.LAST_HOST);
   }
   if (!selectedTool) {
     selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
@@ -1092,55 +1094,72 @@ Toolbox.prototype = {
    * Returns a promise that resolves when the fronts are initialized
    */
   initInspector: function() {
     if (!this._initInspector) {
       this._initInspector = Task.spawn(function*() {
         this._inspector = InspectorFront(this._target.client, this._target.form);
         this._walker = yield this._inspector.getWalker();
         this._selection = new Selection(this._walker);
+
         if (this.highlighterUtils.isRemoteHighlightable) {
-          this._highlighter = yield this._inspector.getHighlighter();
+          let autohide = !gDevTools.testing;
+
+          this.walker.on("highlighter-ready", this._highlighterReady);
+          this.walker.on("highlighter-hide", this._highlighterHidden);
+
+          this._highlighter = yield this._inspector.getHighlighter(autohide);
         }
       }.bind(this));
     }
     return this._initInspector;
   },
 
   /**
    * Destroy the inspector/walker/selection fronts
    * Returns a promise that resolves when the fronts are destroyed
    */
   destroyInspector: function() {
+    if (this._destroying) {
+      return this._destroying;
+    }
+
     if (!this._inspector) {
       return promise.resolve();
     }
 
     let outstanding = () => {
       return Task.spawn(function*() {
         yield this.highlighterUtils.stopPicker();
         yield this._inspector.destroy();
         if (this._highlighter) {
           yield this._highlighter.destroy();
         }
         if (this._selection) {
           this._selection.destroy();
         }
 
+        if (this.walker) {
+          this.walker.off("highlighter-ready", this._highlighterReady);
+          this.walker.off("highlighter-hide", this._highlighterHidden);
+        }
+
         this._inspector = null;
         this._highlighter = null;
         this._selection = null;
         this._walker = null;
       }.bind(this));
     };
 
     // Releasing the walker (if it has been created)
     // This can fail, but in any case, we want to continue destroying the
     // inspector/highlighter/selection
-    let walker = this._walker ? this._walker.release() : promise.resolve();
+    let walker = (this._destroying = this._walker) ?
+                 this._walker.release() :
+                 promise.resolve();
     return walker.then(outstanding, outstanding);
   },
 
   /**
    * Get the toolbox's notification box
    *
    * @return The notification box element.
    */
@@ -1219,17 +1238,25 @@ Toolbox.prototype = {
       return target.destroy();
     }).then(() => {
       this.emit("destroyed");
       // Free _host after the call to destroyed in order to let a chance
       // to destroyed listeners to still query toolbox attributes
       this._host = null;
       this._toolPanels.clear();
     }).then(null, console.error);
-  }
+  },
+
+  _highlighterReady: function() {
+    this.emit("highlighter-ready");
+  },
+
+  _highlighterHidden: function() {
+    this.emit("highlighter-hide");
+  },
 };
 
 /**
  * The ToolboxHighlighterUtils is what you should use for anything related to
  * node highlighting and picking.
  * It encapsulates the logic to connecting to the HighlighterActor.
  */
 function ToolboxHighlighterUtils(toolbox) {
@@ -1279,39 +1306,40 @@ ToolboxHighlighterUtils.prototype = {
   startPicker: function() {
     if (this._isPicking) {
       return promise.resolve();
     }
 
     let deferred = promise.defer();
 
     let done = () => {
+      this._isPicking = true;
       this.toolbox.emit("picker-started");
       this.toolbox.on("select", this.stopPicker);
       deferred.resolve();
     };
 
     promise.all([
       this.toolbox.initInspector(),
       this.toolbox.selectTool("inspector")
     ]).then(() => {
-      this._isPicking = true;
       this.toolbox._pickerButton.setAttribute("checked", "true");
 
       if (this.isRemoteHighlightable) {
-        this.toolbox.highlighter.pick().then(done);
-
         this.toolbox.walker.on("picker-node-hovered", this._onPickerNodeHovered);
         this.toolbox.walker.on("picker-node-picked", this._onPickerNodePicked);
+
+        this.toolbox.highlighter.pick().then(done);
       } else {
-        this.toolbox.walker.pick().then(node => {
-          this.toolbox.selection.setNodeFront(node, "picker-node-picked");
-          this.stopPicker();
+        return this.toolbox.walker.pick().then(node => {
+          this.toolbox.selection.setNodeFront(node, "picker-node-picked").then(() => {
+            this.stopPicker();
+            done();
+          });
         });
-        done();
       }
     });
 
     return deferred.promise;
   },
 
   /**
    * Stop the element picker
@@ -1399,21 +1427,26 @@ ToolboxHighlighterUtils.prototype = {
       return this.toolbox.walker.getNodeActorFromObjectActor(grip.actor);
     });
   },
 
   /**
    * Hide the highlighter.
    * @return a promise that resolves when the highlighter is hidden
    */
-  unhighlight: function() {
+  unhighlight: function(forceHide=false) {
     if (this.isRemoteHighlightable) {
       // If the remote highlighter exists on the target, use it
       return this.toolbox.initInspector().then(() => {
-        return this.toolbox.highlighter.hideBoxModel();
+        let autohide = forceHide || !gDevTools.testing;
+
+        if (autohide) {
+          return this.toolbox.highlighter.hideBoxModel();
+        }
+        return promise.resolve();
       });
     } else {
       // If not, no need to unhighlight as the older highlight method uses a
       // setTimeout to hide itself
       return promise.resolve();
     }
   }
 };
--- a/browser/devtools/inspector/breadcrumbs.js
+++ b/browser/devtools/inspector/breadcrumbs.js
@@ -694,13 +694,13 @@ HTMLBreadcrumbs.prototype = {
       this.scroll();
       this.inspector.emit("breadcrumbs-updated", this.selection.nodeFront);
       doneUpdating();
     }).then(null, err => {
       doneUpdating(this.selection.nodeFront);
       this.selectionGuardEnd(err);
     });
   }
-}
+};
 
 XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
--- a/browser/devtools/inspector/test/browser_inspector_basic_highlighter.js
+++ b/browser/devtools/inspector/test/browser_inspector_basic_highlighter.js
@@ -13,72 +13,50 @@ function test() {
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     doc = content.document;
     waitForFocus(setupTest, content);
   }, true);
 
-  content.location = "data:text/html,<h1>foo</h1><h2>bar</h2>";
+  content.location = "data:text/html;charset=utf-8,<h1>foo</h1><span>bar</span>";
 
   function setupTest() {
     openInspector((aInspector, aToolbox) => {
       toolbox = aToolbox;
       inspector = aInspector;
-      inspector.selection.setNode(doc.querySelector("h2"), null);
-      inspector.once("inspector-updated", runTests);
+      inspector.selection.setNode(doc.querySelector("span"), "test");
+      inspector.toolbox.once("highlighter-ready", runTests);
     });
   }
 
-  function runTests(aInspector) {
-    getHighlighterOutline().setAttribute("disable-transitions", "true");
+  function runTests() {
     Task.spawn(function() {
       yield hoverH1InMarkupView();
       yield assertH1Highlighted();
-      yield mouseLeaveMarkupView();
-      yield assertNoNodeHighlighted();
 
       finishUp();
     }).then(null, Cu.reportError);
   }
 
   function hoverH1InMarkupView() {
     let deferred = promise.defer();
+    let container = getContainerForRawNode(inspector.markup, doc.querySelector("h1"));
 
-    let container = getContainerForRawNode(inspector.markup, doc.querySelector("h1"));
-    EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"},
-      inspector.markup.doc.defaultView);
-    inspector.toolbox.once("node-highlight", deferred.resolve);
+    inspector.toolbox.once("highlighter-ready", deferred.resolve);
+    EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousemove"},
+                                       inspector.markup.doc.defaultView);
 
     return deferred.promise;
   }
 
   function assertH1Highlighted() {
     ok(isHighlighting(), "The highlighter is shown on a markup container hover");
     is(getHighlitNode(), doc.querySelector("h1"), "The highlighter highlights the right node");
-    return promise.resolve();
-  }
-
-  function mouseLeaveMarkupView() {
-    let deferred = promise.defer();
-
-    // Find another element to mouseover over in order to leave the markup-view
-    let btn = toolbox.doc.querySelector(".toolbox-dock-button");
-
-    EventUtils.synthesizeMouse(btn, 2, 2, {type: "mousemove"},
-      toolbox.doc.defaultView);
-    executeSoon(deferred.resolve);
-
-    return deferred.promise;
-  }
-
-  function assertNoNodeHighlighted() {
-    ok(!isHighlighting(), "After the mouse left the markup view, the highlighter is hidden");
-    return promise.resolve();
   }
 
   function finishUp() {
     inspector = doc = toolbox = null;
     gBrowser.removeCurrentTab();
     finish();
   }
 }
--- a/browser/devtools/inspector/test/browser_inspector_breadcrumbs.js
+++ b/browser/devtools/inspector/test/browser_inspector_breadcrumbs.js
@@ -86,13 +86,13 @@ function test()
 
     let checkedButton = container.querySelector("button[checked]");
     let labelId = checkedButton.querySelector(".breadcrumbs-widget-item-id");
     let id = inspector.selection.node.id;
     is(labelId.textContent, "#" + id, "Node " + cursor + ": selection matches");
   }
 
   function finishUp() {
-    doc = nodes = null;
+    doc = nodes = inspector = null;
     gBrowser.removeCurrentTab();
     finish();
   }
 }
--- a/browser/devtools/inspector/test/browser_inspector_bug_650804_search.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_650804_search.js
@@ -47,17 +47,18 @@ function test()
   ];
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     waitForFocus(setupTest, content);
   }, true);
 
-  content.location = "http://mochi.test:8888/browser/browser/devtools/inspector/test/browser_inspector_bug_650804_search.html";
+  content.location = "http://mochi.test:8888/browser/browser/devtools/" +
+                     "inspector/test/browser_inspector_bug_650804_search.html";
 
   function $(id) {
     if (id == null) return null;
     return content.document.getElementById(id);
   }
 
   function setupTest()
   {
--- a/browser/devtools/inspector/test/browser_inspector_bug_674871.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_674871.js
@@ -45,51 +45,54 @@ function test()
     iframeNode = doc.querySelector("iframe");
     iframeBodyNode = iframeNode.contentDocument.querySelector("body");
     ok(iframeNode, "we have the iframe node");
     ok(iframeBodyNode, "we have the body node");
     openInspector(aInspector => {
       inspector = aInspector;
       // Make sure the highlighter is shown so we can disable transitions
       inspector.toolbox.highlighter.showBoxModel(getNodeFront(doc.body)).then(() => {
-        getHighlighterOutline().setAttribute("disable-transitions", "true");
         runTests();
       });
     });
   }
 
   function runTests()
   {
     inspector.toolbox.highlighterUtils.startPicker().then(() => {
       moveMouseOver(iframeNode, 1, 1, isTheIframeHighlighted);
     });
   }
 
   function isTheIframeHighlighted()
   {
-    let outlineRect = getHighlighterOutlineRect();
-    let iframeRect = iframeNode.getBoundingClientRect();
-    for (let dim of ["width", "height", "top", "left"]) {
-      is(Math.floor(outlineRect[dim]), Math.floor(iframeRect[dim]),
-         "Outline dimension is correct " + outlineRect[dim]);
-    }
+    let {p1, p2, p3, p4} = getBoxModelStatus().border.points;
+    let {top, right, bottom, left} = iframeNode.getBoundingClientRect();
+
+    is(top, p1.y, "iframeRect.top === boxModelStatus.p1.y");
+    is(top, p2.y, "iframeRect.top === boxModelStatus.p2.y");
+    is(right, p2.x, "iframeRect.right === boxModelStatus.p2.x");
+    is(right, p3.x, "iframeRect.right === boxModelStatus.p3.x");
+    is(bottom, p3.y, "iframeRect.bottom === boxModelStatus.p3.y");
+    is(bottom, p4.y, "iframeRect.bottom === boxModelStatus.p4.y");
+    is(left, p1.x, "iframeRect.left === boxModelStatus.p1.x");
+    is(left, p4.x, "iframeRect.left === boxModelStatus.p4.x");
 
     iframeNode.style.marginBottom = doc.defaultView.innerHeight + "px";
     doc.defaultView.scrollBy(0, 40);
 
     moveMouseOver(iframeNode, 40, 40, isTheIframeContentHighlighted);
   }
 
   function isTheIframeContentHighlighted()
   {
     is(getHighlitNode(), iframeBodyNode, "highlighter shows the right node");
 
-    // 184 == 200 + 11(border) + 13(padding) - 40(scroll)
-    let outlineRect = getHighlighterOutlineRect();
-    is(outlineRect.height, 184, "highlighter height");
+    let outlineRect = getSimpleBorderRect();
+    is(outlineRect.height, 200, "highlighter height");
 
     inspector.toolbox.highlighterUtils.stopPicker().then(() => {
       let target = TargetFactory.forTab(gBrowser.selectedTab);
       gDevTools.closeToolbox(target);
       finishUp();
     });
   }
 
--- a/browser/devtools/inspector/test/browser_inspector_bug_699308_iframe_navigation.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_699308_iframe_navigation.js
@@ -13,49 +13,44 @@ function test() {
       runInspectorTests();
     });
   }
 
   function showHighlighter(cb) {
     inspector.toolbox.highlighterUtils.startPicker().then(() => {
       EventUtils.synthesizeMouse(content.document.body, 1, 1,
         {type: "mousemove"}, content);
-      inspector.toolbox.once("picker-node-hovered", () => {
-        executeSoon(() => {
-          getHighlighterOutline().setAttribute("disable-transitions", "true");
-          cb();
-        });
-      });
+      inspector.toolbox.once("highlighter-ready", cb);
     });
   }
 
   function runInspectorTests() {
     iframe = content.document.querySelector("iframe");
     ok(iframe, "found the iframe element");
 
     showHighlighter(() => {
       ok(isHighlighting(), "Inspector is highlighting");
 
       iframe.addEventListener("load", onIframeLoad, false);
-
-      executeSoon(function() {
+      executeSoon(() => {
         iframe.contentWindow.location = "javascript:location.reload()";
       });
     });
   }
 
   function onIframeLoad() {
     if (++iframeLoads != 2) {
       executeSoon(function() {
         iframe.contentWindow.location = "javascript:location.reload()";
       });
       return;
     }
 
     iframe.removeEventListener("load", onIframeLoad, false);
+    info("Finished reloading iframe and inspector updated");
 
     ok(isHighlighting(), "Inspector is highlighting after iframe nav");
 
     checksAfterLoads = true;
 
     finishTest();
   }
 
@@ -73,11 +68,12 @@ function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad() {
     gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true);
     waitForFocus(startTest, content);
   }, true);
 
-  content.location = "data:text/html,<p>bug 699308 - test iframe navigation" +
-    "<iframe src='data:text/html,hello world'></iframe>";
+  content.location = "data:text/html;charset=utf-8," +
+                     "<p>bug 699308 - test iframe navigation</p>" +
+                     "<iframe src='data:text/html;charset=utf-8,hello world'></iframe>";
 }
--- a/browser/devtools/inspector/test/browser_inspector_bug_958456_highlight_comments.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_958456_highlight_comments.js
@@ -72,17 +72,16 @@ function finishTest() {
   finish();
 }
 
 function prepareHighlighter() {
   // Make sure the highlighter doesn't have transitions enabled
   let deferred = promise.defer();
   inspector.selection.setNode(doc.querySelector("p"), null);
   inspector.once("inspector-updated", () => {
-    getHighlighterOutline().setAttribute("disable-transitions", "true");
     deferred.resolve();
   });
   return deferred.promise;
 }
 
 function hoverContainer(container) {
   let deferred = promise.defer();
   EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"},
--- a/browser/devtools/inspector/test/browser_inspector_changes.js
+++ b/browser/devtools/inspector/test/browser_inspector_changes.js
@@ -145,10 +145,10 @@ function test() {
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
 
-  content.location = "data:text/html,basic tests for inspector";
+  content.location = "data:text/html;charset=utf-8,browser_inspector_changes.js";
 }
--- a/browser/devtools/inspector/test/browser_inspector_highlighter.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter.js
@@ -46,71 +46,65 @@ function createDocument() {
   doc.body.appendChild(div);
   doc.body.appendChild(div2);
   doc.body.appendChild(div3);
 
   openInspector(aInspector => {
     inspector = aInspector;
     inspector.selection.setNode(div, null);
     inspector.once("inspector-updated", () => {
-      getHighlighterOutline().setAttribute("disable-transitions", "true");
       inspector.toolbox.highlighterUtils.startPicker().then(testMouseOverH1Highlights);
     });
   });
 }
 
 function testMouseOverH1Highlights() {
-  inspector.toolbox.once("picker-node-hovered", () => {
+  inspector.toolbox.once("highlighter-ready", () => {
     ok(isHighlighting(), "Highlighter is shown");
     is(getHighlitNode(), h1, "Highlighter's outline correspond to the selected node");
-    testOutlineDimensions();
+    testBoxModelDimensions();
   });
 
   EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
 }
 
-function testOutlineDimensions() {
+function testBoxModelDimensions() {
   let h1Dims = h1.getBoundingClientRect();
-  let h1Width = h1Dims.width;
-  let h1Height = h1Dims.height;
+  let h1Width = Math.ceil(h1Dims.width);
+  let h1Height = Math.ceil(h1Dims.height);
 
-  let outlineDims = getHighlighterOutlineRect();
-  let outlineWidth = outlineDims.width;
-  let outlineHeight = outlineDims.height;
+  let outlineDims = getSimpleBorderRect();
+  let outlineWidth = Math.ceil(outlineDims.width);
+  let outlineHeight = Math.ceil(outlineDims.height);
 
   // Disabled due to bug 716245
   is(outlineWidth, h1Width, "outline width matches dimensions of element (no zoom)");
   is(outlineHeight, h1Height, "outline height matches dimensions of element (no zoom)");
 
   // zoom the page by a factor of 2
   let contentViewer = gBrowser.selectedBrowser.docShell.contentViewer
                              .QueryInterface(Ci.nsIMarkupDocumentViewer);
   contentViewer.fullZoom = 2;
 
-  // We wait at least 500ms to make sure the highlighter is not "mutting" the
-  // resize event
-
-  window.setTimeout(function() {
-    // simulate the zoomed dimensions of the div element
-    let h1Dims = h1.getBoundingClientRect();
-    // There seems to be some very minor differences in the floats, so let's
-    // floor the values
-    let h1Width = Math.floor(h1Dims.width * contentViewer.fullZoom);
-    let h1Height = Math.floor(h1Dims.height * contentViewer.fullZoom);
+  // simulate the zoomed dimensions of the div element
+  let h1Dims = h1.getBoundingClientRect();
+  // There seems to be some very minor differences in the floats, so let's
+  // floor the values
+  let h1Width = Math.floor(h1Dims.width * contentViewer.fullZoom);
+  let h1Height = Math.floor(h1Dims.height * contentViewer.fullZoom);
 
-    let outlineDims = getHighlighterOutlineRect();
-    let outlineWidth = Math.floor(outlineDims.width);
-    let outlineHeight = Math.floor(outlineDims.height);
+  let outlineDims = getSimpleBorderRect();
+  let outlineWidth = Math.floor(outlineDims.width);
+  let outlineHeight = Math.floor(outlineDims.height);
 
-    // Disabled due to bug 716245
-    is(outlineWidth, h1Width, "outline width matches dimensions of element (zoomed)");
-    is(outlineHeight, h1Height, "outline height matches dimensions of element (zoomed)");
+  is(outlineWidth, h1Width, "outline width matches dimensions of element (zoomed)");
 
-    executeSoon(finishUp);
-  }, 500);
+  is(outlineHeight, h1Height, "outline height matches dimensions of element (zoomed)");
+
+  executeSoon(finishUp);
 }
 
 function finishUp() {
   inspector.toolbox.highlighterUtils.stopPicker().then(() => {
     doc = h1 = inspector = null;
     let target = TargetFactory.forTab(gBrowser.selectedTab);
     gDevTools.closeToolbox(target);
     gBrowser.removeCurrentTab();
@@ -122,10 +116,10 @@ function test() {
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
 
-  content.location = "data:text/html,basic tests for inspector";
+  content.location = "data:text/html;charset=utf-8,browser_inspector_highlighter.js";
 }
--- a/browser/devtools/inspector/test/browser_inspector_iframeTest.js
+++ b/browser/devtools/inspector/test/browser_inspector_iframeTest.js
@@ -26,54 +26,54 @@ function createDocument() {
     iframe2 = iframe1.contentDocument.createElement('iframe');
 
     iframe2.addEventListener('load', function () {
       iframe2.removeEventListener("load", arguments.callee, false);
 
       div2 = iframe2.contentDocument.createElement('div');
       div2.textContent = 'nested div';
       iframe2.contentDocument.body.appendChild(div2);
+
       // Open the inspector, start the picker mode, and start the tests
       openInspector(aInspector => {
         inspector = aInspector;
-        inspector.toolbox.highlighterUtils.startPicker().then(runTests);
+        inspector.once("inspector-updated", () => {
+          inspector.toolbox.highlighterUtils.startPicker().then(runTests);
+        });
       });
     }, false);
 
     iframe2.src = 'data:text/html,nested iframe';
     iframe1.contentDocument.body.appendChild(iframe2);
   }, false);
 
   iframe1.src = 'data:text/html,little iframe';
   doc.body.appendChild(iframe1);
 }
 
 function moveMouseOver(aElement, cb) {
-  inspector.toolbox.once("picker-node-hovered", () => {
-    executeSoon(cb);
-  });
+  inspector.toolbox.once("picker-node-hovered", cb);
   EventUtils.synthesizeMouseAtCenter(aElement, {type: "mousemove"},
     aElement.ownerDocument.defaultView);
 }
 
 function runTests() {
   testDiv1Highlighter();
 }
 
 function testDiv1Highlighter() {
   moveMouseOver(div1, () => {
-    getHighlighterOutline().setAttribute("disable-transitions", "true");
-    is(getHighlitNode(), div1, "highlighter matches selection");
+    is(getHighlitNode(), div1, "highlighter matches selection of div1");
     testDiv2Highlighter();
   });
 }
 
 function testDiv2Highlighter() {
   moveMouseOver(div2, () => {
-    is(getHighlitNode(), div2, "highlighter matches selection");
+    is(getHighlitNode(), div2, "highlighter matches selection of div2");
     selectRoot();
   });
 }
 
 function selectRoot() {
   // Select the root document element to clear the breadcrumbs.
   inspector.selection.setNode(doc.documentElement, null);
   inspector.once("inspector-updated", selectIframe);
--- a/browser/devtools/inspector/test/browser_inspector_initialization.js
+++ b/browser/devtools/inspector/test/browser_inspector_initialization.js
@@ -132,10 +132,10 @@ function test()
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
 
-  content.location = "data:text/html,basic tests for inspector";
+  content.location = "data:text/html;charset=utf-8,browser_inspector_initialization.js";
 }
--- a/browser/devtools/inspector/test/browser_inspector_invalidate.js
+++ b/browser/devtools/inspector/test/browser_inspector_invalidate.js
@@ -8,34 +8,34 @@ function test() {
 
   function createDocument() {
     div = doc.createElement("div");
     div.setAttribute("style", "width: 100px; height: 100px; background:yellow;");
     doc.body.appendChild(div);
 
     openInspector(aInspector => {
       inspector = aInspector;
-      inspector.toolbox.highlighter.showBoxModel(getNodeFront(div)).then(runTest);
+      inspector.once("inspector-updated", () => {
+        inspector.toolbox.highlighter.showBoxModel(getNodeFront(div)).then(runTest);
+      });
     });
   }
 
   function runTest() {
-    let outline = getHighlighterOutline();
-    is(outline.style.width, "100px", "outline has the right width");
+    let rect = getSimpleBorderRect();
+    is(rect.width, 100, "outline has the right width");
 
     div.style.width = "200px";
-    function pollTest() {
-      if (outline.style.width == "100px") {
-        setTimeout(pollTest, 10);
-        return;
-      }
-      is(outline.style.width, "200px", "outline updated");
-      finishUp();
-    }
-    setTimeout(pollTest, 10);
+    inspector.toolbox.once("highlighter-ready", testRectWidth);
+  }
+
+  function testRectWidth() {
+    let rect = getSimpleBorderRect();
+    is(rect.width, 200, "outline updated");
+    finishUp();
   }
 
   function finishUp() {
     inspector.toolbox.highlighter.hideBoxModel().then(() => {
       doc = div = inspector = null;
       gBrowser.removeCurrentTab();
       finish();
     });
@@ -44,10 +44,10 @@ function test() {
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
 
-  content.location = "data:text/html,basic tests for inspector";
+  content.location = "data:text/html;charset=utf-8,browser_inspector_invalidate.js";
 }
--- a/browser/devtools/inspector/test/browser_inspector_scrolling.js
+++ b/browser/devtools/inspector/test/browser_inspector_scrolling.js
@@ -28,44 +28,44 @@ function createDocument()
   iframe.src = "data:text/html,foo bar";
   doc.body.appendChild(iframe);
 }
 
 function inspectNode(aInspector)
 {
   inspector = aInspector;
 
-  inspector.once("inspector-updated", performScrollingTest);
-  executeSoon(function() {
-    inspector.selection.setNode(div, "");
-  });
+  let highlighter = inspector.toolbox.highlighter;
+  highlighter.showBoxModel(getNodeFront(div)).then(performScrollingTest);
 }
 
 function performScrollingTest()
 {
-  executeSoon(function() {
-    // FIXME: this will fail on retina displays. EventUtils will only scroll
-    // 25px down instead of 50.
-    EventUtils.synthesizeWheel(div, 10, 10,
-      { deltaY: 50.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL },
-      iframe.contentWindow);
-  });
-
   gBrowser.selectedBrowser.addEventListener("scroll", function() {
     gBrowser.selectedBrowser.removeEventListener("scroll", arguments.callee,
       false);
+    let isRetina = devicePixelRatio === 2;
+    is(iframe.contentDocument.body.scrollTop,
+      isRetina ? 25 : 50, "inspected iframe scrolled");
 
-    is(iframe.contentDocument.body.scrollTop, 50, "inspected iframe scrolled");
+    finishUp();
+  }, false);
 
-    inspector = div = iframe = doc = null;
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    gDevTools.closeToolbox(target);
-    gBrowser.removeCurrentTab();
-    finish();
-  }, false);
+  EventUtils.synthesizeWheel(div, 10, 10,
+    { deltaY: 50.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL },
+    iframe.contentWindow);
+}
+
+function finishUp()
+{
+  inspector = div = iframe = doc = null;
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  gDevTools.closeToolbox(target);
+  gBrowser.removeCurrentTab();
+  finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
--- a/browser/devtools/inspector/test/browser_inspector_sidebarstate.js
+++ b/browser/devtools/inspector/test/browser_inspector_sidebarstate.js
@@ -20,28 +20,29 @@ function inspectorRuleViewOpened()
 {
   is(inspector.sidebar.getCurrentTabID(), "ruleview", "Rule View is selected by default");
 
   // Select the computed view and turn off the inspector.
   inspector.sidebar.select("computedview");
 
   gDevTools.once("toolbox-destroyed", inspectorClosed);
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-  gDevTools.getToolbox(target).destroy();
+  gDevTools.closeToolbox(target);
 }
 
 function inspectorClosed()
 {
   openInspector(function(panel) {
     inspector = panel;
+
     if (inspector.sidebar.getCurrentTabID()) {
-      // Default sidebar already selected.
+      info("Default sidebar already selected.")
       testNewDefaultTab();
     } else {
-      // Default sidebar still to be selected.
+      info("Default sidebar still to be selected, adding select listener.");
       inspector.sidebar.once("select", testNewDefaultTab);
     }
   });
 }
 
 function testNewDefaultTab()
 {
   is(inspector.sidebar.getCurrentTabID(), "computedview", "Computed view is selected by default.");
@@ -62,10 +63,10 @@ function test()
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
 
-  content.location = "data:text/html,basic tests for inspector";
+  content.location = "data:text/html;charset=utf-8,browser_inspector_sidebarstate.js";
 }
--- a/browser/devtools/inspector/test/browser_inspector_tree_height.js
+++ b/browser/devtools/inspector/test/browser_inspector_tree_height.js
@@ -101,11 +101,11 @@ function test()
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
 
-  content.location = "data:text/html,basic tests for inspector";
+  content.location = "data:text/html;charset=utf-8,browser_inspector_tree_height.js";
 }
 
--- a/browser/devtools/inspector/test/head.js
+++ b/browser/devtools/inspector/test/head.js
@@ -22,22 +22,28 @@ let TargetFactory = devtools.TargetFacto
 
 Components.utils.import("resource://gre/modules/devtools/Console.jsm", tempScope);
 let console = tempScope.console;
 
 // Import the GCLI test helper
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
 
+gDevTools.testing = true;
 SimpleTest.registerCleanupFunction(() => {
-  console.error("Here we are\n")
+  gDevTools.testing = false;
+});
+
+SimpleTest.registerCleanupFunction(() => {
+  console.error("Here we are\n");
   let {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
   console.error("DebuggerServer open connections: " + Object.getOwnPropertyNames(DebuggerServer._connections).length);
 
   Services.prefs.clearUserPref("devtools.dump.emit");
+  Services.prefs.clearUserPref("devtools.inspector.activeSidebar");
 });
 
 function openInspector(callback)
 {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
     callback(toolbox.getCurrentPanel(), toolbox);
   }).then(null, console.error);
@@ -55,73 +61,128 @@ function getNodeFront(node)
   return inspector.walker.frontForRawNode(node);
 }
 
 function getHighlighter()
 {
   return gBrowser.selectedBrowser.parentNode.querySelector(".highlighter-container");
 }
 
-function getHighlighterOutline()
-{
-  let h = getHighlighter();
-  if (h) {
-    return h.querySelector(".highlighter-outline");
-  }
+function getSimpleBorderRect() {
+  let {p1, p2, p3, p4} = getBoxModelStatus().border.points;
+
+  return {
+    top: p1.y,
+    left: p1.x,
+    width: p2.x - p1.x,
+    height: p4.y - p1.y
+  };
+}
+
+function getBoxModelRoot() {
+  let highlighter = getHighlighter();
+  return highlighter.querySelector(".box-model-root");
 }
 
-function getHighlighterOutlineRect() {
-  let helper = new LayoutHelpers(window.content);
-  let outline = getHighlighterOutline();
+function getBoxModelStatus() {
+  let root = getBoxModelRoot();
+  let inspector = getActiveInspector();
+
+  return {
+    visible: !root.hasAttribute("hidden"),
+    currentNode: inspector.walker.currentNode,
+    margin: {
+      points: getPointsForRegion("margin"),
+      visible: isRegionHidden("margin")
+    },
+    border: {
+      points: getPointsForRegion("border"),
+      visible: isRegionHidden("border")
+    },
+    padding: {
+      points: getPointsForRegion("padding"),
+      visible: isRegionHidden("padding")
+    },
+    content: {
+      points: getPointsForRegion("content"),
+      visible: isRegionHidden("content")
+    },
+    guides: {
+      top: getGuideStatus("top"),
+      right: getGuideStatus("right"),
+      bottom: getGuideStatus("bottom"),
+      left: getGuideStatus("left")
+    }
+  };
+}
+
+function getGuideStatus(location) {
+  let root = getBoxModelRoot();
+  let guide = root.querySelector(".box-model-guide-" + location);
 
-  if (outline) {
-    let browserOffsetRect = helper.getDirtyRect(gBrowser.selectedBrowser);
-    let outlineRect = helper.getDirtyRect(outline);
-    outlineRect.top -= browserOffsetRect.top;
-    outlineRect.left -= browserOffsetRect.left;
+  return {
+    visible: !guide.hasAttribute("hidden"),
+    x1: guide.getAttribute("x1"),
+    y1: guide.getAttribute("y1"),
+    x2: guide.getAttribute("x2"),
+    y2: guide.getAttribute("y2")
+  };
+}
+
+function getPointsForRegion(region) {
+  let root = getBoxModelRoot();
+  let box = root.querySelector(".box-model-" + region);
+  let points = box.getAttribute("points").split(/[, ]/);
 
-    return outlineRect;
-  }
+  // We multiply each value by 1 to cast it into a number
+  return {
+    p1: {
+      x: parseFloat(points[0]),
+      y: parseFloat(points[1])
+    },
+    p2: {
+      x: parseFloat(points[2]),
+      y: parseFloat(points[3])
+    },
+    p3: {
+      x: parseFloat(points[4]),
+      y: parseFloat(points[5])
+    },
+    p4: {
+      x: parseFloat(points[6]),
+      y: parseFloat(points[7])
+    }
+  };
+}
+
+function isRegionHidden(region) {
+  let root = getBoxModelRoot();
+  let box = root.querySelector(".box-model-" + region);
+
+  return !box.hasAttribute("hidden");
 }
 
 function isHighlighting()
 {
-  let outline = getHighlighterOutline();
-  return outline && !outline.hasAttribute("hidden");
+  let root = getBoxModelRoot();
+  return !root.hasAttribute("hidden");
 }
 
 function getHighlitNode()
 {
   if (isHighlighting()) {
     let helper = new LayoutHelpers(window.content);
-    let outlineRect = getHighlighterOutlineRect();
-
-    let a = {
-      x: outlineRect.left,
-      y: outlineRect.top
-    };
+    let points = getBoxModelStatus().content.points;
+    let x = (points.p1.x + points.p2.x + points.p3.x + points.p4.x) / 4;
+    let y = (points.p1.y + points.p2.y + points.p3.y + points.p4.y) / 4;
 
-    let b = {
-      x: a.x + outlineRect.width,
-      y: a.y + outlineRect.height
-    };
-
-    let {x, y} = getMidPoint(a, b);
     return helper.getElementFromPoint(window.content.document, x, y);
   }
 }
 
-function getMidPoint(aPointA, aPointB)
-{
-  let pointC = {};
-  pointC.x = (aPointB.x - aPointA.x) / 2 + aPointA.x;
-  pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y;
-  return pointC;
-}
-
 function computedView()
 {
   let sidebar = getActiveInspector().sidebar;
   let iframe = sidebar.tabbox.querySelector(".iframe-computedview");
   return iframe.contentWindow.computedView;
 }
 
 function computedViewTree()
--- a/browser/devtools/layoutview/test/browser_layoutview.js
+++ b/browser/devtools/layoutview/test/browser_layoutview.js
@@ -2,16 +2,21 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let TargetFactory = devtools.TargetFactory;
 
 function test() {
   waitForExplicitFinish();
 
+  gDevTools.testing = true;
+  SimpleTest.registerCleanupFunction(() => {
+    gDevTools.testing = false;
+  });
+
   Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", true);
 
   let doc;
   let node;
   let view;
   let inspector;
 
   // Expected values:
--- a/browser/devtools/layoutview/view.css
+++ b/browser/devtools/layoutview/view.css
@@ -49,33 +49,33 @@ body {
   body {
     position: absolute;
     width: 320px;
     left: -160px;
     margin-left: 50%;
   }
 }
 
-
-#margins {
-  padding: 28px;
+#content,
+#borders {
+  border-width: 1px;
 }
 
 #content {
-  height: 20px;
-  border-width: 1px;
+  height: 25px;
 }
 
+#margins,
 #padding {
+  border-style: solid;
   border-width: 25px;
 }
 
 #borders {
-  border-width: 2px;
-  box-shadow: 0 0 16px black;
+  padding: 25px;
 }
 
 #main > p {
   position: absolute;
   pointer-events: none;
 }
 
 #main > p {
@@ -84,98 +84,94 @@ body {
 }
 
 #main > p > span {
   vertical-align: middle;
   pointer-events: auto;
   cursor: default;
 }
 
-.border.top {
-  left: 0; top: 23px;
-  width: 98px;
-}
-
-.border.bottom {
-  right: 0; bottom: 22px;
-  width: 98px;
-  top: auto;
-}
-
-.border.left {
-  top: 42px; left: 0;
-  width: 56px;
-}
-
-.border.right{
-  bottom: 42px; right: 0;
-  width: 56px;
-  top: auto;
-}
-
-.top, .bottom {
+.top,
+.bottom {
   width: calc(100% - 2px);
   text-align: center;
 }
 
+.padding.top {
+  top: 55px;
+}
+
+.padding.bottom {
+  bottom: 57px;
+}
+
+.border.top {
+  top: 30px;
+}
+
+.border.bottom {
+  bottom: 31px;
+}
+
 .margin.top {
-  top: 8px;
+  top: 5px;
 }
 
 .margin.bottom {
   bottom: 6px;
 }
 
-.padding.top {
-  top: 35px;
-}
-
-.padding.bottom {
-  bottom: 35px;
-}
-
 .size,
 .margin.left,
 .margin.right,
+.border.left,
+.border.right,
 .padding.left,
 .padding.right {
-  top: 0;
+  top: 22px;
   line-height: 132px;
 }
 
 .size {
   width: calc(100% - 2px);
 }
 
 .margin.right,
-.margin.left {
-  width: 28px;
-}
-
+.margin.left,
+.border.left,
+.border.right,
 .padding.right,
 .padding.left {
   width: 25px;
 }
 
+.padding.left {
+  left: 52px;
+}
+
+.padding.right {
+  right: 51px;
+}
+
+.border.left {
+  left: 26px;
+}
+
+.border.right {
+  right: 26px;
+}
+
 .margin.right {
   right: 0;
 }
 
 .margin.left {
   left: 0;
 }
 
-.padding.left {
-  left: 30px;
-}
-
-.padding.right {
-  right: 30px;
-}
-
 .tooltip {
   position: absolute;
   bottom: 0;
   right: 2px;
   pointer-events: none;
 }
 
 body.dim > #header > #element-position,
--- a/browser/devtools/layoutview/view.js
+++ b/browser/devtools/layoutview/view.js
@@ -234,22 +234,51 @@ LayoutView.prototype = {
       }
 
       this.inspector.emit("layoutview-updated");
       return null;
     });
 
     this._lastRequest = lastRequest;
     return this._lastRequest;
-  }
-}
+  },
+
+  showBoxModel: function(options={}) {
+    let toolbox = this.inspector.toolbox;
+    let nodeFront = this.inspector.selection.nodeFront;
+
+    toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
+  },
+
+  hideBoxModel: function() {
+    let toolbox = this.inspector.toolbox;
+
+    toolbox.highlighterUtils.unhighlight();
+  },
+};
 
 let elts;
 let tooltip;
 
+let onmouseover = function(e) {
+  let region = e.target.getAttribute("data-box");
+
+  tooltip.textContent = e.target.getAttribute("tooltip");
+  this.layoutview.showBoxModel({region: region});
+
+  return false;
+}.bind(window);
+
+let onmouseout = function(e) {
+  tooltip.textContent = "";
+  this.layoutview.hideBoxModel();
+
+  return false;
+}.bind(window);
+
 window.setPanel = function(panel) {
   this.layoutview = new LayoutView(panel, window);
 
   // Tooltip mechanism
   elts = document.querySelectorAll("*[tooltip]");
   tooltip = document.querySelector(".tooltip");
   for (let i = 0; i < elts.length; i++) {
     let elt = elts[i];
@@ -271,16 +300,8 @@ window.onunload = function() {
   if (elts) {
     for (let i = 0; i < elts.length; i++) {
       let elt = elts[i];
       elt.removeEventListener("mouseover", onmouseover, true);
       elt.removeEventListener("mouseout", onmouseout, true);
     }
   }
 };
-
-function onmouseover(e) {
-  tooltip.textContent = e.target.getAttribute("tooltip");
-}
-
-function onmouseout(e) {
-  tooltip.textContent = "";
-}
\ No newline at end of file
--- a/browser/devtools/layoutview/view.xhtml
+++ b/browser/devtools/layoutview/view.xhtml
@@ -25,40 +25,40 @@
   <body class="theme-body devtools-monospace">
 
     <p id="header">
       <span id="element-size"></span><span id="element-position"></span>
     </p>
 
     <div id="main">
 
-      <div id="margins" tooltip="&margins.tooltip;">
-        <div id="borders" tooltip="&borders.tooltip;">
-          <div id="padding" tooltip="&padding.tooltip;">
-            <div id="content" tooltip="&content.tooltip;">
+      <div id="margins" data-box="margin" tooltip="&margins.tooltip;">
+        <div id="borders" data-box="border" tooltip="&borders.tooltip;">
+          <div id="padding" data-box="padding" tooltip="&padding.tooltip;">
+            <div id="content" data-box="content" tooltip="&content.tooltip;">
             </div>
           </div>
         </div>
       </div>
 
-      <p class="border top"><span tooltip="border-top"></span></p>
-      <p class="border right"><span tooltip="border-right"></span></p>
-      <p class="border bottom"><span tooltip="border-bottom"></span></p>
-      <p class="border left"><span tooltip="border-left"></span></p>
+      <p class="border top"><span data-box="border" tooltip="border-top"></span></p>
+      <p class="border right"><span data-box="border" tooltip="border-right"></span></p>
+      <p class="border bottom"><span data-box="border" tooltip="border-bottom"></span></p>
+      <p class="border left"><span data-box="border" tooltip="border-left"></span></p>
 
-      <p class="margin top"><span tooltip="margin-top"></span></p>
-      <p class="margin right"><span tooltip="margin-right"></span></p>
-      <p class="margin bottom"><span tooltip="margin-bottom"></span></p>
-      <p class="margin left"><span tooltip="margin-left"></span></p>
+      <p class="margin top"><span data-box="margin" tooltip="margin-top"></span></p>
+      <p class="margin right"><span data-box="margin" tooltip="margin-right"></span></p>
+      <p class="margin bottom"><span data-box="margin" tooltip="margin-bottom"></span></p>
+      <p class="margin left"><span data-box="margin" tooltip="margin-left"></span></p>
 
-      <p class="padding top"><span tooltip="padding-top"></span></p>
-      <p class="padding right"><span tooltip="padding-right"></span></p>
-      <p class="padding bottom"><span tooltip="padding-bottom"></span></p>
-      <p class="padding left"><span tooltip="padding-left"></span></p>
+      <p class="padding top"><span data-box="padding" tooltip="padding-top"></span></p>
+      <p class="padding right"><span data-box="padding" tooltip="padding-right"></span></p>
+      <p class="padding bottom"><span data-box="padding" tooltip="padding-bottom"></span></p>
+      <p class="padding left"><span data-box="padding" tooltip="padding-left"></span></p>
 
-      <p class="size"><span tooltip="&content.tooltip;"></span></p>
+      <p class="size"><span data-box="content" tooltip="&content.tooltip;"></span></p>
 
       <span class="tooltip"></span>
 
     </div>
 
   </body>
 </html>
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -160,29 +160,29 @@ MarkupView.prototype = {
       }
       this._containers.get(nodeFront).hovered = true;
 
       this._hoveredNode = nodeFront;
     }
   },
 
   _onMouseLeave: function() {
-    this._hideBoxModel();
+    this._hideBoxModel(true);
     if (this._hoveredNode) {
       this._containers.get(this._hoveredNode).hovered = false;
     }
     this._hoveredNode = null;
   },
 
   _showBoxModel: function(nodeFront, options={}) {
     this._inspector.toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
   },
 
-  _hideBoxModel: function() {
-    this._inspector.toolbox.highlighterUtils.unhighlight();
+  _hideBoxModel: function(forceHide) {
+    this._inspector.toolbox.highlighterUtils.unhighlight(forceHide);
   },
 
   _briefBoxModelTimer: null,
   _brieflyShowBoxModel: function(nodeFront, options) {
     let win = this._frame.contentWindow;
 
     if (this._briefBoxModelTimer) {
       win.clearTimeout(this._briefBoxModelTimer);
@@ -1540,18 +1540,16 @@ MarkupContainer.prototype = {
       if (firstChild.container) {
         firstChild.container.destroy();
       }
       this.children.removeChild(firstChild);
     }
 
     // Remove event listeners
     this.elt.removeEventListener("dblclick", this._onToggle, false);
-    this.elt.removeEventListener("mouseover", this._onMouseOver, false);
-    this.elt.removeEventListener("mouseout", this._onMouseOut, false);
     this.elt.removeEventListener("mousedown", this._onMouseDown, false);
     this.expander.removeEventListener("click", this._onToggle, false);
 
     // Destroy my editor
     this.editor.destroy();
   }
 };
 
--- a/browser/devtools/markupview/test/browser_inspector_markup_968316_highlight_node_after_mouseleave_mousemove.js
+++ b/browser/devtools/markupview/test/browser_inspector_markup_968316_highlight_node_after_mouseleave_mousemove.js
@@ -10,17 +10,17 @@ function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload(evt) {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     waitForFocus(startTests, content);
   }, true);
 
-  content.location = "data:text/html,<p>Select me!</p>";
+  content.location = "data:text/html;charset=utf-8,<p>Select me!</p>";
 }
 
 function startTests(aInspector, aToolbox) {
   let p = content.document.querySelector("p");
   Task.spawn(function() {
     info("opening the inspector tool");
     let {inspector, toolbox} = yield openInspector();
 
--- a/browser/devtools/markupview/test/browser_inspector_markup_968316_highlit_node_on_hover_then_select.js
+++ b/browser/devtools/markupview/test/browser_inspector_markup_968316_highlit_node_on_hover_then_select.js
@@ -10,17 +10,17 @@ function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload(evt) {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     waitForFocus(startTests, content);
   }, true);
 
-  content.location = "data:text/html,<p>It's going to be legen....</p>";
+  content.location = "data:text/html;charset=utf-8,<p>It's going to be legen....</p>";
 }
 
 function startTests(aInspector, aToolbox) {
   let p = content.document.querySelector("p");
   Task.spawn(function() {
     info("opening the inspector tool");
     let {inspector, toolbox} = yield openInspector();
 
--- a/browser/devtools/markupview/test/browser_inspector_markup_navigation.js
+++ b/browser/devtools/markupview/test/browser_inspector_markup_navigation.js
@@ -120,17 +120,17 @@ function test() {
       case "pagedown":
         EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
         break;
       case "home":
         EventUtils.synthesizeKey("VK_HOME", {});
         break;
     }
 
-    inspector.markup._waitForChildren().then(() => executeSoon(function BIMNT_newNode() {
+    inspector.markup._waitForChildren().then(() => executeSoon(() => {
       let node = inspector.selection.node;
 
       if (className == "*comment*") {
         is(node.nodeType, Node.COMMENT_NODE, "[" + cursor + "] should be a comment after moving " + key);
       } else if (className == "*text*") {
         is(node.nodeType, Node.TEXT_NODE, "[" + cursor + "] should be text after moving " + key);
       } else if (className == "*doctype*") {
         is(node.nodeType, Node.DOCUMENT_TYPE_NODE, "[" + cursor + "] should be doctype after moving " + key);
--- a/browser/devtools/markupview/test/head.js
+++ b/browser/devtools/markupview/test/head.js
@@ -7,16 +7,21 @@ const Cu = Components.utils;
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let TargetFactory = devtools.TargetFactory;
 let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
 let promise = devtools.require("sdk/core/promise");
 let {getInplaceEditorForSpan: inplaceEditor} = devtools.require("devtools/shared/inplace-editor");
 
 //Services.prefs.setBoolPref("devtools.dump.emit", true);
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
 // Clear preferences that may be set during the course of tests.
 function clearUserPrefs() {
   Services.prefs.clearUserPref("devtools.inspector.htmlPanelOpen");
   Services.prefs.clearUserPref("devtools.inspector.sidebarOpen");
   Services.prefs.clearUserPref("devtools.inspector.activeSidebar");
   Services.prefs.clearUserPref("devtools.dump.emit");
 }
 
@@ -115,17 +120,17 @@ function selectNode(nodeOrSelector, insp
  * @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
  * @return a promise that resolves when the container is hovered and the higlighter
  * is shown on the corresponding node
  */
 function hoverContainer(nodeOrSelector, inspector) {
   info("Hovering over the markup-container for node " + nodeOrSelector);
   let highlit = inspector.toolbox.once("node-highlight");
   let container = getContainerForRawNode(inspector.markup, getNode(nodeOrSelector));
-  EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"},
+  EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousemove"},
     inspector.markup.doc.defaultView);
   return highlit;
 }
 
 /**
  * Simulate a click on the markup-container (a line in the markup-view)
  * that corresponds to the node or selector passed.
  * @param {String|DOMNode} nodeOrSelector
@@ -143,33 +148,34 @@ function clickContainer(nodeOrSelector, 
   return updated;
 }
 
 /**
  * Checks if the highlighter is visible currently
  * @return {Boolean}
  */
 function isHighlighterVisible() {
-  let outline = gBrowser.selectedBrowser.parentNode.querySelector(".highlighter-container .highlighter-outline");
-  return outline && !outline.hasAttribute("hidden");
+  let highlighter = gBrowser.selectedBrowser.parentNode
+                            .querySelector(".highlighter-container .box-model-root");
+  return highlighter && !highlighter.hasAttribute("hidden");
 }
 
 /**
  * Simulate the mouse leaving the markup-view area
  * @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
  * @return a promise when done
  */
 function mouseLeaveMarkupView(inspector) {
   info("Leaving the markup-view area");
   let def = promise.defer();
 
   // Find another element to mouseover over in order to leave the markup-view
   let btn = inspector.toolbox.doc.querySelector(".toolbox-dock-button");
 
-  EventUtils.synthesizeMouse(btn, 2, 2, {type: "mousemove"},
+  EventUtils.synthesizeMouseAtCenter(btn, {type: "mousemove"},
     inspector.toolbox.doc.defaultView);
   executeSoon(def.resolve);
 
   return def.promise;
 }
 
 /**
  * Focus a given editable element, enter edit mode, set value, and commit
--- a/browser/devtools/netmonitor/test/head.js
+++ b/browser/devtools/netmonitor/test/head.js
@@ -38,28 +38,34 @@ const STATISTICS_URL = EXAMPLE_URL + "ht
 const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
 const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";
 const STATUS_CODES_SJS = EXAMPLE_URL + "sjs_status-codes-test-server.sjs";
 const SORTING_SJS = EXAMPLE_URL + "sjs_sorting-test-server.sjs";
 
 const TEST_IMAGE = EXAMPLE_URL + "test-image.png";
 const TEST_IMAGE_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 // Enable logging for all the relevant tests.
 const gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", true);
 
 // Always reset some prefs to their original values after the test finishes.
 const gDefaultFilters = Services.prefs.getCharPref("devtools.netmonitor.filters");
 
 registerCleanupFunction(() => {
   info("finish() was called, cleaning up...");
+
   Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
   Services.prefs.setCharPref("devtools.netmonitor.filters", gDefaultFilters);
 });
 
 function addTab(aUrl, aWindow) {
   info("Adding tab: " + aUrl);
 
   let deferred = promise.defer();
--- a/browser/devtools/profiler/test/head.js
+++ b/browser/devtools/profiler/test/head.js
@@ -18,16 +18,21 @@ let TargetFactory = temp.devtools.Target
 
 Cu.import("resource://gre/modules/devtools/dbg-server.jsm", temp);
 let DebuggerServer = temp.DebuggerServer;
 
 // Import the GCLI test helper
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
 registerCleanupFunction(function () {
   helpers = null;
   Services.prefs.clearUserPref(PROFILER_ENABLED);
   Services.prefs.clearUserPref(REMOTE_ENABLED);
   Services.prefs.clearUserPref(SHOW_PLATFORM_DATA);
   DebuggerServer.destroy();
 
   // These tests use a lot of memory due to GL contexts, so force a GC to help
--- a/browser/devtools/responsivedesign/test/head.js
+++ b/browser/devtools/responsivedesign/test/head.js
@@ -5,16 +5,21 @@
 
 let {devtools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let TargetFactory = devtools.TargetFactory;
 
 // Import the GCLI test helper
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
 function openInspector(callback)
 {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
     callback(toolbox.getCurrentPanel());
   });
 }
 
--- a/browser/devtools/scratchpad/test/browser_scratchpad_revert_to_saved.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_revert_to_saved.js
@@ -85,18 +85,17 @@ function testAfterSecondSave() {
 function testAfterSecondRevert() {
   // Check if the file's text got reverted
   is(gScratchpad.getText(), gFileContent + "\nalert(foo.toSource());",
      "The text reverted back to the changed saved text.");
   // The revert menu should be disabled again.
   ok(menu.hasAttribute("disabled"),
      "Revert menu entry is disabled after reverting to changed saved state.");
   gFile.remove(false);
-  gFile = null;
-  gScratchpad = null;
+  gFile = gScratchpad = menu = null;
   finish();
 }
 
 function createAndLoadTemporaryFile()
 {
   // Create a temporary file.
   gFile = FileUtils.getFile("TmpD", [gFileName]);
   gFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
--- a/browser/devtools/scratchpad/test/head.js
+++ b/browser/devtools/scratchpad/test/head.js
@@ -5,16 +5,21 @@
 "use strict";
 
 const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 
 let gScratchpadWindow; // Reference to the Scratchpad chrome window object
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
 /**
  * Open a Scratchpad window.
  *
  * @param function aReadyCallback
  *        Optional. The function you want invoked when the Scratchpad instance
  *        is ready.
  * @param object aOptions
  *        Optional. Options for opening the scratchpad:
--- a/browser/devtools/shared/test/head.js
+++ b/browser/devtools/shared/test/head.js
@@ -1,16 +1,21 @@
 /* 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/. */
 
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let TargetFactory = devtools.TargetFactory;
 let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
 /**
  * Open a new tab at a URL and call a callback on load
  */
 function addTab(aURL, aCallback)
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
--- a/browser/devtools/sourceeditor/editor.js
+++ b/browser/devtools/sourceeditor/editor.js
@@ -27,18 +27,17 @@ const L10N = Services.strings.createBund
 // CM_STYLES, CM_SCRIPTS and CM_IFRAME represent the HTML,
 // JavaScript and CSS that is injected into an iframe in
 // order to initialize a CodeMirror instance.
 
 const CM_STYLES   = [
   "chrome://browser/skin/devtools/common.css",
   "chrome://browser/content/devtools/codemirror/codemirror.css",
   "chrome://browser/content/devtools/codemirror/dialog.css",
-  "chrome://browser/content/devtools/codemirror/mozilla.css",
-  "chrome://browser/content/devtools/codemirror/foldgutter.css"
+  "chrome://browser/content/devtools/codemirror/mozilla.css"
 ];
 
 const CM_SCRIPTS  = [
   "chrome://browser/content/devtools/theme-switching.js",
   "chrome://browser/content/devtools/codemirror/codemirror.js",
   "chrome://browser/content/devtools/codemirror/dialog.js",
   "chrome://browser/content/devtools/codemirror/searchcursor.js",
   "chrome://browser/content/devtools/codemirror/search.js",
--- a/browser/devtools/sourceeditor/test/head.js
+++ b/browser/devtools/sourceeditor/test/head.js
@@ -3,16 +3,21 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 const { require } = devtools;
 const Editor  = require("devtools/sourceeditor/editor");
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
 function setup(cb) {
   const opt = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
   const url = "data:text/xml;charset=UTF-8,<?xml version='1.0'?>" +
     "<?xml-stylesheet href='chrome://global/skin/global.css'?>" +
     "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
     " title='Editor' width='600' height='500'><box flex='1'/></window>";
 
   let win = Services.ww.openWindow(null, url, "_blank", opt, null);
--- a/browser/devtools/styleeditor/test/head.js
+++ b/browser/devtools/styleeditor/test/head.js
@@ -18,16 +18,21 @@ let gPanelWindow;
 let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
               .getService(Ci.nsICacheStorageService);
 
 
 // Import the GCLI test helper
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
 function cleanup()
 {
   gPanelWindow = null;
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
 }
 
--- a/browser/devtools/styleinspector/style-inspector.js
+++ b/browser/devtools/styleinspector/style-inspector.js
@@ -130,17 +130,17 @@ RuleViewTool.prototype = {
 
     this.view.destroy();
 
     delete this.outerIFrame;
     delete this.view;
     delete this.doc;
     delete this.inspector;
   }
-}
+};
 
 function ComputedViewTool(aInspector, aWindow, aIFrame)
 {
   this.inspector = aInspector;
   this.window = aWindow;
   this.document = aWindow.document;
   this.outerIFrame = aIFrame;
   this.view = new ComputedView.CssHtmlTree(this, aInspector.pageStyle);
--- a/browser/devtools/styleinspector/test/browser_ruleview_inherit.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_inherit.js
@@ -107,10 +107,10 @@ function test()
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
     doc = content.document;
     waitForFocus(() => openRuleView(simpleInherit), content);
   }, true);
 
-  content.location = "data:text/html,basic style inspector tests";
+  content.location = "data:text/html;charset=utf-8,browser_inspector_changes.js";
 }
--- a/browser/devtools/styleinspector/test/browser_ruleview_manipulation.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_manipulation.js
@@ -78,10 +78,10 @@ function test()
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
     doc = content.document;
     waitForFocus(() => openRuleView(simpleOverride), content);
   }, true);
 
-  content.location = "data:text/html,basic style inspector tests";
+  content.location = "data:text/html;charset=utf-8,browser_ruleview_manipulation.js";
 }
--- a/browser/devtools/styleinspector/test/browser_ruleview_override.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_override.js
@@ -173,10 +173,10 @@ function test()
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
     doc = content.document;
     waitForFocus(() => openRuleView(simpleOverride), content);
   }, true);
 
-  content.location = "data:text/html,basic style inspector tests";
+  content.location = "data:text/html;charset=utf-8,browser_ruleview_override.js";
 }
--- a/browser/devtools/styleinspector/test/browser_ruleview_ui.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_ui.js
@@ -243,10 +243,10 @@ function test()
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
     doc = content.document;
     waitForFocus(() => openRuleView(startTest), content);
   }, true);
 
-  content.location = "data:text/html,basic style inspector tests";
+  content.location = "data:text/html;charset=utf-8,browser_ruleview_ui.js";
 }
--- a/browser/devtools/styleinspector/test/browser_ruleview_update.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_update.js
@@ -184,10 +184,10 @@ function test()
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
     doc = content.document;
     waitForFocus(() => openRuleView(startTest), content);
   }, true);
 
-  content.location = "data:text/html,basic style inspector tests";
+  content.location = "data:text/html;charset=utf-8,browser_ruleview_update.js";
 }
--- a/browser/devtools/styleinspector/test/head.js
+++ b/browser/devtools/styleinspector/test/head.js
@@ -4,37 +4,42 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const TEST_BASE_HTTP = "http://example.com/browser/browser/devtools/styleinspector/test/";
 const TEST_BASE_HTTPS = "https://example.com/browser/browser/devtools/styleinspector/test/";
 
 //Services.prefs.setBoolPref("devtools.dump.emit", true);
 Services.prefs.setBoolPref("devtools.debugger.log", true);
 
-SimpleTest.registerCleanupFunction(() => {
-  Services.prefs.clearUserPref("devtools.debugger.log");
-  Services.prefs.clearUserPref("devtools.dump.emit");
-});
-
 let tempScope = {};
 
 Cu.import("resource:///modules/devtools/gDevTools.jsm", tempScope);
 let ConsoleUtils = tempScope.ConsoleUtils;
 let gDevTools = tempScope.gDevTools;
 
 Cu.import("resource://gre/modules/devtools/Loader.jsm", tempScope);
 let devtools = tempScope.devtools;
 
 let TargetFactory = devtools.TargetFactory;
 let {CssHtmlTree} = devtools.require("devtools/styleinspector/computed-view");
 let {CssRuleView, _ElementStyle} = devtools.require("devtools/styleinspector/rule-view");
 let {CssLogic, CssSelector} = devtools.require("devtools/styleinspector/css-logic");
 
 let promise = devtools.require("sdk/core/promise");
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
+SimpleTest.registerCleanupFunction(() => {
+  Services.prefs.clearUserPref("devtools.debugger.log");
+  Services.prefs.clearUserPref("devtools.dump.emit");
+});
+
 let {
   editableField,
   getInplaceEditorForSpan: inplaceEditor
 } = devtools.require("devtools/shared/inplace-editor");
 Components.utils.import("resource://gre/modules/devtools/Console.jsm", tempScope);
 let console = tempScope.console;
 
 let browser, hudId, hud, hudBox, filterBox, outputNode, cs;
--- a/browser/devtools/tilt/test/head.js
+++ b/browser/devtools/tilt/test/head.js
@@ -51,16 +51,20 @@ const DESTROYED = Tilt.NOTIFICATIONS.DES
 const SHOWN = Tilt.NOTIFICATIONS.SHOWN;
 const HIDDEN = Tilt.NOTIFICATIONS.HIDDEN;
 const HIGHLIGHTING = Tilt.NOTIFICATIONS.HIGHLIGHTING;
 const UNHIGHLIGHTING = Tilt.NOTIFICATIONS.UNHIGHLIGHTING;
 const NODE_REMOVED = Tilt.NOTIFICATIONS.NODE_REMOVED;
 
 const TILT_ENABLED = Services.prefs.getBoolPref("devtools.tilt.enabled");
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
 
 function isTiltEnabled() {
   info("Apparently, Tilt is" + (TILT_ENABLED ? "" : " not") + " enabled.");
   return TILT_ENABLED;
 }
 
 function isWebGLSupported() {
   let supported = !TiltGL.isWebGLForceEnabled() &&
--- a/browser/devtools/webconsole/test/browser_console_variables_view_highlighter.js
+++ b/browser/devtools/webconsole/test/browser_console_variables_view_highlighter.js
@@ -61,17 +61,17 @@ function onNodeListVviewFetched(aEvent, 
           clickOnDomNodeVariableAndAssertInspectorSelected(index);
         });
       });
 
       // Rather than trying to emulate a mouseenter event, let's call the
       // variable's highlightDomNode and see if it has the desired effect
       prop.highlightDomNode();
     } else {
-      finishTest();
+      finishUp();
     }
   }
 
   function clickOnDomNodeVariableAndAssertInspectorSelected(index) {
     let prop = props[index][1];
 
     // Make sure the inspector is initialized so we can listen to its events
     gToolbox.initInspector().then(() => {
@@ -84,8 +84,14 @@ function onNodeListVviewFetched(aEvent, 
           hoverOverDomNodeVariableAndAssertHighlighter(index + 1);
         });
       });
     });
   }
 
   hoverOverDomNodeVariableAndAssertHighlighter(0);
 }
+
+function finishUp() {
+  gWebConsole = gJSTerm = gVariablesView = gToolbox = null;
+
+  finishTest();
+}
--- a/browser/devtools/webconsole/test/browser_webconsole_autocomplete_in_debugger_stackframe.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_autocomplete_in_debugger_stackframe.js
@@ -209,17 +209,17 @@ function testCompletion(hud) {
   input.setSelectionRange(11, 11);
   jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
   yield undefined;
 
   newItems = popup.getItems();
   is(newItems.length, 0, "no items for foo2Obj[0]");
 
   testDriver = null;
-  executeSoon(finishTest);
+  executeSoon(finishUp);
   yield undefined;
 }
 
 function debuggerOpened(aResult)
 {
   let debuggerWin = aResult.panelWin;
   let debuggerController = debuggerWin.DebuggerController;
   let thread = debuggerController.activeThread;
@@ -232,8 +232,13 @@ function debuggerOpened(aResult)
   });
 }
 
 function onFramesAdded()
 {
   info("onFramesAdded, openConsole() now");
   executeSoon(() => openConsole(null, testNext));
 }
+
+function finishUp() {
+  testDriver = gStackframes = null;
+  finishTest();
+}
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js
@@ -327,10 +327,15 @@ function popupHideAfterCompletionInText(
 
   ok(!popup.isOpen, "popup is not open");
   is(inputNode.value, "dump(window.testBug873250b)",
      "completion was successful after VK_TAB");
   is(inputNode.selectionStart, 26, "cursor location is correct");
   is(inputNode.selectionStart, inputNode.selectionEnd, "cursor location (confirmed)");
   ok(!completeNode.value, "completeNode is empty");
 
+  finishUp();
+}
+
+function finishUp() {
+  HUD = popup = jsterm = inputNode = completeNode = null;
   finishTest();
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
@@ -79,10 +79,15 @@ function testPropertyPanel()
     EventUtils.synthesizeMouse(anchor, 2, 2, {}, gHUD.iframeWindow);
   });
 }
 
 function onVariablesViewReady(aEvent, aView)
 {
   findVariableViewProperties(aView, [
     { name: "body", value: "<body>" },
-  ], { webconsole: gHUD }).then(finishTest);
+  ], { webconsole: gHUD }).then(finishUp);
 }
+
+function finishUp() {
+  gHUD = null;
+  finishTest();
+}
--- a/browser/devtools/webconsole/test/browser_webconsole_output_01.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_01.js
@@ -84,10 +84,15 @@ function test() {
     DebuggerServer.LONG_STRING_LENGTH = LONG_STRING_LENGTH;
     DebuggerServer.LONG_STRING_INITIAL_LENGTH = LONG_STRING_INITIAL_LENGTH;
   });
 
   Task.spawn(function*() {
     let {tab} = yield loadTab(TEST_URI);
     let hud = yield openConsole(tab);
     return checkOutputForInputs(hud, inputTests);
-  }).then(finishTest);
+  }).then(finishUp);
 }
+
+function finishUp() {
+  inputTests = null;
+  finishTest();
+}
--- a/browser/devtools/webconsole/test/browser_webconsole_output_03.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_03.js
@@ -148,17 +148,22 @@ let inputTests = [
     output: 'class="test1 tezt2"',
     printOutput: "[object Attr]",
     inspectable: true,
     variablesViewLabel: 'class="test1 tezt2"',
   },
 ];
 
 function test() {
-
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole().then((hud) => {
       return checkOutputForInputs(hud, inputTests);
-    }).then(finishTest);
+    }).then(finishUp);
   }, true);
 }
+
+function finishUp() {
+  inputTests = null;
+
+  finishTest();
+}
--- a/browser/devtools/webconsole/test/browser_webconsole_output_05.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_05.js
@@ -75,10 +75,15 @@ let inputTests = [
   },
 ];
 
 function test() {
   Task.spawn(function*() {
     let {tab} = yield loadTab(TEST_URI);
     let hud = yield openConsole(tab);
     return checkOutputForInputs(hud, inputTests);
-  }).then(finishTest);
+  }).then(finishUp);
 }
+
+function finishUp() {
+  inputTests = null;
+  finishTest();
+}
--- a/browser/devtools/webconsole/test/browser_webconsole_view_source.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_view_source.js
@@ -23,17 +23,17 @@ function testViewSource(hud) {
 
   let button = content.document.querySelector("button");
   ok(button, "we have the button on the page");
 
   expectUncaughtException();
   EventUtils.sendMouseEvent({ type: "click" }, button, content);
 
   openDebugger().then(({panelWin: { DebuggerView }}) => {
-    info("debugger openeed");
+    info("debugger opened");
     Sources = DebuggerView.Sources;
     openConsole(null, (hud) => {
       info("console opened again");
 
       waitForMessages({
         webconsole: hud,
         messages: [{
           text: "fooBazBaz is not defined",
@@ -66,22 +66,19 @@ let observer = {
   observe: function(aSubject, aTopic, aData) {
     if (aTopic != "domwindowopened") {
       return;
     }
 
     ok(true, "the view source window was opened in response to clicking " +
        "the location node");
 
-    // executeSoon() is necessary to avoid crashing Firefox. See bug 611543.
-    executeSoon(function() {
-      aSubject.close();
-      ok(containsValueInvoked, "custom containsValue() was invoked");
-      Sources.containsValue = containsValue;
-      Sources = containsValue = null;
-      finishTest();
-    });
+    aSubject.close();
+    ok(containsValueInvoked, "custom containsValue() was invoked");
+    Sources.containsValue = containsValue;
+    Sources = containsValue = null;
+    finishTest();
   }
 };
 
 registerCleanupFunction(function() {
   Services.ww.unregisterNotification(observer);
 });
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -35,16 +35,21 @@ const SEVERITY_LOG = 3;
 const GROUP_INDENT = 12;
 
 // The default indent in pixels, applied even without any groups.
 const GROUP_INDENT_DEFAULT = 6;
 
 const WEBCONSOLE_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
 let WCU_l10n = new WebConsoleUtils.l10n(WEBCONSOLE_STRINGS_URI);
 
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+  gDevTools.testing = false;
+});
+
 function log(aMsg)
 {
   dump("*** WebConsoleTest: " + aMsg + "\n");
 }
 
 function pprint(aObj)
 {
   for (let prop in aObj) {
@@ -293,17 +298,17 @@ function dumpMessageElement(aMessage)
                 "severity", aMessage.severity,
                 "repeats", repeats,
                 "clipboardText", aMessage.clipboardText,
                 "text", text);
 }
 
 function finishTest()
 {
-  browser = hudId = hud = filterBox = outputNode = cs = null;
+  browser = hudId = hud = filterBox = outputNode = cs = hudBox = null;
 
   dumpConsoles();
 
   let browserConsole = HUDService.getBrowserConsole();
   if (browserConsole) {
     if (browserConsole.jsterm) {
       browserConsole.jsterm.clearOutput(true);
     }
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -93,19 +93,18 @@ These should match what Safari and other
 <!ENTITY fullScreenCmd.label "Full Screen">
 <!ENTITY fullScreenCmd.accesskey "F">
 <!ENTITY fullScreenCmd.macCommandKey "f">
 <!ENTITY showAllTabsCmd.label "Show All Tabs">
 <!ENTITY showAllTabsCmd.accesskey "A">
 
 <!ENTITY fxaSignIn.label "Sign in to &syncBrand.shortName.label;">
 <!ENTITY fxaSignInError.label "Reconnect to &syncBrand.shortName.label;">
-<!ENTITY syncStartPanel.heading "&brandShortName; is now syncing">
-<!ENTITY syncStartPanel.subTitle "You can manage &syncBrand.shortName.label; in Options.">
-<!ENTITY syncStartPanel.subTitleUnix "You can manage &syncBrand.shortName.label; in Preferences.">
+<!ENTITY syncStartPanel2.heading "&syncBrand.shortName.label; enabled">
+<!ENTITY syncStartPanel2.subTitle "&brandShortName; will begin syncing momentarily.">
 <!ENTITY syncErrorPanel.heading "Cannot connect to &syncBrand.shortName.label;">
 <!ENTITY syncErrorPanel.subTitle "Please sign in to resume syncing.">
 <!ENTITY syncErrorPanel.signInButton.label "Sign In">
 <!ENTITY syncErrorPanel.signInButton.accesskey "S">
 
 
 <!ENTITY fullScreenMinimize.tooltip "Minimize">
 <!ENTITY fullScreenRestore.tooltip "Restore">
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 %endif
 
 @import url("chrome://global/skin/");
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
+@namespace svg url("http://www.w3.org/2000/svg");
 
 %include ../shared/browser.inc
 %include linuxShared.inc
 %filter substitution
 
 %define forwardTransitionLength 150ms
 %define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-container
 %define conditionalForwardWithUrlbarWidth 27
@@ -1951,32 +1952,35 @@ toolbarbutton.chevron > .toolbarbutton-i
   color: ButtonText;
   padding: 0 3px;
   margin-top: 10px;
 }
 
 /* Sync Panel */
 
 .sync-panel-icon {
+  height:32px;
   width: 32px;
   background: url("chrome://browser/content/abouthome/sync.png") top left no-repeat;
 }
 
 .sync-panel-inner {
   width: 0;
   padding-left: 10px;
 }
 
 .sync-panel-button-box {
   margin-top: 1em;
 }
 
 #sync-error-panel-title,
 #sync-start-panel-title {
+  font-size: 120%;
   font-weight: bold;
+  margin-bottom: 5px;
 }
 
 #sync-start-panel-subtitle,
 #sync-error-panel-subtitle {
   margin: 0;
 }
 
 /* Status panel */
--- a/browser/themes/linux/devtools/layoutview.css
+++ b/browser/themes/linux/devtools/layoutview.css
@@ -6,41 +6,45 @@
   background-image: url(layout-background-grid.png);
 }
 
 .theme-light .theme-body {
   background-image: url(layout-background-grid.png), radial-gradient(circle at 50% 70%, hsl(210,53%,45%) 0%, hsl(210,54%,33%) 100%);
 }
 
 .theme-body {
-  color: hsl(210,100%,85%) !important;
+  color: hsl(210,53%,45%) !important;
   box-sizing: border-box;
 }
 
 #main {
+  background-color: white;
   border-color: hsla(210,100%,85%,0.7);
   border-style: dotted;
 }
 
-#main > .border {
-  color: hsl(210,53%,45%);
+#content {
+  background-color: #80d4ff;
+  border-color: hsl(210,100%,85%);
+  border-style: dotted;
 }
 
-.border > span {
-  background-color: hsl(210,100%,85%);
-  border-radius: 2px;
-  padding: 0 4px;
-}
-
-#content {
-  border-color: hsla(210,100%,85%,0.7);
-  border-style: dotted 
+#padding,
+#margins {
+  border-color: hsla(210,100%,85%,0.2);
+  outline: dotted 1px hsl(210,100%,85%);
 }
 
 #padding {
-  border-color: hsla(210,100%,85%,0.2);
-  border-style: solid;
+  background-color: #66cc52;
 }
 
 #borders {
-  border-style: solid;
+  background-color: #ffe431;
+  border-style: dotted;
   border-color: hsl(210,100%,85%);
+  box-shadow: 0 0 8px #000;
 }
+
+#margins {
+  background-color: #d89b28;
+  opacity: 0.6;
+}
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -9,16 +9,17 @@
 %define forwardTransitionLength 150ms
 %define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-container
 %define conditionalForwardWithUrlbarWidth 27
 %define spaceAboveTabbar 9px
 %define toolbarButtonPressed :hover:active:not([disabled="true"]):not([cui-areatype="menu-panel"])
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
+@namespace svg url("http://www.w3.org/2000/svg");
 
 #urlbar:-moz-lwtheme:not([focused="true"]),
 .searchbar-textbox:-moz-lwtheme:not([focused="true"]) {
   opacity: .9;
 }
 
 #navigator-toolbox::after {
   -moz-box-ordinal-group: 101; /* tabs toolbar is 100 */
@@ -3781,16 +3782,17 @@ toolbarbutton.chevron > .toolbarbutton-m
 
 #ctrlTab-showAll {
   margin-top: .5em;
 }
 
 /* Sync Panels */
 
 .sync-panel-icon {
+  height:32px;
   width: 32px;
   background: url("chrome://browser/content/abouthome/sync.png") top left no-repeat;
 }
 
 @media (min-resolution: 2dppx) {
   .sync-panel-icon {
     background: url("chrome://browser/content/abouthome/sync@2x.png") top left no-repeat;
     background-size: 32px 32px;
@@ -3818,17 +3820,19 @@ toolbarbutton.chevron > .toolbarbutton-m
 }
 
 .sync-panel-button:-moz-focusring {
   @hudButtonFocused@
 }
 
 #sync-error-panel-title,
 #sync-start-panel-title {
+  font-size: 120%;
   font-weight: bold;
+  margin-bottom: 5px;
 }
 
 #sync-start-panel-subtitle,
 #sync-error-panel-subtitle {
   margin: 0;
 }
 
 /* Status panel */
--- a/browser/themes/osx/devtools/layoutview.css
+++ b/browser/themes/osx/devtools/layoutview.css
@@ -6,41 +6,45 @@
   background-image: url(layout-background-grid.png);
 }
 
 .theme-light .theme-body {
   background-image: url(layout-background-grid.png), radial-gradient(circle at 50% 70%, hsl(210,53%,45%) 0%, hsl(210,54%,33%) 100%);
 }
 
 .theme-body {
-  color: hsl(210,100%,85%) !important;
+  color: hsl(210,53%,45%) !important;
   box-sizing: border-box;
 }
 
 #main {
+  background-color: white;
   border-color: hsla(210,100%,85%,0.7);
   border-style: dotted;
 }
 
-#main > .border {
-  color: hsl(210,53%,45%);
+#content {
+  background-color: #80d4ff;
+  border-color: hsl(210,100%,85%);
+  border-style: dotted;
 }
 
-.border > span {
-  background-color: hsl(210,100%,85%);
-  border-radius: 2px;
-  padding: 0 4px;
-}
-
-#content {
-  border-color: hsla(210,100%,85%,0.7);
-  border-style: dotted 
+#padding,
+#margins {
+  border-color: hsla(210,100%,85%,0.2);
+  outline: dotted 1px hsl(210,100%,85%);
 }
 
 #padding {
-  border-color: hsla(210,100%,85%,0.2);
-  border-style: solid;
+  background-color: #66cc52;
 }
 
 #borders {
-  border-style: solid;
+  background-color: #ffe431;
+  border-style: dotted;
   border-color: hsl(210,100%,85%);
+  box-shadow: 0 0 8px #000;
 }
+
+#margins {
+  background-color: #d89b28;
+  opacity: 0.6;
+}
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -248,16 +248,17 @@ toolbarpaletteitem[place="toolbar"] {
 
 #wrapper-zoom-controls[place="palette"] > #zoom-controls > #zoom-reset-button,
 #wrapper-zoom-controls[place="palette"] > #zoom-controls > #zoom-reset-button + separator {
   display: none;
 }
 
 #wrapper-personal-bookmarks:not([place="toolbar"]) > #personal-bookmarks {
   -moz-box-pack: center;
+  min-height: 48px;
 }
 
 #customization-palette > toolbarpaletteitem > label {
   text-align: center;
   margin-left: 0;
   margin-right: 0;
 }
 
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -129,17 +129,18 @@
 .panelUI-grid:not([customize-transitioning]) .toolbarbutton-1 > .toolbarbutton-multiline-text {
   position: absolute;
   clip: rect(auto, auto, 2.2em, auto);
 }
 
 .panelUI-grid .toolbarbutton-1 > .toolbarbutton-text,
 .panelUI-grid .toolbarbutton-1 > .toolbarbutton-multiline-text {
   text-align: center;
-  margin: 2px 0 0;
+  /* Need to override toolkit theming which sets margin: 0 !important; */
+  margin: 2px 0 0 !important;
 }
 
 .panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-multiline-text {
   text-align: center;
   margin: -1px 0 0;
 }
 
 #wrapper-edit-controls:-moz-any([place="palette"],[place="panel"]) > #edit-controls,
--- a/browser/themes/shared/devtools/highlighter.inc.css
+++ b/browser/themes/shared/devtools/highlighter.inc.css
@@ -1,20 +1,48 @@
 %if 0
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 %endif
 
-/* Highlighter */
+/* Box model highlighter */
+svg|g.box-model-container {
+  opacity: 0.4;
+}
+
+svg|polygon.box-model-content {
+  fill: #80d4ff;
+}
+
+svg|polygon.box-model-padding {
+  fill: #66cc52;
+}
+
+svg|polygon.box-model-border {
+  fill: #ffe431;
+}
 
-.highlighter-outline {
-  box-shadow: 0 0 0 1px black;
-  outline: 1px dashed white;
-  outline-offset: -1px;
+svg|polygon.box-model-margin {
+  fill: #d89b28;
+}
+
+svg|polygon.box-model-content,
+svg|polygon.box-model-padding,
+svg|polygon.box-model-border,
+svg|polygon.box-model-margin {
+  stroke: none;
+}
+
+svg|line.box-model-guide-top,
+svg|line.box-model-guide-right,
+svg|line.box-model-guide-bottom,
+svg|line.box-model-guide-left {
+  stroke: #08C;
+  stroke-dasharray: 5 3;
 }
 
 /* Highlighter - Node Infobar */
 
 .highlighter-nodeinfobar {
   color: hsl(216,33%,97%);
   border-radius: 3px;
   background: hsl(214,13%,24%) no-repeat padding-box;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1,16 +1,17 @@
 /* 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/. */
 
 @import url("chrome://global/skin/");
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
+@namespace svg url("http://www.w3.org/2000/svg");
 
 %include ../shared/browser.inc
 %include windowsShared.inc
 %filter substitution
 %define toolbarShadowColor hsla(209,67%,12%,0.35)
 %define navbarTextboxCustomBorder border-color: rgba(0,0,0,.32);
 %define forwardTransitionLength 150ms
 %define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-container
@@ -2381,32 +2382,35 @@ toolbarbutton.bookmark-item[dragover="tr
 
 #ctrlTab-showAll {
   margin-top: .5em;
 }
 
 /* Sync Panel */
 
 .sync-panel-icon {
+  height:32px;
   width: 32px;
   background: url("chrome://browser/content/abouthome/sync.png") top left no-repeat;
 }
 
 .sync-panel-inner {
   width: 0;
   padding-left: 10px;
 }
 
 .sync-panel-button-box {
   margin-top: 1em;
 }
 
 #sync-error-panel-title,
 #sync-start-panel-title {
+  font-size: 120%;
   font-weight: bold;
+  margin-bottom: 5px;
 }
 
 #sync-start-panel-subtitle,
 #sync-error-panel-subtitle {
   margin: 0;
 }
 
 /* Status panel */
--- a/browser/themes/windows/devtools/layoutview.css
+++ b/browser/themes/windows/devtools/layoutview.css
@@ -6,41 +6,45 @@
   background-image: url(layout-background-grid.png);
 }
 
 .theme-light .theme-body {
   background-image: url(layout-background-grid.png), radial-gradient(circle at 50% 70%, hsl(210,53%,45%) 0%, hsl(210,54%,33%) 100%);
 }
 
 .theme-body {
-  color: hsl(210,100%,85%) !important;
+  color: hsl(210,53%,45%) !important;
   box-sizing: border-box;
 }
 
 #main {
+  background-color: white;
   border-color: hsla(210,100%,85%,0.7);
   border-style: dotted;
 }
 
-#main > .border {
-  color: hsl(210,53%,45%);
+#content {
+  background-color: #80d4ff;
+  border-color: hsl(210,100%,85%);
+  border-style: dotted;
 }
 
-.border > span {
-  background-color: hsl(210,100%,85%);
-  border-radius: 2px;
-  padding: 0 4px;
-}
-
-#content {
-  border-color: hsla(210,100%,85%,0.7);
-  border-style: dotted 
+#padding,
+#margins {
+  border-color: hsla(210,100%,85%,0.2);
+  outline: dotted 1px hsl(210,100%,85%);
 }
 
 #padding {
-  border-color: hsla(210,100%,85%,0.2);
-  border-style: solid;
+  background-color: #66cc52;
 }
 
 #borders {
-  border-style: solid;
+  background-color: #ffe431;
+  border-style: dotted;
   border-color: hsl(210,100%,85%);
+  box-shadow: 0 0 8px #000;
 }
+
+#margins {
+  background-color: #d89b28;
+  opacity: 0.6;
+}
--- a/mobile/android/base/background/announcements/AnnouncementsConstants.java.in
+++ b/mobile/android/base/background/announcements/AnnouncementsConstants.java.in
@@ -40,11 +40,11 @@ public class AnnouncementsConstants {
   // Stop reporting idle counts once they hit one year.
   public static long MAX_SANE_IDLE_DAYS = 365;
 
   // Don't track last launch if the timestamp is ridiculously out of range:
   // four years after build.
   public static long LATEST_ACCEPTED_LAUNCH_TIMESTAMP_MSEC = GlobalConstants.BUILD_TIMESTAMP_MSEC +
                                                              4 * 365 * MILLISECONDS_PER_DAY;
 
-  public static String ANNOUNCE_USER_AGENT = "Firefox Announcements " + GlobalConstants.MOZ_APP_VERSION;
+  public static String USER_AGENT = "Firefox Announcements " + GlobalConstants.MOZ_APP_VERSION;
   public static String ANNOUNCE_CHANNEL = GlobalConstants.MOZ_UPDATE_CHANNEL.replace("default", GlobalConstants.MOZ_OFFICIAL_BRANDING ? "release" : "dev");
 }
--- a/mobile/android/base/background/announcements/AnnouncementsFetchResourceDelegate.java
+++ b/mobile/android/base/background/announcements/AnnouncementsFetchResourceDelegate.java
@@ -42,21 +42,25 @@ public class AnnouncementsFetchResourceD
 
   public AnnouncementsFetchResourceDelegate(Resource resource, AnnouncementsFetchDelegate delegate) {
     super(resource);
     this.startTime = System.currentTimeMillis();
     this.delegate  = delegate;
   }
 
   @Override
+  public String getUserAgent() {
+    return delegate.getUserAgent();
+  }
+
+  @Override
   public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
     super.addHeaders(request, client);
 
     // The basics.
-    request.addHeader("User-Agent",      delegate.getUserAgent());
     request.addHeader("Accept-Language", delegate.getLocale().toString());
     request.addHeader("Accept",          ACCEPT_HEADER);
 
     // We never want to keep connections alive.
     request.addHeader("Connection", "close");
 
     // Set If-Modified-Since to avoid re-fetching content.
     final String ifModifiedSince = delegate.getLastDate();
@@ -171,9 +175,9 @@ public class AnnouncementsFetchResourceD
   /**
    * Be very thorough in case the superclass implementation changes.
    * We never want this to be an authenticated request.
    */
   @Override
   public AuthHeaderProvider getAuthHeaderProvider() {
     return null;
   }
-}
\ No newline at end of file
+}
--- a/mobile/android/base/background/announcements/AnnouncementsService.java
+++ b/mobile/android/base/background/announcements/AnnouncementsService.java
@@ -162,16 +162,17 @@ public class AnnouncementsService extend
   protected long getEarliestNextFetch() {
     return this.getSharedPreferences().getLong(AnnouncementsConstants.PREF_EARLIEST_NEXT_ANNOUNCE_FETCH, 0L);
   }
 
   protected void setLastFetch(final long fetch) {
     this.getSharedPreferences().edit().putLong(AnnouncementsConstants.PREF_LAST_FETCH_LOCAL_TIME, fetch).commit();
   }
 
+  @Override
   public long getLastFetch() {
     return this.getSharedPreferences().getLong(AnnouncementsConstants.PREF_LAST_FETCH_LOCAL_TIME, 0L);
   }
 
   protected String setLastDate(final String fetch) {
     if (fetch == null) {
       this.getSharedPreferences().edit().remove(AnnouncementsConstants.PREF_LAST_FETCH_SERVER_DATE).commit();
       return null;
@@ -220,17 +221,17 @@ public class AnnouncementsService extend
 
   @Override
   public Locale getLocale() {
     return Locale.getDefault();
   }
 
   @Override
   public String getUserAgent() {
-    return AnnouncementsConstants.ANNOUNCE_USER_AGENT;
+    return AnnouncementsConstants.USER_AGENT;
   }
 
   protected void persistTimes(long fetched, String date) {
     setLastFetch(fetched);
     if (date != null) {
       setLastDate(date);
     }
   }
--- a/mobile/android/base/background/bagheera/BagheeraClient.java
+++ b/mobile/android/base/background/bagheera/BagheeraClient.java
@@ -162,16 +162,21 @@ public class BagheeraClient {
                                     final BagheeraRequestDelegate delegate) {
       super(resource);
       this.namespace = namespace;
       this.id = id;
       this.delegate = delegate;
     }
 
     @Override
+    public String getUserAgent() {
+      return delegate.getUserAgent();
+    }
+
+    @Override
     public int socketTimeout() {
       return DEFAULT_SOCKET_TIMEOUT_MSEC;
     }
 
     @Override
     public void handleHttpResponse(HttpResponse response) {
       final int status = response.getStatusLine().getStatusCode();
       switch (status) {
--- a/mobile/android/base/background/bagheera/BagheeraRequestDelegate.java
+++ b/mobile/android/base/background/bagheera/BagheeraRequestDelegate.java
@@ -5,9 +5,11 @@
 package org.mozilla.gecko.background.bagheera;
 
 import ch.boye.httpclientandroidlib.HttpResponse;
 
 public interface BagheeraRequestDelegate {
   void handleSuccess(int status, String namespace, String id, HttpResponse response);
   void handleError(Exception e);
   void handleFailure(int status, String namespace, HttpResponse response);
+
+  public String getUserAgent();
 }
--- a/mobile/android/base/background/fxa/FxAccountClient10.java
+++ b/mobile/android/base/background/fxa/FxAccountClient10.java
@@ -15,16 +15,17 @@ import java.util.Arrays;
 import java.util.Locale;
 import java.util.concurrent.Executor;
 
 import javax.crypto.Mac;
 
 import org.json.simple.JSONObject;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientMalformedResponseException;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
+import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.HKDF;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
 import org.mozilla.gecko.sync.net.Resource;
@@ -201,16 +202,21 @@ public class FxAccountClient10 {
     public AuthHeaderProvider getAuthHeaderProvider() {
       if (tokenId != null && reqHMACKey != null) {
         return new HawkAuthHeaderProvider(Utils.byte2Hex(tokenId), reqHMACKey, payload, skewHandler.getSkewInSeconds());
       }
       return super.getAuthHeaderProvider();
     }
 
     @Override
+    public String getUserAgent() {
+      return FxAccountConstants.USER_AGENT;
+    }
+
+    @Override
     public void handleHttpResponse(HttpResponse response) {
       try {
         final int status = validateResponse(response);
         skewHandler.updateSkew(response, now());
         invokeHandleSuccess(status, response);
       } catch (FxAccountClientRemoteException e) {
         if (!skewHandler.updateSkew(response, now())) {
           // If we couldn't update skew, but we got a failure, let's try clearing the skew.
--- a/mobile/android/base/background/fxa/FxAccountUtils.java
+++ b/mobile/android/base/background/fxa/FxAccountUtils.java
@@ -7,22 +7,26 @@ package org.mozilla.gecko.background.fxa
 import java.io.UnsupportedEncodingException;
 import java.math.BigInteger;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 
+import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.nativecode.NativeCrypto;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.HKDF;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.crypto.PBKDF2;
 
 public class FxAccountUtils {
+  private static final String LOG_TAG = FxAccountUtils.class.getSimpleName();
+
   public static final int SALT_LENGTH_BYTES = 32;
   public static final int SALT_LENGTH_HEX = 2 * SALT_LENGTH_BYTES;
 
   public static final int HASH_LENGTH_BYTES = 16;
   public static final int HASH_LENGTH_HEX = 2 * HASH_LENGTH_BYTES;
 
   public static final int CRYPTO_KEY_LENGTH_BYTES = 32;
   public static final int CRYPTO_KEY_LENGTH_HEX = 2 * CRYPTO_KEY_LENGTH_BYTES;
@@ -101,17 +105,23 @@ public class FxAccountUtils {
    * the plain-text password for any amount of time. Equivalent, but slightly
    * more secure, is the quickly client-side stretched password.
    * <p>
    * We separate this since multiple login-time operations want it, and the
    * PBKDF2 operation is computationally expensive.
    */
   public static byte[] generateQuickStretchedPW(byte[] emailUTF8, byte[] passwordUTF8) throws GeneralSecurityException, UnsupportedEncodingException {
     byte[] S = FxAccountUtils.KWE("quickStretch", emailUTF8);
-    return PBKDF2.pbkdf2SHA256(passwordUTF8, S, NUMBER_OF_QUICK_STRETCH_ROUNDS, 32);
+    try {
+      return NativeCrypto.pbkdf2SHA256(passwordUTF8, S, NUMBER_OF_QUICK_STRETCH_ROUNDS, 32);
+    } catch (Throwable t) {
+      // Important to catch Throwable's; we expressly want to catch UnsatisfiedLinkError instances.
+      Logger.warn(LOG_TAG, "Got throwable stretching password using native pbkdf2SHA256 implementation; ignoring and using Java implementation.", t);
+      return PBKDF2.pbkdf2SHA256(passwordUTF8, S, NUMBER_OF_QUICK_STRETCH_ROUNDS, 32);
+    }
   }
 
   /**
    * The password-derived credential used to authenticate to the Firefox Account
    * auth server.
    */
   public static byte[] generateAuthPW(byte[] quickStretchedPW) throws GeneralSecurityException, UnsupportedEncodingException {
     return HKDF.derive(quickStretchedPW, new byte[0], FxAccountUtils.KW("authPW"), 32);
--- a/mobile/android/base/background/healthreport/HealthReportConstants.java.in
+++ b/mobile/android/base/background/healthreport/HealthReportConstants.java.in
@@ -6,16 +6,18 @@
 package org.mozilla.gecko.background.healthreport;
 
 import org.mozilla.gecko.background.common.GlobalConstants;
 
 public class HealthReportConstants {
   public static final String HEALTH_AUTHORITY = "@ANDROID_PACKAGE_NAME@.health";
   public static final String GLOBAL_LOG_TAG = "GeckoHealth";
 
+  public static final String USER_AGENT = "Firefox-Android-HealthReport/ (" + GlobalConstants.MOZ_APP_DISPLAYNAME + " " + GlobalConstants.MOZ_APP_VERSION + ")";
+
   /**
    * The earliest allowable value for the last ping time, corresponding to May 2nd 2013.
    * Used for sanity checks.
    */
   public static final long EARLIEST_LAST_PING = 1367500000000L;
 
   // Not `final` so we have the option to turn this on at runtime with a magic addon.
   public static boolean UPLOAD_FEATURE_DISABLED = false;
--- a/mobile/android/base/background/healthreport/upload/AndroidSubmissionClient.java
+++ b/mobile/android/base/background/healthreport/upload/AndroidSubmissionClient.java
@@ -200,16 +200,21 @@ public class AndroidSubmissionClient imp
       this.delegate = delegate;
       this.localTime = localTime;
       this.isUpload = isUpload;
       this.methodString = this.isUpload ? "upload" : "delete";
       this.id = id;
     }
 
     @Override
+    public String getUserAgent() {
+      return HealthReportConstants.USER_AGENT;
+    }
+
+    @Override
     public void handleSuccess(int status, String namespace, String id, HttpResponse response) {
       BaseResource.consumeEntity(response);
       if (isUpload) {
         setLastUploadLocalTimeAndDocumentId(localTime, id);
       }
       Logger.debug(LOG_TAG, "Successful " + methodString + " at " + localTime + ".");
       delegate.onSuccess(localTime, id);
     }
--- a/mobile/android/base/browserid/verifier/BrowserIDRemoteVerifierClient.java
+++ b/mobile/android/base/browserid/verifier/BrowserIDRemoteVerifierClient.java
@@ -4,17 +4,16 @@
 
 package org.mozilla.gecko.browserid.verifier;
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierException.BrowserIDVerifierErrorResponseException;
 import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierException.BrowserIDVerifierMalformedResponseException;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.net.BaseResource;
@@ -33,16 +32,21 @@ public class BrowserIDRemoteVerifierClie
     private final BrowserIDVerifierDelegate delegate;
 
     protected RemoteVerifierResourceDelegate(Resource resource, BrowserIDVerifierDelegate delegate) {
       super(resource);
       this.delegate = delegate;
     }
 
     @Override
+    public String getUserAgent() {
+      return null;
+    }
+
+    @Override
     public void handleHttpResponse(HttpResponse response) {
       SyncResponse res = new SyncResponse(response);
       int statusCode = res.getStatusCode();
       Logger.debug(LOG_TAG, "Got response with status code " + statusCode + ".");
 
       if (statusCode != 200) {
         delegate.handleError(new BrowserIDVerifierErrorResponseException("Expected status code 200."));
         return;
--- a/mobile/android/base/fxa/FxAccountConstants.java.in
+++ b/mobile/android/base/fxa/FxAccountConstants.java.in
@@ -1,15 +1,16 @@
 #filter substitution
 /* 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.fxa;
 
+import org.mozilla.gecko.background.common.GlobalConstants;
 import org.mozilla.gecko.background.common.log.Logger;
 
 public class FxAccountConstants {
   public static final String GLOBAL_LOG_TAG = "FxAccounts";
   public static final String ACCOUNT_TYPE = "@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@";
 
   public static final String DEFAULT_AUTH_SERVER_ENDPOINT = "https://api.accounts.firefox.com/v1";
   public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "https://token.services.mozilla.com/1.0/sync/1.5";
@@ -26,9 +27,11 @@ public class FxAccountConstants {
     }
   }
 
   // You must be at least 14 years old to create a Firefox Account.
   public static final int MINIMUM_AGE_TO_CREATE_AN_ACCOUNT = 14;
 
   // You must wait 15 minutes after failing an age check before trying to create a different account.
   public static final long MINIMUM_TIME_TO_WAIT_AFTER_AGE_CHECK_FAILED_IN_MILLISECONDS = 15 * 60 * 1000;
+
+  public static final String USER_AGENT = "Firefox-Android-FxAccounts/ (" + GlobalConstants.MOZ_APP_DISPLAYNAME + " " + GlobalConstants.MOZ_APP_VERSION + ")";
 }
--- a/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java
@@ -9,29 +9,29 @@ import java.util.Map;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.background.fxa.PasswordStretcher;
+import org.mozilla.gecko.background.fxa.QuickPasswordStretcher;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.ProgressDisplay;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Engaged;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.setup.Constants;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
 import android.accounts.AccountManager;
 import android.content.Intent;
 import android.text.Editable;
-import android.text.InputType;
 import android.text.TextWatcher;
 import android.text.method.PasswordTransformationMethod;
 import android.text.method.SingleLineTransformationMethod;
 import android.util.Patterns;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnFocusChangeListener;
@@ -298,9 +298,18 @@ abstract public class FxAccountAbstractS
       setResult(RESULT_OK, intent);
 
       // Show success activity depending on verification status.
       Intent successIntent = makeSuccessIntent(email, result);
       startActivity(successIntent);
       finish();
     }
   }
+
+  /**
+   * Factory function that produces a new PasswordStretcher instance.
+   *
+   * @return PasswordStretcher instance.
+   */
+  protected PasswordStretcher makePasswordStretcher(String password) {
+    return new QuickPasswordStretcher(password);
+  }
 }
--- a/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
@@ -15,17 +15,16 @@ import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
 import org.mozilla.gecko.background.fxa.FxAccountClient;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.background.fxa.PasswordStretcher;
-import org.mozilla.gecko.background.fxa.QuickPasswordStretcher;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountCreateAccountTask;
 
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Bundle;
@@ -191,27 +190,27 @@ public class FxAccountCreateAccountActiv
           public void onClick(DialogInterface dialog, int which) {
             yearEdit.setText(yearItems[which]);
             updateButtonState();
           }
         };
         final AlertDialog dialog = new AlertDialog.Builder(FxAccountCreateAccountActivity.this)
         .setTitle(R.string.fxaccount_create_account_year_of_birth)
         .setItems(yearItems, listener)
-        .setIcon(R.drawable.fxaccount_icon)
+        .setIcon(R.drawable.icon)
         .create();
 
         dialog.show();
       }
     });
   }
 
   public void createAccount(String email, String password, Map<String, Boolean> engines) {
     String serverURI = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT;
-    PasswordStretcher passwordStretcher = new QuickPasswordStretcher(password);
+    PasswordStretcher passwordStretcher = makePasswordStretcher(password);
     // This delegate creates a new Android account on success, opens the
     // appropriate "success!" activity, and finishes this activity.
     RequestDelegate<LoginResponse> delegate = new AddAccountDelegate(email, passwordStretcher, serverURI, engines) {
       @Override
       public void handleError(Exception e) {
         showRemoteError(e, R.string.fxaccount_create_account_unknown_error);
       }
 
--- a/mobile/android/base/fxa/activities/FxAccountSignInActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountSignInActivity.java
@@ -10,17 +10,16 @@ import java.util.concurrent.Executors;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClient;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.background.fxa.PasswordStretcher;
-import org.mozilla.gecko.background.fxa.QuickPasswordStretcher;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -98,17 +97,17 @@ public class FxAccountSignInActivity ext
       return;
     }
     this.setResult(resultCode, data);
     this.finish();
   }
 
   public void signIn(String email, String password) {
     String serverURI = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT;
-    PasswordStretcher passwordStretcher = new QuickPasswordStretcher(password);
+    PasswordStretcher passwordStretcher = makePasswordStretcher(password);
     // This delegate creates a new Android account on success, opens the
     // appropriate "success!" activity, and finishes this activity.
     RequestDelegate<LoginResponse> delegate = new AddAccountDelegate(email, passwordStretcher, serverURI) {
       @Override
       public void handleError(Exception e) {
         showRemoteError(e, R.string.fxaccount_sign_in_unknown_error);
       }
 
--- a/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
@@ -11,17 +11,16 @@ import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClient;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.background.fxa.PasswordStretcher;
-import org.mozilla.gecko.background.fxa.QuickPasswordStretcher;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Engaged;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.login.State.StateLabel;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
@@ -152,17 +151,17 @@ public class FxAccountUpdateCredentialsA
       redirectToActivity(FxAccountStatusActivity.class);
     }
   }
 
   public void updateCredentials(String email, String password) {
     String serverURI = fxAccount.getAccountServerURI();
     Executor executor = Executors.newSingleThreadExecutor();
     FxAccountClient client = new FxAccountClient20(serverURI, executor);
-    PasswordStretcher passwordStretcher = new QuickPasswordStretcher(password);
+    PasswordStretcher passwordStretcher = makePasswordStretcher(password);
     try {
       hideRemoteError();
       RequestDelegate<LoginResponse> delegate = new UpdateCredentialsDelegate(email, passwordStretcher, serverURI);
       new FxAccountSignInTask(this, this, email, passwordStretcher, client, delegate).execute();
     } catch (Exception e) {
       Logger.warn(LOG_TAG, "Got exception updating credentials for account.", e);
       showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
     }
--- a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
@@ -293,16 +293,21 @@ public class FxAccountSyncAdapter extend
                                    final KeyBundle syncKeyBundle,
                                    final String clientState,
                                    final SessionCallback callback,
                                    final Bundle extras) {
     final TokenServerClientDelegate delegate = new TokenServerClientDelegate() {
       private boolean didReceiveBackoff = false;
 
       @Override
+      public String getUserAgent() {
+        return FxAccountConstants.USER_AGENT;
+      }
+
+      @Override
       public void handleSuccess(final TokenServerToken token) {
         FxAccountConstants.pii(LOG_TAG, "Got token! uid is " + token.uid + " and endpoint is " + token.endpoint + ".");
 
         if (!didReceiveBackoff) {
           // We must be OK to touch this token server.
           tokenBackoffHandler.setEarliestNextRequest(0L);
         }
 
--- a/mobile/android/base/locales/en-US/sync_strings.dtd
+++ b/mobile/android/base/locales/en-US/sync_strings.dtd
@@ -100,59 +100,64 @@
 <!ENTITY sync.title.send.tab.label 'Send Tab To Devices'>
 <!ENTITY sync.button.send.label 'Send'>
 <!ENTITY sync.button.set.up.sync.label 'Set up &syncBrand.shortName.label;'>
 <!ENTITY sync.title.redirect.to.set.up.sync.label 'Set up &syncBrand.shortName.label; to send tabs'>
 <!ENTITY sync.text.redirect.to.set.up.sync.label 'Set up &syncBrand.fullName.label; on your device to send tabs to other devices.'>
 <!ENTITY sync.text.tab.sent.label 'Your tab was sent!'>
 <!ENTITY sync.text.tab.not.sent.label 'There was a problem sending your tab.'>
 
-<!-- Firefox account strings. -->
+<!-- Firefox Account strings. -->
 
 <!-- Localization note: these are shown in all screens that query the
      user for an email address and password. Hide and show are button
      labels. -->
 <!ENTITY fxaccount_email_hint 'Email'>
 <!ENTITY fxaccount_password_hint 'Password'>
 <!ENTITY fxaccount_password_hide 'Hide'>
 <!ENTITY fxaccount_password_show 'Show'>
 
 <!-- Localization note: these are shown in screens after the user has
      created or signed in to an account, and take the user back to
      Firefox. -->
 <!ENTITY fxaccount_back_to_browsing 'Back to browsing'>
 
+<!-- Localization note: the following two strings are interpolated
+     into fxaccount_create_account_policy_text2; see note for that
+     string as well.  Compare fxaccount_status_{linktos,linkprivacy}:
+     these strings are separated to accommodate languages that decline
+     the two uses differently. -->
 <!ENTITY fxaccount_policy_linktos 'Terms of Service'>
 <!ENTITY fxaccount_policy_linkprivacy 'Privacy Notice'>
 
 <!ENTITY fxaccount_getting_started_welcome_to_sync 'Welcome to &syncBrand.shortName.label;'>
 <!ENTITY fxaccount_getting_started_description 'Sign in to sync your tabs, bookmarks, passwords &amp; more.'>
 <!ENTITY fxaccount_getting_started_get_started 'Get started'>
 <!ENTITY fxaccount_getting_started_old_firefox 'Using an older version of &syncBrand.shortName.label;?'>
 
 <!-- Localization note: the Firefox below should not change with the
      particular version of Firefox installed (Release, Beta, Aurora,
-     etc).  The account remains a "Firefox" account. -->
-<!ENTITY fxaccount_create_account_header 'Create a Firefox account'>
+     etc).  The account remains a "Firefox Account". -->
+<!ENTITY fxaccount_create_account_header2 'Create a Firefox Account'>
 <!ENTITY fxaccount_create_account_password_length_restriction 'Must be at least 8 characters'>
 <!ENTITY fxaccount_create_account_year_of_birth 'Year of birth'>
 <!-- Localization note: &formatS1; is fxaccount_policy_linktos, &formatS2; is fxaccount_policy_linkprivacy, both hyperlinked. -->
-<!ENTITY fxaccount_create_account_policy_text 'By proceeding, I agree to the &formatS1; and &formatS2; of Firefox online services.'>
+<!ENTITY fxaccount_create_account_policy_text2 'By proceeding, I agree to the &formatS1; and &formatS2; of Firefox cloud services.'>
 <!ENTITY fxaccount_create_account_button 'Next'>
 <!ENTITY fxaccount_create_account_choose_what_to_sync 'Choose what to sync'>
 <!ENTITY fxaccount_create_account_sign_in_instead 'Already have an account? Sign in'>
 <!ENTITY fxaccount_create_account_1990_or_earlier '1990 or earlier'>
 <!ENTITY fxaccount_create_account_unknown_error 'Could not create account'>
 
 <!ENTITY fxaccount_account_create_not_allowed 'Cannot create account'>
 <!ENTITY fxaccount_account_create_not_allowed_you_must_meet_certain_age_requirements 'You must meet certain age requirements to create an account.'>
 <!ENTITY fxaccount_account_create_not_allowed_learn_more 'Learn more'>
 
 <!ENTITY fxaccount_confirm_account_header 'Confirm your account'>
-<!-- Localization note: &formatS; is the Firefox account's email address. -->
+<!-- Localization note: &formatS; is the Firefox Account's email address. -->
 <!ENTITY fxaccount_confirm_account_verification_link 'A verification link has been sent to &formatS;'>
 <!ENTITY fxaccount_confirm_account_resend_email 'Resend email'>
 <!ENTITY fxaccount_confirm_account_verification_link_sent2 'Verification email sent'>
 <!ENTITY fxaccount_confirm_account_verification_link_not_sent2 'Couldn\&apos;t send verification email'>
 
 <!ENTITY fxaccount_sign_in_sub_header 'Sign in'>
 <!ENTITY fxaccount_sign_in_button_label 'Sign in'>
 <!ENTITY fxaccount_sign_in_forgot_password 'Forgot password?'>
@@ -161,38 +166,44 @@
 
 <!ENTITY fxaccount_account_verified_sub_header 'Account verified'>
 <!ENTITY fxaccount_account_verified_description2 'Your data will begin syncing momentarily.'>
 
 <!ENTITY fxaccount_update_credentials_header 'Sign in'>
 <!ENTITY fxaccount_update_credentials_button_label 'Sign in'>
 <!ENTITY fxaccount_update_credentials_unknown_error 'Could not sign in'>
 
-<!ENTITY fxaccount_status_header 'Firefox account'>
+<!ENTITY fxaccount_status_header2 'Firefox Account'>
 <!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
 <!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
 <!ENTITY fxaccount_status_sync_enabled '&syncBrand.shortName.label;: enabled'>
 <!ENTITY fxaccount_status_needs_verification2 'Your account needs to be verified. Tap to resend verification email.'>
 <!ENTITY fxaccount_status_needs_credentials 'Cannot connect. Tap to sign in.'>
 <!ENTITY fxaccount_status_needs_upgrade 'You need to upgrade &brandShortName; to sign in.'>
 <!ENTITY fxaccount_status_bookmarks 'Bookmarks'>
 <!ENTITY fxaccount_status_history 'History'>
 <!ENTITY fxaccount_status_passwords 'Passwords'>
 <!ENTITY fxaccount_status_tabs 'Open tabs'>
 <!ENTITY fxaccount_status_legal 'Legal' >
+<!-- Localization note: when tapped, the following two strings link to
+     external web pages.  Compare fxaccount_policy_{linktos,linkprivacy}:
+     these strings are separated to accommodate languages that decline
+     the two uses differently. -->
+<!ENTITY fxaccount_status_linktos 'Terms of Service'>
+<!ENTITY fxaccount_status_linkprivacy 'Privacy Notice'>
 
 <!-- Localization note: this is the name shown by the Android system
-     itself for a Firefox account. Don't localize this. -->
+     itself for a Firefox Account. Don't localize this. -->
 <!ENTITY fxaccount_account_type_label 'Firefox'>
 
 <!-- Localization note: these are shown by the Android system itself,
      when the user navigates to the Android > Accounts > [Firefox
-     Account] Screen. The link takes the user to the Firefox account
+     Account] Screen. The link takes the user to the Firefox Account
      status activity, which lets them manage their Firefox
-     account. -->
+     Account. -->
 <!ENTITY fxaccount_options_title '&syncBrand.shortName.label; Options'>
 <!ENTITY fxaccount_options_configure_title 'Configure &syncBrand.shortName.label;'>
 
 <!-- Localization note: these error messages are shown after a request
      has been made to the remote server, and an error of some type has
      been returned. -->
 <!ENTITY fxaccount_remote_error_UPGRADE_REQUIRED 'You need to upgrade Firefox'>
 
@@ -203,10 +214,10 @@
 <!ENTITY fxaccount_remote_error_ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT 'Account is not verified'>
 <!ENTITY fxaccount_remote_error_CLIENT_HAS_SENT_TOO_MANY_REQUESTS 'Server busy, try again soon'>
 <!ENTITY fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD 'Server busy, try again soon'>
 <!ENTITY fxaccount_remote_error_UNKNOWN_ERROR 'There was a problem'>
 <!ENTITY fxaccount_remote_error_COULD_NOT_CONNECT 'Cannot connect to network'>
 
 <!ENTITY fxaccount_sync_sign_in_error_notification_title2 '&syncBrand.shortName.label; is not connected'>
 <!-- Note to translators: the format string below will be replaced
-     with the Firefox account's email address. -->
+     with the Firefox Account's email address. -->
 <!ENTITY fxaccount_sync_sign_in_error_notification_text2 'Tap to sign in as &formatS;'>
deleted file mode 100755
index 131f05dd288a4af90f7f0f99decea7de30ab9974..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
old mode 100755
new mode 100644
index dba656bd09b8129980ba9bf9f238f23c7e59c345..2f3868bb9dc4a5a10cb5f1e93c65783eaf59506e
GIT binary patch
literal 3773
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5599f931a917147f9e6737579d994d36ccba0eea
GIT binary patch
literal 2018
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
AR{#J2
deleted file mode 100644
index b6e7595e0bf7d65c88a5dc18f1d4c201ea22b273..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
index 983d8a654a4d06dd66e8dee5888aa93f1235dd25..89caec5c1311077196b308e83d7f748cc0d321a1
GIT binary patch
literal 2810
zc$_^}c|6o#7artiDU>CNXcJk&6v>jZBx9d2Bb5*t+KUhxN*SYshBQ<tdl*tA3ZX2S
zVfNkZmO`>5@qWGA{oLg_=id9A=bX>|<Dz`LT$L2JDuO^DCAZ_wP+-jiMwh${FvAMB
zJO-9^=Z|_G1%Yr`>z6}h0bA-i)YTDG`%PyI1d_J#@$hp2oNaAwa5$X7;Zf<Mcrt@P
zX5dM5003JA(i$!;EoBSD9Roug!5EQB<?_W`;TTsS;Rx0Mh0Em#L_{hFxCj6aPXx9*
zMq`UveBi(smoFKg_)g>SID9dMJ<1VE0MFnco&#JLt{rCc1OPU*bF{EO26TXYjf6kO
z;YolHbT*GjWsOjnBUIKfg$)n_9gqZs9_Ee$n9Ss``Jxdjn>8vVFqs4jlS2~`DXcZA
z6!z-s>J-psXU(nG_{lUjR|rUDa(H64P;74FvX%>9WMSv>Z)P!|hs78DnwtZ{nc2D!
zXe>5w%_K3nj8OrQEI<Z6><UUG4-shqIfcm;iN$0F7nDo}5(IRSY4i<uDJ;Ht(|zid
zc%*LyYYUo6q_Q1719TqHNOU$(rPl-@=r;LrkH8p14|>i1{$pzP*RPPMtHTr~WmK{~
zi`K^!6F8DLBcifFA%iQlcZH_3^NRZg(nzw~D;A3<l1rgDmoUR{oXAFwVk*U=fN`;D
z6gMo?&!NY*a7kQA7hT*&5m{mxHbqPZPm<p&Nc+V5!Vsf6_=7Cb)#g#ZDmG9=o={})
zn0~93*Gv+X4Dh4gaL}FntZqK`tH9v})5$X+`2%lydb+u-<5c)XbWVX-GXAVj0IOjW
zX-w;Na`%BJa_$Dy>jX$n#$IQBadla0>96bs#c8#<739S7_M4?MRIf$LvrEKZH(JdK
zI4b3jFvouz@heIMJ?Yh+gwaNiA6-jDM)ahJUMJ0Q1JoO;{l^q=4R)YU{PR>FT!0tV
z`A<hgI@M48VjNq({|*F_`{Cw%)Gv<y9eY1m%}u=*wozL1<dG#*22x$t+sDLkK4Jcv
zWUOiE_~wV3_ohBhS5<d}Wrs?=qBG`NSHCx48yabz;Qh48YvSd@dJcVFf55Y%LF_x@
zuEp2sNog+O=Qqp{0>f#jc;5jie#Jh211N9w3f<;)ah|wV4ypy-pQo!{SF&!fVtxzq
zycRDHA+3Q?`Vj4!`H!F0-cR{ZV(=Ht`IO72{jd{UMP!M348>y97+JP0ivIUK6j?QS
zWv(79^`7Tfs`#+`Q$}#5DyadAECo}*dju{$8hgsVGc-2dLCeHJgj;1VvJEe&IT2*L
zy=N;TJ!)n*sHutDV_@ONlOw~RTIk5Te!J7pODj&;racfM?mL$o#f^i6>ez}c0m?p5
z?Op1fHY$&WI}H>#&o>p~m_L$tl8w=_!>C88sn?;r@Bh5FvN#$z(!hY}rZ{=4%bMe&
z@9e_#NE=_VkB)nKAQ=^)vtB*){p`*c85{a>mx9WXkiO3^GQL95id*orGMj9X362hT
z!Tv9$ZL?*3EGs`{Y<I5ltO}L-Of}YcRTphP)w{KPz;mwbXeln~9FZ9MY-_@iyBeAI
zn#c!7mYrLDj~ztSx4X2;?JVrI+~yr#@z2!85SN`Hr;kirNQ9)4BqrGSdw0@e3z#Ts
z(1UZGY-@;(N6)RB1e~e{*!0YG6;|bV^rXJZqF#c=na`xiXsQ6AWU%cXIVGb>OCj18
z0+RvT&KwFJ4lrE_-1{*t2db@?O-s*;M`JhYCLSrVxsykpDZ0IG{~XwFE|Fk{&m~X{
z;VqE(6XyD2cfD)+Ssv4+Wv=q@Klpm9rAbMV4u-{>3TsVznQLP>H*ECW)w|7vyc=e<
zJ2`;Gx9uALb3Ajevm5!_d+!nZ6|+_+Nx`Gq8Wl`WJPUm^`*^!lx|~%E60v_F%c}Pj
zTV3Pb4t=HT_TtN_gk?j?CMCvTOPVdS;hVK_W`}SAZ}Y%w;Zat4rt7cd5}LU^?vD@l
zW}B#rARnX|<>$NSUCt)k>@4#|Vx{gCyUByfik4-q#YTU>bTks@OYwC!{ONNK6!xD>
z9k=TWcKOyh->m&dMG_)E*@G=h(^`^;jmO?xiG=L>h_rm)YINUOIceZ;g>#{quX%R5
zn)}lo{ZD88#uV<7(1aGLgtq$i#s&YhEapGkMTuRAlk*L%t-fu|Zo=<YYjr2>BqpFq
zNe}x&Mp-9b9PjUMs>iAu$LiB=p`t=Q#lNII?VaGv<jEhLkvHXWbd3YIFT$S4Opg(#
zp#uT<pLTQbF_|}N=Pl%{2SS9S?3Cg!H}I*~F3q1+-gpat5Ay&mo3(gGP`&OWBIU#J
zxdT7snGqk-8S;mxoF7)htdn&=Wvj$`g<5v$e7<xJv0#3BTkU!(Gd(!4kh&$}U~;N#
zyMW?EAikU}3A4~v7}AABAk30W#nUfLr75X|=1+QddO}q->%Q=e8EsGWzQe5R(^{9X
zTl*hz3Yxn-<U7oEwXc7h>6H97x%Rb7qG^IBvI#{9nh9cAqNY;U?{_pAftr>jxaH^$
zgx)}}8(d=T%TNO^Ag<k!!=2YUZTht?Y;%@9H`aRn)}4^Dec%uqX})xpXHNB5tPTe8
zP=3Oo>~2%e0Ue}AMVfbRU-&bI+!waqUGuLd1fx`#qv{0ePqqLmr-iXBpmjEMIbSS_
zaOf=0FMn0w7dB_2rBPwuxf^#uKajt$OpA=lEgbx2dro@?$-EUq8_4m}<m#M1k@cBa
zcx!so4vpq-XF@Jm4Y9XhxEvT4$wAb4f?GPG@3x%`Oj_5arAdoW{}ti)jXju!vZ+Rd
zqc4VVHMH|CQO#;r%I{n|RXUYESro2vwIeb8L3MS{$KMS%4!pnEJ-=rbAC#&Yfi?;1
zG~jyC>#5qw*RDpkHRdi%Wet8Dzr6q((!qt`-X?vy-2m;p>Ywsz`$Ndo;+e36)%e7{
zj@^3><!+RVtKB!)viVTvMp)F%{Ac3fK+CkMO5u*T3JUxFfax1Nx)g#gguA$ZK~&#y
zhgEKR($ue9?^0;T>bL2rx<?9b?+fUabYH4_)#=^1FUJ2Q3EQ|eIy^sG5IWZD=4P`Q
zN|~cqL8SfN{EQ{{PE(4O%B%ekNDwWsxUqzQ6JuTpiyXtRC!zYcr)IX%idA2~fGEfR
zeYt<HUv2^9=WoO+X4g3V$-Wy?R&P|i-sx|1Q&t)YiBWc+@`J;loC)UPHQzcEe{hV`
zH_$U=H&`Y)IJx|%%>^QyjwZ(+!35V!Js)T03#BOe;(9Se)<|3PN212#>Jn*>n#7p;
zpBA`2f7NcN%tX`?XWWW0{GH8Z|FdI1aiuA}OHGZ%T0<*XZF1ta$eN&{q($w<%Yy_^
zSd7&p>&$y$DbHR<sMd)3rFyEi&(>+EM=OMeB)~IVaE6<^OW^7g*SmkJPOlm&eJ6D`
z$Syy(lT~~EvbYTbKP<h{Zb?7;TX9kQz*AE_soRZKaE&LGY8n1N26KH){!f4o{hx6%
z6z-5~R1g3BUvCU<nX;<wJ~l-)dSq<sYf`tDHKaQcdzy9eH^c|E1doB$k-x%HA%$>`
ziD!+PV%T3=W_|i4%x?4|>QL?ULC!}@L!Zi&koFUK6APM2$uJ(rK-ZzTBI)mp^r$=J
fGNxI@cvaRT(n``l*i8U_8Xz|pFXvjvv#I|AjQi%r
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d734d8be45d0fdbc122800fc4ed72ab2b2b31ee1
GIT binary patch
literal 5061
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
AQvd(}
--- a/mobile/android/base/resources/layout/fxaccount_create_account.xml
+++ b/mobile/android/base/resources/layout/fxaccount_create_account.xml
@@ -38,34 +38,32 @@
             android:hint="@string/fxaccount_create_account_year_of_birth"
             android:inputType="none" />
 
         <TextView
             android:id="@+id/policy"
             style="@style/FxAccountLinkifiedItem"
             android:layout_marginBottom="0dp"
             android:layout_marginTop="10dp"
+            android:layout_marginLeft="10dp"
+            android:layout_marginRight="10dp"
             android:text="@string/fxaccount_create_account_policy_text"
             android:textColorLink="@color/fxaccount_linkified_textColorLinkSubdued" />
 
         <TextView
             android:id="@+id/remote_error"
             style="@style/FxAccountErrorItem" />
 
         <RelativeLayout
             style="@style/FxAccountButtonLayout"
             android:layout_marginBottom="10dp" >
 
             <ProgressBar
                 android:id="@+id/progress"
-                style="@style/FxAccountProgress"
-                android:layout_alignBottom="@id/button"
-                android:layout_alignLeft="@id/button"
-                android:layout_alignRight="@id/button"
-                android:layout_alignTop="@id/button" />
+                style="@style/FxAccountProgress" />
 
             <Button
                 android:id="@+id/button"
                 style="@style/FxAccountButton"
                 android:text="@string/fxaccount_create_account_button" />
         </RelativeLayout>
 
         <CheckBox
@@ -86,9 +84,9 @@
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <ImageView
             style="@style/FxAccountIcon"
             android:contentDescription="@string/fxaccount_empty_contentDescription" />
     </LinearLayout>
 
-</ScrollView>
+</ScrollView>
\ No newline at end of file
--- a/mobile/android/base/resources/layout/fxaccount_sign_in.xml
+++ b/mobile/android/base/resources/layout/fxaccount_sign_in.xml
@@ -25,21 +25,17 @@
         <TextView
             android:id="@+id/remote_error"
             style="@style/FxAccountErrorItem" />
 
         <RelativeLayout style="@style/FxAccountButtonLayout" >
 
             <ProgressBar
                 android:id="@+id/progress"
-                style="@style/FxAccountProgress"
-                android:layout_alignBottom="@id/button"
-                android:layout_alignLeft="@id/button"
-                android:layout_alignRight="@id/button"
-                android:layout_alignTop="@id/button" />
+                style="@style/FxAccountProgress" />
 
             <Button
                 android:id="@+id/button"
                 style="@style/FxAccountButton"
                 android:text="@string/fxaccount_sign_in_button_label" />
         </RelativeLayout>
 
         <LinearLayout
@@ -64,16 +60,18 @@
                 android:gravity="right"
                 android:text="@string/fxaccount_sign_in_create_account_instead" />
         </LinearLayout>
 
         <TextView
             android:id="@+id/policy"
             style="@style/FxAccountLinkifiedItem"
             android:layout_marginTop="10dp"
+            android:layout_marginLeft="10dp"
+            android:layout_marginRight="10dp"
             android:text="@string/fxaccount_create_account_policy_text"
             android:textColorLink="@color/fxaccount_linkified_textColorLinkSubdued" />
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <ImageView
             style="@style/FxAccountIcon"
             android:contentDescription="@string/fxaccount_empty_contentDescription" />
--- a/mobile/android/base/resources/layout/fxaccount_update_credentials.xml
+++ b/mobile/android/base/resources/layout/fxaccount_update_credentials.xml
@@ -25,21 +25,17 @@
         <TextView
             android:id="@+id/remote_error"
             style="@style/FxAccountErrorItem" />
 
         <RelativeLayout style="@style/FxAccountButtonLayout" >
 
             <ProgressBar
                 android:id="@+id/progress"
-                style="@style/FxAccountProgress"
-                android:layout_alignBottom="@id/button"
-                android:layout_alignLeft="@id/button"
-                android:layout_alignRight="@id/button"
-                android:layout_alignTop="@id/button" />
+                style="@style/FxAccountProgress" />
 
             <Button
                 android:id="@+id/button"
                 style="@style/FxAccountButton"
                 android:text="@string/fxaccount_update_credentials_button_label" />
         </RelativeLayout>
 
         <TextView
--- a/mobile/android/base/resources/values/fxaccount_colors.xml
+++ b/mobile/android/base/resources/values/fxaccount_colors.xml
@@ -1,36 +1,35 @@
 <?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/. -->
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
   <color name="fxaccount_textColor">#424f59</color>
-  <color name="fxaccount_textColorSubdued">#c0c9d0</color>
+  <color name="fxaccount_textColorSubdued">#8a9ba8</color>
 
-  <color name="fxaccount_linkified_textColor">#c0c9d0</color>
-  <color name="fxaccount_linkified_textColorLink">#0096dd</color>
-  <color name="fxaccount_linkified_textColorLinkSubdued">#c0c9d0</color>
+  <color name="fxaccount_linkified_textColor">#8a9ba8</color>
+  <color name="fxaccount_linkified_textColorLink">#0095dd</color>
+  <color name="fxaccount_linkified_textColorLinkSubdued">#8a9ba8</color>
 
   <color name="fxaccount_error">#d63920</color>
 
   <color name="fxaccount_error_preference_backgroundcolor">#fad4d2</color>
 
   <color name="fxaccount_button_textColor">#ffffff</color>
-  <color name="fxaccount_button_background_active">#e66000</color>
-  <color name="fxaccount_button_background_hit">#fd9500</color>
-  <color name="fxaccount_button_background_loading">#424f59</color>
-  <color name="fxaccount_button_background_inactive">#c0c9d0</color>
+  <color name="fxaccount_button_background_active">#0095dd</color>
+  <color name="fxaccount_button_background_hit">#0088cc</color>
+  <color name="fxaccount_button_background_inactive">#8a9ba8</color>
   <color name="fxaccount_input_textColor">#424f59</color>
   <color name="fxaccount_input_textColor_pressed">#424f59</color>
-  <color name="fxaccount_input_textColor_inactive">#c0c9d0</color>
-  <color name="fxaccount_input_textColorHint">#c0c9d0</color>
-  <color name="fxaccount_link_textColor">#0096dd</color>
-  <color name="fxaccount_link_textColor_pressed">#00767d</color>
-  <color name="fxaccount_link_textColor_inactive">#c0c9d0</color>
-  <color name="fxaccount_input_borderActive">#6a7b86</color>
-  <color name="fxaccount_input_borderInactive">#c0c9d0</color>
-  <color name="fxaccount_password_show_textcolor">#c0c9d0</color>
+  <color name="fxaccount_input_textColor_inactive">#8a9ba8</color>
+  <color name="fxaccount_input_textColorHint">#8a9ba8</color>
+  <color name="fxaccount_link_textColor">#0095dd</color>
+  <color name="fxaccount_link_textColor_pressed">#0088cc</color>
+  <color name="fxaccount_link_textColor_inactive">#8a9ba8</color>
+  <color name="fxaccount_input_borderActive">#0095dd</color>
+  <color name="fxaccount_input_borderInactive">#8a9ba8</color>
+  <color name="fxaccount_password_show_textcolor">#8a9ba8</color>
   <color name="fxaccount_password_show_backgroundcolor">#ffffff</color>
   <color name="fxaccount_password_hide_textcolor">#ffffff</color>
-  <color name="fxaccount_password_hide_backgroundcolor">#424f59</color>
+  <color name="fxaccount_password_hide_backgroundcolor">#8a9ba8</color>
 </resources>
--- a/mobile/android/base/resources/values/fxaccount_dimens.xml
+++ b/mobile/android/base/resources/values/fxaccount_dimens.xml
@@ -1,17 +1,23 @@
 <?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/. -->
 
 <resources>
   <dimen name="fxaccount_stroke_width">1dp</dimen>
   <dimen name="fxaccount_corner_radius">5dp</dimen>
-  <dimen name="fxaccount_input_padding">10dp</dimen>
+
+  <!-- The amount of horizontal padding that appears inside the email,
+       password, etc input fields. -->
+  <dimen name="fxaccount_input_padding_horizontal">20dp</dimen>
+  <!-- And the amount of vertical padding that appears inside input
+       fields. -->
+  <dimen name="fxaccount_input_padding_vertical">10dp</dimen>
 
   <!-- Preference fragment padding, bottom -->
   <dimen name="preference_fragment_padding_bottom">0dp</dimen>
   <!-- Preference fragment padding, sides -->
   <dimen name="preference_fragment_padding_side">16dp</dimen>
 
   <integer name="preference_fragment_scrollbarStyle">0x02000000</integer> <!-- outsideOverlay -->
 </resources>
--- a/mobile/android/base/resources/values/fxaccount_styles.xml
+++ b/mobile/android/base/resources/values/fxaccount_styles.xml
@@ -5,17 +5,17 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/.
 -->
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="FxAccountTheme" parent="@style/Gecko" />
 
     <style name="FxAccountTheme.FxAccountStatusActivity" parent="@style/FxAccountTheme">
-         <item name="android:windowNoTitle">false</item>
+        <item name="android:windowNoTitle">false</item>
     </style>
 
     <style name="FxAccountMiddle">
         <item name="android:background">@android:color/white</item>
         <item name="android:orientation">vertical</item>
         <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_weight">1</item>
@@ -59,17 +59,20 @@
         <item name="android:padding">20dp</item>
         <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_marginBottom">10dp</item>
     </style>
 
     <style name="FxAccountEditItem" parent="@android:style/Widget.EditText">
         <item name="android:textSize">20sp</item>
-        <item name="android:padding">@dimen/fxaccount_input_padding</item>
+        <item name="android:paddingLeft">@dimen/fxaccount_input_padding_horizontal</item>
+        <item name="android:paddingRight">@dimen/fxaccount_input_padding_horizontal</item>
+        <item name="android:paddingTop">@dimen/fxaccount_input_padding_vertical</item>
+        <item name="android:paddingBottom">@dimen/fxaccount_input_padding_vertical</item>
         <item name="android:background">@drawable/fxaccount_textfield_background</item>
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:singleLine">true</item>
         <item name="android:textColor">@color/fxaccount_input_textColor</item>
         <item name="android:textColorHint">@color/fxaccount_input_textColorHint</item>
     </style>
 
@@ -83,17 +86,17 @@
         <item name="android:gravity">center</item>
     </style>
 
     <style name="FxAccountIcon">
         <item name="android:layout_marginTop">20dp</item>
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_gravity">center_horizontal</item>
-        <item name="android:src">@drawable/fxaccount_icon</item>
+        <item name="android:src">@drawable/icon</item>
     </style>
 
     <style name="FxAccountErrorItem">
         <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_marginBottom">10dp</item>
         <item name="android:layout_marginTop">10dp</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:gravity">center_horizontal</item>
@@ -102,28 +105,29 @@
         <item name="android:text">Error</item>
         <item name="android:textColor">@android:color/white</item>
         <item name="android:textColorLink">@android:color/white</item>
         <item name="android:textSize">18sp</item>
         <item name="android:visibility">invisible</item>
     </style>
 
     <style name="FxAccountProgress">
-        <item name="android:layout_width">fill_parent</item>
-        <item name="android:layout_height">fill_parent</item>
-        <item name="android:background">@drawable/fxaccount_button_background</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_centerInParent">true</item>
         <item name="android:visibility">invisible</item>
     </style>
 
     <style name="FxAccountButtonLayout">
         <item name="android:orientation">vertical</item>
         <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">wrap_content</item>
+        <item name="android:background">@drawable/fxaccount_button_background</item>
     </style>
 
     <style name="FxAccountCheckBox">
         <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_marginBottom">10dp</item>
         <item name="android:textColor">@drawable/fxaccount_checkbox_textcolor</item>
     </style>
 
-</resources>
+</resources>
\ No newline at end of file
--- a/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
+++ b/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
@@ -51,24 +51,24 @@
         <CheckBoxPreference
             android:key="passwords"
             android:persistent="false"
             android:title="@string/fxaccount_status_passwords" />
     </PreferenceCategory>
     <PreferenceCategory
         android:key="legal_category"
         android:title="@string/fxaccount_status_legal" >
-        <Preference android:title="@string/fxaccount_policy_linktos" >
+        <Preference android:title="@string/fxaccount_status_linktos" >
             <intent
                 android:action="android.intent.action.VIEW"
                 android:data="@string/fxaccount_link_tos"
                 android:targetClass="@string/browser_intent_class"
                 android:targetPackage="@string/browser_intent_package" />
         </Preference>
-        <Preference android:title="@string/fxaccount_policy_linkprivacy" >
+        <Preference android:title="@string/fxaccount_status_linkprivacy" >
             <intent
                 android:action="android.intent.action.VIEW"
                 android:data="@string/fxaccount_link_pn"
                 android:targetClass="@string/browser_intent_class"
                 android:targetPackage="@string/browser_intent_package" />
         </Preference>
     </PreferenceCategory>
     <PreferenceCategory
--- a/mobile/android/base/sync/SyncConstants.java.in
+++ b/mobile/android/base/sync/SyncConstants.java.in
@@ -13,19 +13,19 @@ import org.mozilla.gecko.background.comm
 public class SyncConstants {
   public static final String GLOBAL_LOG_TAG = "FxSync";
   public static final String SYNC_MAJOR_VERSION  = "1";
   public static final String SYNC_MINOR_VERSION  = "0";
   public static final String SYNC_VERSION_STRING = SYNC_MAJOR_VERSION + "." +
                                                    GlobalConstants.MOZ_APP_VERSION + "." +
                                                    SYNC_MINOR_VERSION;
 
-  public static final String SYNC_USER_AGENT = "Firefox AndroidSync " +
-                                               SYNC_VERSION_STRING + " (" +
-                                               GlobalConstants.MOZ_APP_DISPLAYNAME + ")";
+  public static final String USER_AGENT = "Firefox AndroidSync " +
+                                          SYNC_VERSION_STRING + " (" +
+                                          GlobalConstants.MOZ_APP_DISPLAYNAME + ")";
 
   public static final String ACCOUNTTYPE_SYNC = "@MOZ_ANDROID_SHARED_ACCOUNT_TYPE@";
 
   /**
    * Bug 790931: this action is broadcast when an Android Sync Account is
    * deleted.  This allows each installed Firefox to delete any Sync Account
    * pickle file and to (try to) wipe its client record from the Sync server.
    * <p>
--- a/mobile/android/base/sync/jpake/stage/DeleteChannel.java
+++ b/mobile/android/base/sync/jpake/stage/DeleteChannel.java
@@ -4,16 +4,17 @@
 
 package org.mozilla.gecko.sync.jpake.stage;
 
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.sync.SyncConstants;
 import org.mozilla.gecko.sync.jpake.JPakeClient;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 import org.mozilla.gecko.sync.setup.auth.AccountAuthenticator;
 
 import ch.boye.httpclientandroidlib.HttpResponse;
 import ch.boye.httpclientandroidlib.client.ClientProtocolException;
 import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
@@ -31,16 +32,20 @@ public class DeleteChannel {
     try {
       httpResource = new BaseResource(jClient.channelUrl);
     } catch (URISyntaxException e) {
       Logger.debug(LOG_TAG, "Encountered URISyntax exception, displaying abort anyway.");
       jClient.displayAbort(reason);
       return;
     }
     httpResource.delegate = new BaseResourceDelegate(httpResource) {
+      @Override
+      public String getUserAgent() {
+        return SyncConstants.USER_AGENT;
+      }
 
       @Override
       public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
         request.setHeader(new BasicHeader(KEYEXCHANGE_ID_HEADER,  jClient.clientId));
         request.setHeader(new BasicHeader(KEYEXCHANGE_CID_HEADER, jClient.channel));
       }
 
       @Override
--- a/mobile/android/base/sync/jpake/stage/GetChannelStage.java
+++ b/mobile/android/base/sync/jpake/stage/GetChannelStage.java
@@ -5,16 +5,17 @@
 package org.mozilla.gecko.sync.jpake.stage;
 
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 
 import org.json.simple.parser.JSONParser;
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.sync.SyncConstants;
 import org.mozilla.gecko.sync.jpake.JPakeClient;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 import org.mozilla.gecko.sync.net.SyncResponse;
 import org.mozilla.gecko.sync.setup.Constants;
 
 import ch.boye.httpclientandroidlib.HttpResponse;
 import ch.boye.httpclientandroidlib.client.ClientProtocolException;
@@ -75,16 +76,20 @@ public class GetChannelStage extends JPa
       jClient.abort(Constants.JPAKE_ERROR_CHANNEL);
       return;
     }
   }
 
   private void makeChannelRequest(final GetChannelStageDelegate callbackDelegate, String getChannelUrl, final String clientId) throws URISyntaxException {
     final BaseResource httpResource = new BaseResource(getChannelUrl);
     httpResource.delegate = new BaseResourceDelegate(httpResource) {
+      @Override
+      public String getUserAgent() {
+        return SyncConstants.USER_AGENT;
+      }
 
       @Override
       public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
         request.setHeader(new BasicHeader("X-KeyExchange-Id", clientId));
       }
 
       @Override
       public void handleHttpResponse(HttpResponse response) {
--- a/mobile/android/base/sync/jpake/stage/GetRequestStage.java
+++ b/mobile/android/base/sync/jpake/stage/GetRequestStage.java
@@ -6,16 +6,17 @@ package org.mozilla.gecko.sync.jpake.sta
 
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 import java.util.Timer;
 import java.util.TimerTask;
 
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.sync.SyncConstants;
 import org.mozilla.gecko.sync.jpake.JPakeClient;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 import org.mozilla.gecko.sync.net.Resource;
 import org.mozilla.gecko.sync.net.SyncResponse;
 import org.mozilla.gecko.sync.setup.Constants;
 
 import ch.boye.httpclientandroidlib.Header;
@@ -96,16 +97,20 @@ public class GetRequestStage extends JPa
     Logger.debug(LOG_TAG, "Scheduling GET request.");
     getStepTimerTask = new GetStepTimerTask(httpRequest);
     timerScheduler.schedule(getStepTimerTask, jClient.jpakePollInterval);
   }
 
   private Resource createGetRequest(final GetRequestStageDelegate callbackDelegate, final JPakeClient jpakeClient) throws URISyntaxException {
     BaseResource httpResource = new BaseResource(jpakeClient.channelUrl);
     httpResource.delegate = new BaseResourceDelegate(httpResource) {
+      @Override
+      public String getUserAgent() {
+        return SyncConstants.USER_AGENT;
+      }
 
       @Override
       public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
         request.setHeader(new BasicHeader("X-KeyExchange-Id", jpakeClient.clientId));
         if (jpakeClient.myEtag != null) {
           request.setHeader(new BasicHeader("If-None-Match", jpakeClient.myEtag));
         }
       }
--- a/mobile/android/base/sync/jpake/stage/PutRequestStage.java
+++ b/mobile/android/base/sync/jpake/stage/PutRequestStage.java
@@ -7,20 +7,21 @@ package org.mozilla.gecko.sync.jpake.sta
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 import java.util.Timer;
 import java.util.TimerTask;
 
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.sync.SyncConstants;
 import org.mozilla.gecko.sync.jpake.JPakeClient;
 import org.mozilla.gecko.sync.net.BaseResource;
+import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 import org.mozilla.gecko.sync.net.Resource;
-import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 import org.mozilla.gecko.sync.setup.Constants;
 
 import ch.boye.httpclientandroidlib.Header;
 import ch.boye.httpclientandroidlib.HttpResponse;
 import ch.boye.httpclientandroidlib.client.ClientProtocolException;
 import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
 import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
 import ch.boye.httpclientandroidlib.message.BasicHeader;
@@ -87,16 +88,20 @@ public class PutRequestStage extends JPa
       return;
     }
     Logger.debug(LOG_TAG, "Outgoing message: " + jClient.jOutgoing.toJSONString());
   }
 
   private Resource createPutRequest(final PutRequestStageDelegate callbackDelegate, final JPakeClient jpakeClient) throws URISyntaxException {
     BaseResource httpResource = new BaseResource(jpakeClient.channelUrl);
     httpResource.delegate = new BaseResourceDelegate(httpResource) {
+      @Override
+      public String getUserAgent() {
+        return SyncConstants.USER_AGENT;
+      }
 
       @Override
       public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
         request.setHeader(new BasicHeader("X-KeyExchange-Id", jpakeClient.clientId));
         if (jpakeClient.theirEtag != null) {
           request.setHeader(new BasicHeader("If-Match", jpakeClient.theirEtag));
         } else {
           request.setHeader(new BasicHeader("If-None-Match", "*"));
--- a/mobile/android/base/sync/net/BaseResource.java
+++ b/mobile/android/base/sync/net/BaseResource.java
@@ -172,16 +172,20 @@ public class BaseResource implements Res
     addAuthCacheToContext(request, context);
 
     HttpParams params = client.getParams();
     HttpConnectionParams.setConnectionTimeout(params, delegate.connectionTimeout());
     HttpConnectionParams.setSoTimeout(params, delegate.socketTimeout());
     HttpConnectionParams.setStaleCheckingEnabled(params, false);
     HttpProtocolParams.setContentCharset(params, charset);
     HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
+    final String userAgent = delegate.getUserAgent();
+    if (userAgent != null) {
+      HttpProtocolParams.setUserAgent(params, userAgent);
+    }
     delegate.addHeaders(request, client);
   }
 
   private static Object connManagerMonitor = new Object();
   private static ClientConnectionManager connManager;
 
   // Call within a synchronized block on connManagerMonitor.
   private static ClientConnectionManager enableTLSConnectionManager() throws KeyManagementException, NoSuchAlgorithmException  {
--- a/mobile/android/base/sync/net/ResourceDelegate.java
+++ b/mobile/android/base/sync/net/ResourceDelegate.java
@@ -21,16 +21,23 @@ import ch.boye.httpclientandroidlib.impl
  * @author rnewman
  *
  */
 public interface ResourceDelegate {
   // Request augmentation.
   AuthHeaderProvider getAuthHeaderProvider();
   void addHeaders(HttpRequestBase request, DefaultHttpClient client);
 
+  /**
+   * The value of the User-Agent header to include with the request.
+   *
+   * @return User-Agent header value; null means do not set User-Agent header.
+   */
+  public String getUserAgent();
+
   // Response handling.
 
   /**
    * Override this to handle an HttpResponse.
    *
    * ResourceDelegate implementers <b>must</b> ensure that HTTP responses are
    * fully consumed to ensure that connections are returned to the pool, for
    * example by calling <code>EntityUtils.consume(response.getEntity())</code>.
--- a/mobile/android/base/sync/net/SyncStorageRequest.java
+++ b/mobile/android/base/sync/net/SyncStorageRequest.java
@@ -13,17 +13,16 @@ import java.util.HashMap;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.SyncConstants;
 
 import ch.boye.httpclientandroidlib.HttpEntity;
 import ch.boye.httpclientandroidlib.HttpResponse;
 import ch.boye.httpclientandroidlib.client.ClientProtocolException;
 import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
 import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.params.CoreProtocolPNames;
 
 public class SyncStorageRequest implements Resource {
   public static HashMap<String, String> SERVER_ERROR_MESSAGES;
   static {
     HashMap<String, String> errors = new HashMap<String, String>();
 
     // Sync protocol errors.
     errors.put("1", "Illegal method/protocol");
@@ -106,16 +105,21 @@ public class SyncStorageRequest implemen
     }
 
     @Override
     public AuthHeaderProvider getAuthHeaderProvider() {
       return request.delegate.getAuthHeaderProvider();
     }
 
     @Override
+    public String getUserAgent() {
+      return SyncConstants.USER_AGENT;
+    }
+
+    @Override
     public void handleHttpResponse(HttpResponse response) {
       Logger.debug(LOG_TAG, "SyncStorageResourceDelegate handling response: " + response.getStatusLine() + ".");
       SyncStorageRequestDelegate d = this.request.delegate;
       SyncStorageResponse res = new SyncStorageResponse(response);
       // It is the responsibility of the delegate handlers to completely consume the response.
       if (res.wasSuccessful()) {
         d.handleRequestSuccess(res);
       } else {
@@ -141,18 +145,16 @@ public class SyncStorageRequest implemen
 
     @Override
     public void handleTransportException(GeneralSecurityException e) {
       this.request.delegate.handleRequestError(e);
     }
 
     @Override
     public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
-      client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, SyncConstants.SYNC_USER_AGENT);
-
       // Clients can use their delegate interface to specify X-If-Unmodified-Since.
       String ifUnmodifiedSince = this.request.delegate.ifUnmodifiedSince();
       if (ifUnmodifiedSince != null) {
         Logger.debug(LOG_TAG, "Making request with X-If-Unmodified-Since = " + ifUnmodifiedSince);
         request.setHeader("x-if-unmodified-since", ifUnmodifiedSince);
       }
       if (request.getMethod().equalsIgnoreCase("DELETE")) {
         request.addHeader("x-confirm-delete", "1");
@@ -168,24 +170,28 @@ public class SyncStorageRequest implemen
     super();
   }
 
   // Default implementation. Override this.
   protected BaseResourceDelegate makeResourceDelegate(SyncStorageRequest request) {
     return new SyncStorageResourceDelegate(request);
   }
 
+  @Override
   public void get() {
     this.resource.get();
   }
 
+  @Override
   public void delete() {
     this.resource.delete();
   }
 
+  @Override
   public void post(HttpEntity body) {
     this.resource.post(body);
   }
 
+  @Override
   public void put(HttpEntity body) {
     this.resource.put(body);
   }
 }
--- a/mobile/android/base/sync/setup/auth/AuthenticateAccountStage.java
+++ b/mobile/android/base/sync/setup/auth/AuthenticateAccountStage.java
@@ -91,23 +91,26 @@ public class AuthenticateAccountStage im
    * @param authRequestUrl
    * @param authHeader
    * @throws URISyntaxException
    */
   // Made public for testing.
   public void authenticateAccount(final AuthenticateAccountStageDelegate callbackDelegate, final String authRequestUrl, final String authHeader) throws URISyntaxException {
     final BaseResource httpResource = new BaseResource(authRequestUrl);
     httpResource.delegate = new BaseResourceDelegate(httpResource) {
+      @Override
+      public String getUserAgent() {
+        return SyncConstants.USER_AGENT;
+      }
 
       @Override
       public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
         // Make reference to request, to abort if necessary.
         httpRequest = request;
         client.log.enableDebug(true);
-        request.setHeader(new BasicHeader("User-Agent", SyncConstants.SYNC_USER_AGENT));
         // Host header is not set for some reason, so do it explicitly.
         try {
           URI authServerUri = new URI(authRequestUrl);
           request.setHeader(new BasicHeader("Host", authServerUri.getHost()));
         } catch (URISyntaxException e) {
           Logger.error(LOG_TAG, "Malformed uri, will be caught elsewhere.", e);
         }
         request.setHeader(new BasicHeader("Authorization", authHeader));
--- a/mobile/android/base/sync/setup/auth/EnsureUserExistenceStage.java
+++ b/mobile/android/base/sync/setup/auth/EnsureUserExistenceStage.java
@@ -8,16 +8,17 @@ import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.sync.SyncConstants;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 
 import ch.boye.httpclientandroidlib.HttpResponse;
 import ch.boye.httpclientandroidlib.client.ClientProtocolException;
 
 public class EnsureUserExistenceStage implements AuthenticatorStage {
   private final String LOG_TAG = "EnsureUserExistence";
@@ -51,16 +52,20 @@ public class EnsureUserExistenceStage im
       }
 
     };
 
     // This is not the same as Utils.nodeWeaveURL: it's missing the trailing node/weave.
     String userRequestUrl = aa.nodeServer + "user/1.0/" + aa.username;
     final BaseResource httpResource = new BaseResource(userRequestUrl);
     httpResource.delegate = new BaseResourceDelegate(httpResource) {
+      @Override
+      public String getUserAgent() {
+        return SyncConstants.USER_AGENT;
+      }
 
       @Override
       public void handleHttpResponse(HttpResponse response) {
         int statusCode = response.getStatusLine().getStatusCode();
         switch(statusCode) {
         case 200:
           try {
             InputStream content = response.getEntity().getContent();
--- a/mobile/android/base/sync/setup/auth/FetchUserNodeStage.java
+++ b/mobile/android/base/sync/setup/auth/FetchUserNodeStage.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko.sync.setup.aut
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.sync.SyncConstants;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 
 import ch.boye.httpclientandroidlib.HttpResponse;
 import ch.boye.httpclientandroidlib.client.ClientProtocolException;
 
 public class FetchUserNodeStage implements AuthenticatorStage {
@@ -73,16 +74,20 @@ public class FetchUserNodeStage implemen
       }
     });
   }
 
   private BaseResource makeFetchNodeRequest(final FetchNodeStageDelegate callbackDelegate, String fetchNodeUrl) throws URISyntaxException {
     // Fetch node containing user.
     final BaseResource httpResource = new BaseResource(fetchNodeUrl);
     httpResource.delegate = new BaseResourceDelegate(httpResource) {
+      @Override
+      public String getUserAgent() {
+        return SyncConstants.USER_AGENT;
+      }
 
       @Override
       public void handleHttpResponse(HttpResponse response) {
         int statusCode = response.getStatusLine().getStatusCode();
         switch(statusCode) {
         case 200:
           try {
             InputStream content = response.getEntity().getContent();
--- a/mobile/android/base/sync/stage/EnsureClusterURLStage.java
+++ b/mobile/android/base/sync/stage/EnsureClusterURLStage.java
@@ -10,16 +10,17 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.NodeAuthenticationException;
 import org.mozilla.gecko.sync.NullClusterURLException;
+import org.mozilla.gecko.sync.SyncConstants;
 import org.mozilla.gecko.sync.ThreadPool;
 import org.mozilla.gecko.sync.delegates.NodeAssignmentCallback;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 
 import ch.boye.httpclientandroidlib.HttpEntity;
 import ch.boye.httpclientandroidlib.HttpResponse;
 import ch.boye.httpclientandroidlib.client.ClientProtocolException;
@@ -71,16 +72,20 @@ public class EnsureClusterURLStage exten
    * @throws URISyntaxException
    */
   public static void fetchClusterURL(final String nodeWeaveURL,
                                      final ClusterURLFetchDelegate delegate) throws URISyntaxException {
     Logger.info(LOG_TAG, "In fetchClusterURL: node/weave is " + nodeWeaveURL);
 
     BaseResource resource = new BaseResource(nodeWeaveURL);
     resource.delegate = new BaseResourceDelegate(resource) {
+      @Override
+      public String getUserAgent() {
+        return SyncConstants.USER_AGENT;
+      }
 
       /**
        * Handle the response for GET https://server/pathname/version/username/node/weave.
        *
        * Returns the Sync Node that the client is located on.
        * Storage operations should be directed to that node.
        *
        * Return value: the node URL, an unadorned (not JSON) string.
@@ -174,16 +179,17 @@ public class EnsureClusterURLStage exten
       public void handleTransportException(GeneralSecurityException e) {
         delegate.handleError(e);
       }
     };
 
     resource.get();
   }
 
+  @Override
   public void execute() throws NoSuchStageException {
     final URI oldClusterURL = session.config.getClusterURL();
     final boolean wantNodeAssignment = callback.wantNodeAssignment();
 
     if (!wantNodeAssignment && oldClusterURL != null) {
       Logger.info(LOG_TAG, "Cluster URL is already set and not stale. Continuing with sync.");
       session.advance();
       return;
--- a/mobile/android/base/tokenserver/TokenServerClient.java
+++ b/mobile/android/base/tokenserver/TokenServerClient.java
@@ -252,16 +252,21 @@ public class TokenServerClient {
       this.delegate = delegate;
       this.assertion = assertion;
       this.clientState = clientState;
       this.resource = resource;
       this.conditionsAccepted = conditionsAccepted;
     }
 
     @Override
+    public String getUserAgent() {
+      return delegate.getUserAgent();
+    }
+
+    @Override
     public void handleHttpResponse(HttpResponse response) {
       // Skew.
       SkewHandler skewHandler = SkewHandler.getSkewHandlerForResource(resource);
       skewHandler.updateSkew(response, System.currentTimeMillis());
 
       // Extract backoff regardless of whether this was an error response, and
       // Retry-After for 503 responses. The error will be handled elsewhere.)
       SyncResponse res = new SyncResponse(response);
--- a/mobile/android/base/tokenserver/TokenServerClientDelegate.java
+++ b/mobile/android/base/tokenserver/TokenServerClientDelegate.java
@@ -1,16 +1,19 @@
 /* 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.tokenserver;
 
+
 public interface TokenServerClientDelegate {
   void handleSuccess(TokenServerToken token);
   void handleFailure(TokenServerException e);
   void handleError(Exception e);
 
   /**
    * Might be called multiple times, in addition to the other terminating handler methods.
    */
   void handleBackoff(int backoffSeconds);
-}
\ No newline at end of file
+
+  public String getUserAgent();
+}
--- a/mobile/android/services/strings.xml.in
+++ b/mobile/android/services/strings.xml.in
@@ -96,19 +96,19 @@
   <string name="sync_title_redirect_to_set_up_sync">&sync.title.redirect.to.set.up.sync.label;</string>
   <string name="sync_text_redirect_to_set_up_sync">&sync.text.redirect.to.set.up.sync.label;</string>
   <string name="sync_text_tab_sent">&sync.text.tab.sent.label;</string>
   <string name="sync_text_tab_not_sent">&sync.text.tab.not.sent.label;</string>
 
 <string name="browser_intent_package">@ANDROID_PACKAGE_NAME@</string>
 <string name="browser_intent_class">@ANDROID_PACKAGE_NAME@.App</string>
 
-<!-- Firefox account strings. -->
+<!-- Firefox Account strings. -->
 
-<!-- Firefox account links. -->
+<!-- Firefox Account links. -->
 <!-- https://support.mozilla.org/1/mobile/%VERSION%/%OS%/%LOCALE%/old-sync -->
 <string name="fxaccount_link_old_firefox">https://support.mozilla.org/1/mobile/&formatS1;/&formatS2;/&formatS3;/old-sync</string>
 <string name="fxaccount_link_create_not_allowed">http://www.consumer.ftc.gov/articles/0031-protecting-your-childs-privacy-online</string>
 <string name="fxaccount_link_tos">https://accounts.firefox.com/legal/terms</string>
 <string name="fxaccount_link_pn">https://accounts.firefox.com/legal/privacy</string>
 <string name="fxaccount_link_forgot_password">https://accounts.firefox.com/reset_password</string>
 
 <!-- Per Bug 974627, decorative images should not have non-empty
@@ -125,21 +125,21 @@
 <string name="fxaccount_policy_linktos">&fxaccount_policy_linktos;</string>
 <string name="fxaccount_policy_linkprivacy">&fxaccount_policy_linkprivacy;</string>
 
 <string name="fxaccount_getting_started_welcome_to_sync">&fxaccount_getting_started_welcome_to_sync;</string>
 <string name="fxaccount_getting_started_description">&fxaccount_getting_started_description;</string>
 <string name="fxaccount_getting_started_get_started">&fxaccount_getting_started_get_started;</string>
 <string name="fxaccount_getting_started_old_firefox">&fxaccount_getting_started_old_firefox;</string>
 
-<string name="fxaccount_create_account_header">&fxaccount_create_account_header;</string>
+<string name="fxaccount_create_account_header">&fxaccount_create_account_header2;</string>
 <string name="fxaccount_create_account_password_length_restriction">&fxaccount_create_account_password_length_restriction;</string>
 <string name="fxaccount_create_account_year_of_birth">&fxaccount_create_account_year_of_birth;</string>
 
-<string name="fxaccount_create_account_policy_text">&fxaccount_create_account_policy_text;</string>
+<string name="fxaccount_create_account_policy_text">&fxaccount_create_account_policy_text2;</string>
 <string name="fxaccount_create_account_button">&fxaccount_create_account_button;</string>
 <string name="fxaccount_create_account_choose_what_to_sync">&fxaccount_create_account_choose_what_to_sync;</string>
 <string name="fxaccount_create_account_sign_in_instead">&fxaccount_create_account_sign_in_instead;</string>
 <string name="fxaccount_create_account_1990_or_earlier">&fxaccount_create_account_1990_or_earlier;</string>
 <string name="fxaccount_create_account_unknown_error">&fxaccount_create_account_unknown_error;</string>
 
 <string name="fxaccount_account_create_not_allowed">&fxaccount_account_create_not_allowed;</string>
 <string name="fxaccount_account_create_not_allowed_you_must_meet_certain_age_requirements">&fxaccount_account_create_not_allowed_you_must_meet_certain_age_requirements;</string>
@@ -160,28 +160,30 @@
 <string name="fxaccount_account_verified_sub_header">&fxaccount_account_verified_sub_header;</string>
 <string name="fxaccount_account_verified_description">&fxaccount_account_verified_description2;</string>
 
 <string name="fxaccount_update_credentials_header">&fxaccount_update_credentials_header;</string>
 <string name="fxaccount_update_credentials_button_label">&fxaccount_update_credentials_button_label;</string>
 <string name="fxaccount_update_credentials_unknown_error">&fxaccount_update_credentials_unknown_error;</string>
 
 <string name="fxaccount_status_activity_label">&syncBrand.shortName.label;</string>
-<string name="fxaccount_status_header">&fxaccount_status_header;</string>
+<string name="fxaccount_status_header">&fxaccount_status_header2;</string>
 <string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
 <string name="fxaccount_status_sync">&fxaccount_status_sync;</string>
 <string name="fxaccount_status_sync_enabled">&fxaccount_status_sync_enabled;</string>
 <string name="fxaccount_status_needs_verification">&fxaccount_status_needs_verification2;</string>
 <string name="fxaccount_status_needs_credentials">&fxaccount_status_needs_credentials;</string>
 <string name="fxaccount_status_needs_upgrade">&fxaccount_status_needs_upgrade;</string>
 <string name="fxaccount_status_bookmarks">&fxaccount_status_bookmarks;</string>
 <string name="fxaccount_status_history">&fxaccount_status_history;</string>
 <string name="fxaccount_status_passwords">&fxaccount_status_passwords;</string>
 <string name="fxaccount_status_tabs">&fxaccount_status_tabs;</string>
 <string name="fxaccount_status_legal">&fxaccount_status_legal;</string>
+<string name="fxaccount_status_linktos">&fxaccount_status_linktos;</string>
+<string name="fxaccount_status_linkprivacy">&fxaccount_status_linkprivacy;</string>
 
 <string name="fxaccount_label">&fxaccount_account_type_label;</string>
 
 <string name="fxaccount_options_title">&fxaccount_options_title;</string>
 <string name="fxaccount_options_configure_title">&fxaccount_options_configure_title;</string>
 
 <string name="fxaccount_remote_error_UPGRADE_REQUIRED">&fxaccount_remote_error_UPGRADE_REQUIRED;</string>
 <string name="fxaccount_remote_error_ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS">&fxaccount_remote_error_ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS_2;</string>
new file mode 100644
--- /dev/null
+++ b/services/common/tests/unit/test_utils_sets.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://services-common/utils.js");
+
+const EMPTY = new Set();
+const A = new Set(["a"]);
+const ABC = new Set(["a", "b", "c"]);
+const ABCD = new Set(["a", "b", "c", "d"]);
+const BC = new Set(["b", "c"]);
+const BCD = new Set(["b", "c", "d"]);
+const FGH = new Set(["f", "g", "h"]);
+const BCDFGH = new Set(["b", "c", "d", "f", "g", "h"]);
+
+let union = CommonUtils.union;
+let difference = CommonUtils.difference;
+let intersection = CommonUtils.intersection;
+let setEqual = CommonUtils.setEqual;
+
+function do_check_setEqual(a, b) {
+  do_check_true(setEqual(a, b));
+}
+
+function do_check_not_setEqual(a, b) {
+  do_check_false(setEqual(a, b));
+}
+
+function run_test() {
+  run_next_test();
+}
+
+add_test(function test_setEqual() {
+  do_check_setEqual(EMPTY, EMPTY);
+  do_check_setEqual(EMPTY, new Set());
+  do_check_setEqual(A, A);
+  do_check_setEqual(A, new Set(["a"]));
+  do_check_setEqual(new Set(["a"]), A);
+  do_check_not_setEqual(A, EMPTY);
+  do_check_not_setEqual(EMPTY, A);
+  do_check_not_setEqual(ABC, A);
+  run_next_test();
+});
+
+add_test(function test_union() {
+  do_check_setEqual(EMPTY, union(EMPTY, EMPTY));
+  do_check_setEqual(ABC, union(EMPTY, ABC));
+  do_check_setEqual(ABC, union(ABC, ABC));
+  do_check_setEqual(ABCD, union(ABC, BCD));
+  do_check_setEqual(ABCD, union(BCD, ABC));
+  do_check_setEqual(BCDFGH, union(BCD, FGH));
+  run_next_test();
+});
+
+add_test(function test_difference() {
+  do_check_setEqual(EMPTY, difference(EMPTY, EMPTY));
+  do_check_setEqual(EMPTY, difference(EMPTY, A));
+  do_check_setEqual(EMPTY, difference(A, A));
+  do_check_setEqual(ABC, difference(ABC, EMPTY));
+  do_check_setEqual(ABC, difference(ABC, FGH));
+  do_check_setEqual(A, difference(ABC, BCD));
+  run_next_test();
+});
+
+add_test(function test_intersection() {
+  do_check_setEqual(EMPTY, intersection(EMPTY, EMPTY));
+  do_check_setEqual(EMPTY, intersection(ABC, EMPTY));
+  do_check_setEqual(EMPTY, intersection(ABC, FGH));
+  do_check_setEqual(BC, intersection(ABC, BCD));
+  run_next_test();
+});
--- a/services/common/tests/unit/xpcshell.ini
+++ b/services/common/tests/unit/xpcshell.ini
@@ -12,16 +12,17 @@ firefox-appdir = browser
 [test_utils_deepCopy.js]
 [test_utils_encodeBase32.js]
 [test_utils_encodeBase64URL.js]
 [test_utils_ensureMillisecondsTimestamp.js]
 [test_utils_exceptionStr.js]
 [test_utils_json.js]
 [test_utils_makeURI.js]
 [test_utils_namedTimer.js]
+[test_utils_sets.js]
 [test_utils_stackTrace.js]
 [test_utils_utf8.js]
 [test_utils_uuid.js]
 
 [test_async_chain.js]
 [test_async_querySpinningly.js]
 [test_bagheera_server.js]
 [test_bagheera_client.js]
--- a/services/common/utils.js
+++ b/services/common/utils.js
@@ -8,16 +8,72 @@ this.EXPORTED_SYMBOLS = ["CommonUtils"];
 
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/osfile.jsm")
 Cu.import("resource://gre/modules/Log.jsm");
 
 this.CommonUtils = {
+  /*
+   * Set manipulation methods. These should be lifted into toolkit, or added to
+   * `Set` itself.
+   */
+
+  /**
+   * Return elements of `a` or `b`.
+   */
+  union: function (a, b) {
+    let out = new Set(a);
+    for (let x of b) {
+      out.add(x);
+    }
+    return out;
+  },
+
+  /**
+   * Return elements of `a` that are not present in `b`.
+   */
+  difference: function (a, b) {
+    let out = new Set(a);
+    for (let x of b) {
+      out.delete(x);
+    }
+    return out;
+  },
+
+  /**
+   * Return elements of `a` that are also in `b`.
+   */
+  intersection: function (a, b) {
+    let out = new Set();
+    for (let x of a) {
+      if (b.has(x)) {
+        out.add(x);
+      }
+    }
+    return out;
+  },
+
+  /**
+   * Return true if `a` and `b` are the same size, and
+   * every element of `a` is in `b`.
+   */
+  setEqual: function (a, b) {
+    if (a.size != b.size) {
+      return false;
+    }
+    for (let x of a) {
+      if (!b.has(x)) {
+        return false;
+      }
+    }
+    return true;
+  },
+
   exceptionStr: function exceptionStr(e) {
     if (!e) {
       return "" + e;
     }
     let message = e.message ? e.message : e;
     return message + " " + CommonUtils.stackTrace(e);
   },
 
--- a/services/healthreport/HealthReportComponents.manifest
+++ b/services/healthreport/HealthReportComponents.manifest
@@ -1,12 +1,14 @@
 # Register Firefox Health Report providers.
 category healthreport-js-provider-default AddonsProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider-default AppInfoProvider resource://gre/modules/HealthReport.jsm
+#ifdef MOZ_CRASHREPORTER
 category healthreport-js-provider-default CrashesProvider resource://gre/modules/HealthReport.jsm
+#endif
 category healthreport-js-provider-default HealthReportProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider-default PlacesProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider-default ProfileMetadataProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider-default SearchesProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider-default SessionsProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider-default SysInfoProvider resource://gre/modules/HealthReport.jsm
 
 # No Aurora or Beta providers yet; use the categories
--- a/services/healthreport/docs/dataformat.rst
+++ b/services/healthreport/docs/dataformat.rst
@@ -1019,34 +1019,53 @@ Example
       ]
     }
 
 org.mozilla.crashes.crashes
 ---------------------------
 
 This measurement contains a historical record of application crashes.
 
+Version 2
+^^^^^^^^^
+
+The switch to version 2 coincides with the introduction of the
+:ref:`crashes_crashmanager`, which provides a more robust source of
+crash data.
+
+This measurement will be reported on each day there was a crash. The
+following fields may be present in each record:
+
+mainCrash
+   The number of main process crashes that occurred on the given day.
+
+Yes, version 2 does not track submissions like version 1. It is very
+likely submissions will be re-added later.
+
+Also absent from version 2 are plugin crashes and hangs. These will be
+re-added, likely in version 3.
+
 Version 1
 ^^^^^^^^^
 
 This measurement will be reported on each day there was a crash. The
 following properties are reported:
 
 pending
     The number of crash reports that haven't been submitted.
 submitted
     The number of crash reports that were submitted.
 
 Notes
 ^^^^^
 
-Crashes are typically submitted immediately after they occur (by checking
-a box in the crash reporter, which should appear automatically after a
-crash). If the crash reporter submits the crash successfully, we get a
-submitted crash. Else, we leave it as pending.
+Main process crashes are typically submitted immediately after they
+occur (by checking a box in the crash reporter, which should appear
+automatically after a crash). If the crash reporter submits the crash
+successfully, we get a submitted crash. Else, we leave it as pending.
 
 A pending crash does not mean it will eventually be submitted.
 
 Pending crash reports can be submitted post-crash by going to
 about:crashes.
 
 If a pending crash is submitted via about:crashes, the submitted count
 increments but the pending count does not decrement. This is because FHR
@@ -1057,16 +1076,20 @@ Example
 ^^^^^^^
 
 ::
 
     "org.mozilla.crashes.crashes": {
       "_v": 1,
       "pending": 1,
       "submitted": 2
+    },
+    "org.mozilla.crashes.crashes": {
+      "_v": 2,
+      "mainCrash": 2
     }
 
 org.mozilla.healthreport.submissions
 ------------------------------------
 
 This measurement contains a history of FHR's own data submission activity.
 It was added in Firefox 23 in early May 2013.
 
--- a/services/healthreport/moz.build
+++ b/services/healthreport/moz.build
@@ -3,11 +3,11 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 SPHINX_TREES['healthreport'] = 'docs'
 
 TEST_DIRS += ['tests']
 
-EXTRA_COMPONENTS += [
+EXTRA_PP_COMPONENTS += [
     'HealthReportComponents.manifest',
 ]
--- a/services/healthreport/providers.jsm
+++ b/services/healthreport/providers.jsm
@@ -14,18 +14,19 @@
 
 "use strict";
 
 #ifndef MERGED_COMPARTMENT
 
 this.EXPORTED_SYMBOLS = [
   "AddonsProvider",
   "AppInfoProvider",
-  "CrashDirectoryService",
+#ifdef MOZ_CRASHREPORTER
   "CrashesProvider",
+#endif
   "HealthReportProvider",
   "PlacesProvider",
   "SearchesProvider",
   "SessionsProvider",
   "SysInfoProvider",
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
@@ -987,199 +988,96 @@ AddonsProvider.prototype = Object.freeze
     }
 
     data.counts["plugin"] = pluginTags.length;
 
     return data;
   },
 });
 
+#ifdef MOZ_CRASHREPORTER
 
-function DailyCrashesMeasurement() {
+function DailyCrashesMeasurement1() {
   Metrics.Measurement.call(this);
 }
 
-DailyCrashesMeasurement.prototype = Object.freeze({
+DailyCrashesMeasurement1.prototype = Object.freeze({
   __proto__: Metrics.Measurement.prototype,
 
   name: "crashes",
   version: 1,
 
   fields: {
     pending: DAILY_COUNTER_FIELD,
     submitted: DAILY_COUNTER_FIELD,
   },
 });
 
+function DailyCrashesMeasurement2() {
+  Metrics.Measurement.call(this);
+}
+
+DailyCrashesMeasurement2.prototype = Object.freeze({
+  __proto__: Metrics.Measurement.prototype,
+
+  name: "crashes",
+  version: 2,
+
+  fields: {
+    mainCrash: DAILY_LAST_NUMERIC_FIELD,
+  },
+});
+
 this.CrashesProvider = function () {
   Metrics.Provider.call(this);
+
+  // So we can unit test.
+  this._manager = Services.crashmanager;
 };
 
 CrashesProvider.prototype = Object.freeze({
   __proto__: Metrics.Provider.prototype,
 
   name: "org.mozilla.crashes",
 
-  measurementTypes: [DailyCrashesMeasurement],
+  measurementTypes: [
+    DailyCrashesMeasurement1,
+    DailyCrashesMeasurement2,
+  ],
 
   pullOnly: true,
 
-  collectConstantData: function () {
+  collectDailyData: function () {
     return this.storage.enqueueTransaction(this._populateCrashCounts.bind(this));
   },
 
   _populateCrashCounts: function () {
-    let now = new Date();
-    let service = new CrashDirectoryService();
-
-    let pending = yield service.getPendingFiles();
-    let submitted = yield service.getSubmittedFiles();
-
-    function getAgeLimit() {
-      return 0;
-    }
-
-    let lastCheck = yield this.getState("lastCheck");
-    if (!lastCheck) {
-      lastCheck = getAgeLimit();
-    } else {
-      lastCheck = parseInt(lastCheck, 10);
-      if (Number.isNaN(lastCheck)) {
-        lastCheck = getAgeLimit();
-      }
-    }
-
-    let m = this.getMeasurement("crashes", 1);
-
-    // Aggregate counts locally to avoid excessive storage interaction.
-    let counts = {
-      pending: new Metrics.DailyValues(),
-      submitted: new Metrics.DailyValues(),
+    this._log.info("Grabbing crash counts from crash manager.");
+    let crashCounts = yield this._manager.getCrashCountsByDay();
+    let fields = {
+      "main-crash": "mainCrash",
     };
 
-    // FUTURE detect mtimes in the future and react more intelligently.
-    for (let filename in pending) {
-      let modified = pending[filename].modified;
-
-      if (modified.getTime() < lastCheck) {
-        continue;
-      }
-
-      counts.pending.appendValue(modified, 1);
-    }
-
-    for (let filename in submitted) {
-      let modified = submitted[filename].modified;
+    let m = this.getMeasurement("crashes", 2);
 
-      if (modified.getTime() < lastCheck) {
-        continue;
-      }
-
-      counts.submitted.appendValue(modified, 1);
-    }
+    for (let [day, types] of crashCounts) {
+      let date = Metrics.daysToDate(day);
+      for (let [type, count] of types) {
+        if (!(type in fields)) {
+          this._log.warn("Unknown crash type encountered: " + type);
+          continue;
+        }
 
-    for (let [date, values] in counts.pending) {
-      yield m.incrementDailyCounter("pending", date, values.length);
+        yield m.setDailyLastNumeric(fields[type], count, date);
+      }
     }
-
-    for (let [date, values] in counts.submitted) {
-      yield m.incrementDailyCounter("submitted", date, values.length);
-    }
-
-    yield this.setState("lastCheck", "" + now.getTime());
   },
 });
 
-
-/**
- * Helper for interacting with the crashes directory.
- *
- * FUTURE Extract to JSM alongside crashreporter. Use in about:crashes.
- */
-this.CrashDirectoryService = function () {
-  let base = Cc["@mozilla.org/file/directory_service;1"]
-               .getService(Ci.nsIProperties)
-               .get("UAppData", Ci.nsIFile);
-
-  let cr = base.clone();
-  cr.append("Crash Reports");
-
-  let submitted = cr.clone();
-  submitted.append("submitted");
-
-  let pending = cr.clone();
-  pending.append("pending");
-
-  this._baseDir = base.path;
-  this._submittedDir = submitted.path;
-  this._pendingDir = pending.path;
-};
-
-CrashDirectoryService.prototype = Object.freeze({
-  RE_SUBMITTED_FILENAME: /^bp-.+\.txt$/,
-  RE_PENDING_FILENAME: /^.+\.dmp$/,
-
-  getPendingFiles: function () {
-    return this._getDirectoryEntries(this._pendingDir,
-                                     this.RE_PENDING_FILENAME);
-  },
-
-  getSubmittedFiles: function () {
-    return this._getDirectoryEntries(this._submittedDir,
-                                     this.RE_SUBMITTED_FILENAME);
-  },
-
-  _getDirectoryEntries: function (path, re) {
-    let files = {};
-
-    return Task.spawn(function iterateDirectory() {
-      // If the directory doesn't exist, exit immediately. Else, re-throw
-      // any errors.
-      try {
-        yield OS.File.stat(path);
-      } catch (ex if ex instanceof OS.File.Error) {
-        if (ex.becauseNoSuchFile) {
-          throw new Task.Result({});
-        }
-
-        throw ex;
-      }
-
-      let iterator = new OS.File.DirectoryIterator(path);
-
-      try {
-        while (true) {
-          let entry;
-          try {
-            entry = yield iterator.next();
-          } catch (ex if ex == StopIteration) {
-            break;
-          }
-
-          if (!entry.name.match(re)) {
-            continue;
-          }
-
-          let info = yield OS.File.stat(entry.path);
-
-          files[entry.name] = {
-            // Last modified should be adequate, because crash files aren't
-            // modified after they're first written.
-            modified: info.lastModificationDate,
-            size: info.size,
-          };
-        }
-
-        throw new Task.Result(files);
-      } finally {
-        iterator.close();
-      }
-    });
-  },
-});
+#endif
 
 
 /**
  * Holds basic statistics about the Places database.
  */
 function PlacesMeasurement() {
   Metrics.Measurement.call(this);
 }
--- a/services/healthreport/tests/xpcshell/test_provider_crashes.js
+++ b/services/healthreport/tests/xpcshell/test_provider_crashes.js
@@ -3,154 +3,86 @@
 
 "use strict";
 
 const {utils: Cu} = Components;
 
 
 Cu.import("resource://gre/modules/Metrics.jsm");
 Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
+Cu.import("resource://testing-common/AppData.jsm");
 Cu.import("resource://testing-common/services/healthreport/utils.jsm");
-Cu.import("resource://testing-common/AppData.jsm");
-
-
-const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
+Cu.import("resource://testing-common/CrashManagerTest.jsm");
 
 
 function run_test() {
   run_next_test();
 }
 
-// run_test() needs to finish synchronously, so we do async init here.
-add_task(function test_init() {
+add_task(function* init() {
+  do_get_profile();
   yield makeFakeAppDir();
 });
 
-let gPending = {};
-let gSubmitted = {};
-
-add_task(function test_directory_service() {
-  let d = new CrashDirectoryService();
-
-  let entries = yield d.getPendingFiles();
-  do_check_eq(typeof(entries), "object");
-  do_check_eq(Object.keys(entries).length, 0);
-
-  entries = yield d.getSubmittedFiles();
-  do_check_eq(typeof(entries), "object");
-  do_check_eq(Object.keys(entries).length, 0);
-
-  let now = new Date();
-
-  // We lose granularity when writing to filesystem.
-  now.setUTCMilliseconds(0);
-  let dates = [];
-  for (let i = 0; i < 10; i++) {
-    dates.push(new Date(now.getTime() - i * MILLISECONDS_PER_DAY));
-  }
-
-  let pending = {};
-  let submitted = {};
-  for (let date of dates) {
-    pending[createFakeCrash(false, date)] = date;
-    submitted[createFakeCrash(true, date)] = date;
-  }
-
-  entries = yield d.getPendingFiles();
-  do_check_eq(Object.keys(entries).length, Object.keys(pending).length);
-  for (let id in pending) {
-    let filename = id + ".dmp";
-    do_check_true(filename in entries);
-    do_check_eq(entries[filename].modified.getTime(), pending[id].getTime());
-  }
-
-  entries = yield d.getSubmittedFiles();
-  do_check_eq(Object.keys(entries).length, Object.keys(submitted).length);
-  for (let id in submitted) {
-    let filename = "bp-" + id + ".txt";
-    do_check_true(filename in entries);
-    do_check_eq(entries[filename].modified.getTime(), submitted[id].getTime());
-  }
-
-  gPending = pending;
-  gSubmitted = submitted;
+add_task(function test_constructor() {
+  let provider = new CrashesProvider();
 });
 
-add_test(function test_constructor() {
-  let provider = new CrashesProvider();
-
-  run_next_test();
-});
-
-add_task(function test_init() {
+add_task(function* test_init() {
   let storage = yield Metrics.Storage("init");
   let provider = new CrashesProvider();
   yield provider.init(storage);
   yield provider.shutdown();
 
   yield storage.close();
 });
 
-add_task(function test_collect() {
+add_task(function* test_collect() {
   let storage = yield Metrics.Storage("collect");
   let provider = new CrashesProvider();
   yield provider.init(storage);
 
-  // FUTURE Don't rely on state from previous test.
-  yield provider.collectConstantData();
+  // Install custom manager so we don't interfere with other tests.
+  let manager = yield getManager();
+  provider._manager = manager;
+
+  let day1 = new Date(2014, 0, 1, 0, 0, 0);
+  let day2 = new Date(2014, 0, 3, 0, 0, 0);
 
-  let m = provider.getMeasurement("crashes", 1);
-  let values = yield m.getValues();
-  do_check_eq(values.days.size, Object.keys(gPending).length);
-  for each (let date in gPending) {
-    do_check_true(values.days.hasDay(date));
+  // FUTURE Bug 982836 CrashManager will grow public APIs for adding crashes.
+  // Switch to that here.
+  let store = yield manager._getStore();
+  store.addMainProcessCrash("id1", day1);
+  store.addMainProcessCrash("id2", day1);
+  store.addMainProcessCrash("id3", day2);
+
+  // Flush changes (this may not be needed but it doesn't hurt).
+  yield store.save();
+
+  yield provider.collectDailyData();
 
-    let value = values.days.getDay(date);
-    do_check_true(value.has("pending"));
-    do_check_true(value.has("submitted"));
-    do_check_eq(value.get("pending"), 1);
-    do_check_eq(value.get("submitted"), 1);
-  }
+  let m = provider.getMeasurement("crashes", 2);
+  let values = yield m.getValues();
+  do_check_eq(values.days.size, 2);
+  do_check_true(values.days.hasDay(day1));
+  do_check_true(values.days.hasDay(day2));
+
+  let value = values.days.getDay(day1);
+  do_check_true(value.has("mainCrash"));
+  do_check_eq(value.get("mainCrash"), 2);
 
-  let currentState = yield provider.getState("lastCheck");
-  do_check_eq(typeof(currentState), "string");
-  do_check_true(currentState.length > 0);
-  let lastState = currentState;
+  value = values.days.getDay(day2);
+  do_check_eq(value.get("mainCrash"), 1);
 
-  // If we collect again, we should get no new data.
-  yield provider.collectConstantData();
+  // Check that adding a new crash increments counter on next collect.
+  store = yield manager._getStore();
+  store.addMainProcessCrash("id4", day2);
+  yield store.save();
+
+  yield provider.collectDailyData();
   values = yield m.getValues();
-  for each (let date in gPending) {
-    let day = values.days.getDay(date);
-    do_check_eq(day.get("pending"), 1);
-    do_check_eq(day.get("submitted"), 1);
-  }
-
-  currentState = yield provider.getState("lastCheck");
-  do_check_neq(currentState, lastState);
-  do_check_true(currentState > lastState);
-
-  let now = new Date();
-  let tomorrow = new Date(now.getTime() + MILLISECONDS_PER_DAY);
-  let yesterday = new Date(now.getTime() - MILLISECONDS_PER_DAY);
-
-  createFakeCrash(false, yesterday);
-
-  // Create multiple to test that multiple are handled properly.
-  createFakeCrash(false, tomorrow);
-  createFakeCrash(false, tomorrow);
-  createFakeCrash(false, tomorrow);
-
-  yield provider.collectConstantData();
-  values = yield m.getValues();
-  do_check_eq(values.days.size, 11);
-  do_check_eq(values.days.getDay(tomorrow).get("pending"), 3);
-
-  for each (let date in gPending) {
-    let day = values.days.getDay(date);
-    do_check_eq(day.get("pending"), 1);
-    do_check_eq(day.get("submitted"), 1);
-  }
+  value = values.days.getDay(day2);
+  do_check_eq(value.get("mainCrash"), 2);
 
   yield provider.shutdown();
   yield storage.close();
 });
 
--- a/services/healthreport/tests/xpcshell/xpcshell.ini
+++ b/services/healthreport/tests/xpcshell/xpcshell.ini
@@ -3,13 +3,14 @@ head = head.js
 tail =
 
 [test_load_modules.js]
 [test_profile.js]
 [test_healthreporter.js]
 [test_provider_addons.js]
 [test_provider_appinfo.js]
 [test_provider_crashes.js]
+run-if = crashreporter
 [test_provider_places.js]
 [test_provider_searches.js]
 [test_provider_sysinfo.js]
 [test_provider_sessions.js]
 
--- a/services/sync/Makefile.in
+++ b/services/sync/Makefile.in
@@ -46,16 +46,17 @@ sync_engine_modules := \
   history.js \
   passwords.js \
   prefs.js \
   tabs.js \
   $(NULL)
 
 sync_stage_modules := \
   cluster.js \
+  declined.js \
   enginesync.js \
   $(NULL)
 
 sync_testing_modules := \
   fakeservices.js \
   rotaryengine.js \
   utils.js \
   $(NULL)
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -157,16 +157,29 @@ this.BrowserIDManager.prototype = {
     for (let topic of OBSERVER_TOPICS) {
       Services.obs.removeObserver(this, topic);
     }
     this.resetCredentials();
     this._signedInUser = null;
     return Promise.resolve();
   },
 
+  offerSyncOptions: function () {
+    // If the user chose to "Customize sync options" when signing
+    // up with Firefox Accounts, ask them to choose what to sync.
+    const url = "chrome://browser/content/sync/customize.xul";
+    const features = "centerscreen,chrome,modal,dialog,resizable=no";
+    let win = Services.wm.getMostRecentWindow("navigator:browser");
+
+    let data = {accepted: false};
+    win.openDialog(url, "_blank", features, data);
+
+    return data;
+  },
+
   initializeWithCurrentIdentity: function(isInitialSync=false) {
     // 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();
@@ -188,27 +201,22 @@ this.BrowserIDManager.prototype = {
       // The user must be verified before we can do anything at all; we kick
       // this and the rest of initialization off in the background (ie, we
       // don't return the promise)
       this._log.info("Waiting for user to be verified.");
       this._fxaService.whenVerified(accountData).then(accountData => {
         this._updateSignedInUser(accountData);
         this._log.info("Starting fetch for key bundle.");
         if (this.needsCustomization) {
-          // If the user chose to "Customize sync options" when signing
-          // up with Firefox Accounts, ask them to choose what to sync.
-          const url = "chrome://browser/content/sync/customize.xul";
-          const features = "centerscreen,chrome,modal,dialog,resizable=no";
-          let win = Services.wm.getMostRecentWindow("navigator:browser");
-
-          let data = {accepted: false};
-          win.openDialog(url, "_blank", features, data);
-
+          let data = this.offerSyncOptions();
           if (data.accepted) {
             Services.prefs.clearUserPref(PREF_SYNC_SHOW_CUSTOMIZATION);
+
+            // Mark any non-selected engines as declined.
+            Weave.Service.engineManager.declineDisabled();
           } else {
             // Log out if the user canceled the dialog.
             return this._fxaService.signOut();
           }
         }
       }).then(() => {
         return this._fetchSyncKeyBundle();
       }).then(() => {
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -440,19 +440,21 @@ Store.prototype = {
     throw "override wipe in a subclass";
   }
 };
 
 this.EngineManager = function EngineManager(service) {
   this.service = service;
 
   this._engines = {};
+
+  // This will be populated by Service on startup.
+  this._declined = new Set();
   this._log = Log.repository.getLogger("Sync.EngineManager");
-  this._log.level = Log.Level[Svc.Prefs.get(
-    "log.logger.service.engines", "Debug")];
+  this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.engines", "Debug")];
 }
 EngineManager.prototype = {
   get: function (name) {
     // Return an array of engines if we have an array of names
     if (Array.isArray(name)) {
       let engines = [];
       name.forEach(function(name) {
         let engine = this.get(name);
@@ -472,20 +474,79 @@ EngineManager.prototype = {
     }
     return engine;
   },
 
   getAll: function () {
     return [engine for ([name, engine] in Iterator(this._engines))];
   },
 
+  /**
+   * N.B., does not pay attention to the declined list.
+   */
   getEnabled: function () {
     return this.getAll().filter(function(engine) engine.enabled);
   },
 
+  get enabledEngineNames() {
+    return [e.name for each (e in this.getEnabled())];
+  },
+
+  persistDeclined: function () {
+    Svc.Prefs.set("declinedEngines", [...this._declined].join(","));
+  },
+
+  /**
+   * Returns an array.
+   */
+  getDeclined: function () {
+    return [...this._declined];
+  },
+
+  setDeclined: function (engines) {
+    this._declined = new Set(engines);
+    this.persistDeclined();
+  },
+
+  isDeclined: function (engineName) {
+    return this._declined.has(engineName);
+  },
+
+  /**
+   * Accepts a Set or an array.
+   */
+  decline: function (engines) {
+    for (let e of engines) {
+      this._declined.add(e);
+    }
+    this.persistDeclined();
+  },
+
+  undecline: function (engines) {
+    for (let e of engines) {
+      this._declined.delete(e);
+    }
+    this.persistDeclined();
+  },
+
+  /**
+   * Mark any non-enabled engines as declined.
+   *
+   * This is useful after initial customization during setup.
+   */
+  declineDisabled: function () {
+    for (let e of this.getAll()) {
+      if (!e.enabled) {
+        this._log.debug("Declining disabled engine " + e.name);
+        this._declined.add(e.name);
+      }
+    }
+    this.persistDeclined();
+  },
+
   /**
    * Register an Engine to the service. Alternatively, give an array of engine
    * objects to register.
    *
    * @param engineObject
    *        Engine object used to get an instance of the engine
    * @return The engine object if anything failed
    */
@@ -633,26 +694,26 @@ SyncEngine.kRecoveryStrategy = {
   retry:  "retry",
   error:  "error"
 };
 
 SyncEngine.prototype = {
   __proto__: Engine.prototype,
   _recordObj: CryptoWrapper,
   version: 1,
-  
+
   // How many records to pull in a single sync. This is primarily to avoid very
   // long first syncs against profiles with many history records.
   downloadLimit: null,
-  
+
   // How many records to pull at one time when specifying IDs. This is to avoid
   // URI length limitations.
   guidFetchBatchSize: DEFAULT_GUID_FETCH_BATCH_SIZE,
   mobileGUIDFetchBatchSize: DEFAULT_MOBILE_GUID_FETCH_BATCH_SIZE,
-  
+
   // How many records to process in a single batch.
   applyIncomingBatchSize: DEFAULT_STORE_BATCH_SIZE,
 
   get storageURL() this.service.storageURL,
 
   get engineURL() this.storageURL + this.name,
 
   get cryptoKeysURL() this.storageURL + "crypto/keys",
@@ -858,17 +919,17 @@ SyncEngine.prototype = {
     }
 
     if (isMobile) {
       batchSize = MOBILE_BATCH_SIZE;
     }
     newitems.newer = this.lastSync;
     newitems.full  = true;
     newitems.limit = batchSize;
-    
+
     // applied    => number of items that should be applied.
     // failed     => number of items that failed in this sync.
     // newFailed  => number of items that failed for the first time in this sync.
     // reconciled => number of items that were reconciled.
     let count = {applied: 0, failed: 0, newFailed: 0, reconciled: 0};
     let handled = [];
     let applyBatch = [];
     let failed = [];
--- a/services/sync/modules/identity.js
+++ b/services/sync/modules/identity.js
@@ -548,10 +548,15 @@ IdentityManager.prototype = {
   onRESTRequestBasic: function onRESTRequestBasic(request) {
     let up = this.username + ":" + this.basicPassword;
     request.setHeader("authorization", "Basic " + btoa(up));
   },
 
   createClusterManager: function(service) {
     Cu.import("resource://services-sync/stages/cluster.js");
     return new ClusterManager(service);
-  }
+  },
+
+  offerSyncOptions: function () {
+    // Do nothing for Sync 1.1.
+    return {accepted: true};
+  },
 };
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -26,16 +26,17 @@ Cu.import("resource://services-sync/cons
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/clients.js");
 Cu.import("resource://services-sync/identity.js");
 Cu.import("resource://services-sync/policies.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/resource.js");
 Cu.import("resource://services-sync/rest.js");
 Cu.import("resource://services-sync/stages/enginesync.js");
+Cu.import("resource://services-sync/stages/declined.js");
 Cu.import("resource://services-sync/status.js");
 Cu.import("resource://services-sync/userapi.js");
 Cu.import("resource://services-sync/util.js");
 
 const ENGINE_MODULES = {
   Addons: "addons.js",
   Bookmarks: "bookmarks.js",
   Form: "forms.js",
@@ -60,20 +61,16 @@ Sync11Service.prototype = {
   _locked: false,
   _loggedIn: false,
 
   infoURL: null,
   storageURL: null,
   metaURL: null,
   cryptoKeyURL: null,
 
-  get enabledEngineNames() {
-    return [e.name for each (e in this.engineManager.getEnabled())];
-  },
-
   get serverURL() Svc.Prefs.get("serverURL"),
   set serverURL(value) {
     if (!value.endsWith("/")) {
       value += "/";
     }
 
     // Only do work if it's actually changing
     if (value == this.serverURL)
@@ -425,16 +422,22 @@ Sync11Service.prototype = {
     let engines = [];
     // Applications can provide this preference (comma-separated list)
     // to specify which engines should be registered on startup.
     let pref = Svc.Prefs.get("registerEngines");
     if (pref) {
       engines = pref.split(",");
     }
 
+    let declined = [];
+    pref = Svc.Prefs.get("declinedEngines");
+    if (pref) {
+      declined = pref.split(",");
+    }
+
     this.clientsEngine = new ClientEngine(this);
 
     for (let name of engines) {
       if (!name in ENGINE_MODULES) {
         this._log.info("Do not know about engine: " + name);
         continue;
       }
 
@@ -443,22 +446,24 @@ Sync11Service.prototype = {
         Cu.import("resource://services-sync/engines/" + ENGINE_MODULES[name], ns);
 
         let engineName = name + "Engine";
         if (!(engineName in ns)) {
           this._log.warn("Could not find exported engine instance: " + engineName);
           continue;
         }
 
-        this.engineManager.register(ns[engineName], this);
+        this.engineManager.register(ns[engineName]);
       } catch (ex) {
         this._log.warn("Could not register engine " + name + ": " +
                        CommonUtils.exceptionStr(ex));
       }
     }
+
+    this.engineManager.setDeclined(declined);
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
   // nsIObserver
 
   observe: function observe(subject, topic, data) {
@@ -1057,16 +1062,17 @@ Sync11Service.prototype = {
       // ... fetch the current record from the server, and COPY THE FLAGS.
       let newMeta = this.recordManager.get(this.metaURL);
 
       if (!this.recordManager.response.success || !newMeta) {
         this._log.debug("No meta/global record on the server. Creating one.");
         newMeta = new WBORecord("meta", "global");
         newMeta.payload.syncID = this.syncID;
         newMeta.payload.storageVersion = STORAGE_VERSION;
+        newMeta.payload.declined = this.engineManager.getDeclined();
 
         newMeta.isNew = true;
 
         this.recordManager.set(this.metaURL, newMeta);
         if (!newMeta.upload(this.resource(this.metaURL)).success) {
           this._log.warn("Unable to upload new meta/global. Failing remote setup.");
           return false;
         }
@@ -1240,20 +1246,54 @@ Sync11Service.prototype = {
       let synchronizer = new EngineSynchronizer(this);
       let cb = Async.makeSpinningCallback();
       synchronizer.onComplete = cb;
 
       synchronizer.sync();
       // wait() throws if the first argument is truthy, which is exactly what
       // we want.
       let result = cb.wait();
+
+      // We successfully synchronized. Now let's update our declined engines.
+      let meta = this.recordManager.get(this.metaURL);
+      if (!meta) {
+        this._log.warn("No meta/global; can't update declined state.");
+        return;
+      }
+
+      let declinedEngines = new DeclinedEngines(this);
+      let didChange = declinedEngines.updateDeclined(meta, this.engineManager);
+      if (!didChange) {
+        this._log.info("No change to declined engines. Not reuploading meta/global.");
+        return;
+      }
+
+      this.uploadMetaGlobal(meta);
     }))();
   },
 
   /**
+   * Upload meta/global, throwing the response on failure.
+   */
+  uploadMetaGlobal: function (meta) {
+    this._log.debug("Uploading meta/global: " + JSON.stringify(meta));
+
+    // It would be good to set the X-If-Unmodified-Since header to `timestamp`
+    // for this PUT to ensure at least some level of transactionality.
+    // Unfortunately, the servers don't support it after a wipe right now
+    // (bug 693893), so we're going to defer this until bug 692700.
+    let res = this.resource(this.metaURL);
+    let response = res.put(meta);
+    if (!response.success) {
+      throw response;
+    }
+    this.recordManager.set(this.metaURL, meta);
+  },
+
+  /**
    * If we have a passphrase, rather than a 25-alphadigit sync key,
    * use the provided sync ID to bootstrap it using PBKDF2.
    *
    * Store the new 'passphrase' back into the identity manager.
    *
    * We can check this as often as we want, because once it's done the
    * check will no longer succeed. It only matters that it happens after
    * we decide to bump the server storage version.
@@ -1298,36 +1338,29 @@ Sync11Service.prototype = {
 
     // Wipe the server.
     let wipeTimestamp = this.wipeServer();
 
     // Upload a new meta/global record.
     let meta = new WBORecord("meta", "global");
     meta.payload.syncID = this.syncID;
     meta.payload.storageVersion = STORAGE_VERSION;
+    meta.payload.declined = this.engineManager.getDeclined();
     meta.isNew = true;
 
-    this._log.debug("New metadata record: " + JSON.stringify(meta.payload));
-    let res = this.resource(this.metaURL);
-    // It would be good to set the X-If-Unmodified-Since header to `timestamp`
-    // for this PUT to ensure at least some level of transactionality.
-    // Unfortunately, the servers don't support it after a wipe right now
-    // (bug 693893), so we're going to defer this until bug 692700.
-    let resp = res.put(meta);
-    if (!resp.success) {
-      // If we got into a race condition, we'll abort the sync this way, too.
-      // That's fine. We'll just wait till the next sync. The client that we're
-      // racing is probably busy uploading stuff right now anyway.
-      throw resp;
-    }
-    this.recordManager.set(this.metaURL, meta);
+    // uploadMetaGlobal throws on failure -- including race conditions.
+    // If we got into a race condition, we'll abort the sync this way, too.
+    // That's fine. We'll just wait till the next sync. The client that we're
+    // racing is probably busy uploading stuff right now anyway.
+    this.uploadMetaGlobal(meta);
 
     // Wipe everything we know about except meta because we just uploaded it
     let engines = [this.clientsEngine].concat(this.engineManager.getAll());
     let collections = [engine.name for each (engine in engines)];
+    // TODO: there's a bug here. We should be calling resetClient, no?
 
     // Generate, upload, and download new keys. Do this last so we don't wipe
     // them...
     this.generateNewSymmetricKeys();
   },
 
   /**
    * Wipe user data from the server.
--- a/services/sync/modules/stages/cluster.js
+++ b/services/sync/modules/stages/cluster.js
@@ -1,11 +1,11 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+ * 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/. */
 
 this.EXPORTED_SYMBOLS = ["ClusterManager"];
 
 const {utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/policies.js");
new file mode 100644
--- /dev/null
+++ b/services/sync/modules/stages/declined.js
@@ -0,0 +1,76 @@
+/* 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/. */
+
+/**
+ * This file contains code for maintaining the set of declined engines,
+ * in conjunction with EngineManager.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["DeclinedEngines"];
+
+const {utils: Cu} = Components;
+
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-common/observers.js");
+Cu.import("resource://gre/modules/Preferences.jsm");
+
+
+
+this.DeclinedEngines = function (service) {
+  this._log = Log.repository.getLogger("Sync.Declined");
+  this._log.level = Log.Level[new Preferences(PREFS_BRANCH).get("log.logger.declined")];
+
+  this.service = service;
+}
+this.DeclinedEngines.prototype = {
+  updateDeclined: function (meta, engineManager=this.service.engineManager) {
+    let enabled = new Set([e.name for each (e in engineManager.getEnabled())]);
+    let known = new Set([e.name for each (e in engineManager.getAll())]);
+    let remoteDeclined = new Set(meta.payload.declined || []);
+    let localDeclined = new Set(engineManager.getDeclined());
+
+    this._log.debug("Handling remote declined: " + JSON.stringify([...remoteDeclined]));
+    this._log.debug("Handling local declined: " + JSON.stringify([...localDeclined]));
+
+    // Any engines that are locally enabled should be removed from the remote
+    // declined list.
+    //
+    // Any engines that are locally declined should be added to the remote
+    // declined list.
+    let newDeclined = CommonUtils.union(localDeclined, CommonUtils.difference(remoteDeclined, enabled));
+
+    // If our declined set has changed, put it into the meta object and mark
+    // it as changed.
+    let declinedChanged = !CommonUtils.setEqual(newDeclined, remoteDeclined);
+    this._log.debug("Declined changed? " + declinedChanged);
+    if (declinedChanged) {
+      meta.changed = true;
+      meta.payload.declined = [...newDeclined];
+    }
+
+    // Update the engine manager regardless.
+    engineManager.setDeclined(newDeclined);
+
+    // Any engines that are locally known, locally disabled, and not remotely
+    // or locally declined, are candidates for enablement.
+    let undecided = CommonUtils.difference(CommonUtils.difference(known, enabled), newDeclined);
+    if (undecided.size) {
+      let subject = {
+        declined: newDeclined,
+        enabled: enabled,
+        known: known,
+        undecided: undecided,
+      };
+      CommonUtils.nextTick(() => {
+        Observers.notify("weave:engines:notdeclined", subject);
+      });
+    }
+
+    return declinedChanged;
+  },
+};
--- a/services/sync/modules/stages/enginesync.js
+++ b/services/sync/modules/stages/enginesync.js
@@ -1,11 +1,11 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+ * 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/. */
 
 /**
  * This file contains code for synchronizing engines.
  */
 
 this.EXPORTED_SYMBOLS = ["EngineSynchronizer"];
 
 const {utils: Cu} = Components;
@@ -66,21 +66,23 @@ EngineSynchronizer.prototype = {
     let infoURL = this.service.infoURL;
     let now = Math.floor(Date.now() / 1000);
     let lastPing = Svc.Prefs.get("lastPing", 0);
     if (now - lastPing > 86400) { // 60 * 60 * 24
       infoURL += "?v=" + WEAVE_VERSION;
       Svc.Prefs.set("lastPing", now);
     }
 
+    let engineManager = this.service.engineManager;
+
     // Figure out what the last modified time is for each collection
     let info = this.service._fetchInfo(infoURL);
 
     // Convert the response to an object and read out the modified times
-    for (let engine of [this.service.clientsEngine].concat(this.service.engineManager.getAll())) {
+    for (let engine of [this.service.clientsEngine].concat(engineManager.getAll())) {
       engine.lastModified = info.obj[engine.name] || 0;
     }
 
     if (!(this.service._remoteSetup(info))) {
       this.onComplete(new Error("Aborting sync, remote setup failed"));
       return;
     }
 
@@ -92,23 +94,23 @@ EngineSynchronizer.prototype = {
       this._log.warn("Client engine sync failed. Aborting.");
       this.onComplete(null);
       return;
     }
 
     // Wipe data in the desired direction if necessary
     switch (Svc.Prefs.get("firstSync")) {
       case "resetClient":
-        this.service.resetClient(this.service.enabledEngineNames);
+        this.service.resetClient(engineManager.enabledEngineNames);
         break;
       case "wipeClient":
-        this.service.wipeClient(this.service.enabledEngineNames);
+        this.service.wipeClient(engineManager.enabledEngineNames);
         break;
       case "wipeRemote":
-        this.service.wipeRemote(this.service.enabledEngineNames);
+        this.service.wipeRemote(engineManager.enabledEngineNames);
         break;
     }
 
     if (this.service.clientsEngine.localCommands) {
       try {
         if (!(this.service.clientsEngine.processIncomingCommands())) {
           this.service.status.sync = ABORT_SYNC_COMMAND;
           this.onComplete(new Error("Processed command aborted sync."));
@@ -137,17 +139,17 @@ EngineSynchronizer.prototype = {
       this._log.debug("Updating enabled engines failed: " +
                       Utils.exceptionStr(ex));
       this.service.errorHandler.checkServerError(ex);
       this.onComplete(ex);
       return;
     }
 
     try {
-      for each (let engine in this.service.engineManager.getEnabled()) {
+      for (let engine of engineManager.getEnabled()) {
         // If there's any problems with syncing the engine, report the failure
         if (!(this._syncEngine(engine)) || this.service.status.enforceBackoff) {
           this._log.info("Aborting sync for failure in " + engine.name);
           break;
         }
       }
 
       // If _syncEngine fails for a 401, we might not have a cluster URL here.
@@ -155,22 +157,27 @@ EngineSynchronizer.prototype = {
       // throwing an exception when trying to fetch metaURL.
       if (!this.service.clusterURL) {
         this._log.debug("Aborting sync, no cluster URL: " +
                         "not uploading new meta/global.");
         this.onComplete(null);
         return;
       }
 
-      // Upload meta/global if any engines changed anything
+      // Upload meta/global if any engines changed anything.
       let meta = this.service.recordManager.get(this.service.metaURL);
       if (meta.isNew || meta.changed) {
-        this.service.resource(this.service.metaURL).put(meta);
-        delete meta.isNew;
-        delete meta.changed;
+        this._log.info("meta/global changed locally: reuploading.");
+        try {
+          this.service.uploadMetaGlobal(meta);
+          delete meta.isNew;
+          delete meta.changed;
+        } catch (error) {
+          this._log.error("Unable to upload meta/global. Leaving marked as new.");
+        }
       }
 
       // If there were no sync engine failures
       if (this.service.status.service != SYNC_FAILED_PARTIAL) {
         Svc.Prefs.set("lastSync", new Date().toString());
         this.service.status.sync = SYNC_SUCCEEDED;
       }
     } finally {
@@ -200,75 +207,102 @@ EngineSynchronizer.prototype = {
         // appropriate value.
         return false;
       }
     }
 
     return true;
   },
 
-  _updateEnabledEngines: function _updateEnabledEngines() {
+  _updateEnabledFromMeta: function (meta, numClients, engineManager=this.service.engineManager) {
     this._log.info("Updating enabled engines: " +
-                   this.service.scheduler.numClients + " clients.");
-    let meta = this.service.recordManager.get(this.service.metaURL);
-    if (meta.isNew || !meta.payload.engines)
+                    numClients + " clients.");
+
+    if (meta.isNew || !meta.payload.engines) {
+      this._log.debug("meta/global isn't new, or is missing engines. Not updating enabled state.");
       return;
+    }
 
     // If we're the only client, and no engines are marked as enabled,
     // thumb our noses at the server data: it can't be right.
     // Belt-and-suspenders approach to Bug 615926.
-    if ((this.service.scheduler.numClients <= 1) &&
+    if ((numClients <= 1) &&
         ([e for (e in meta.payload.engines) if (e != "clients")].length == 0)) {
       this._log.info("One client and no enabled engines: not touching local engine status.");
       return;
     }
 
     this.service._ignorePrefObserver = true;
 
-    let enabled = this.service.enabledEngineNames;
+    let enabled = engineManager.enabledEngineNames;
+
+    let toDecline = new Set();
+    let toUndecline = new Set();
+
     for (let engineName in meta.payload.engines) {
       if (engineName == "clients") {
         // Clients is special.
         continue;
       }
       let index = enabled.indexOf(engineName);
       if (index != -1) {
         // The engine is enabled locally. Nothing to do.
         enabled.splice(index, 1);
         continue;
       }
-      let engine = this.service.engineManager.get(engineName);
+      let engine = engineManager.get(engineName);
       if (!engine) {
         // The engine doesn't exist locally. Nothing to do.
         continue;
       }
 
       if (Svc.Prefs.get("engineStatusChanged." + engine.prefName, false)) {
         // The engine was disabled locally. Wipe server data and
         // disable it everywhere.
         this._log.trace("Wiping data for " + engineName + " engine.");
         engine.wipeServer();
         delete meta.payload.engines[engineName];
-        meta.changed = true;
+        meta.changed = true;             // TODO: Should we still do this?
+
+        // We also here mark the engine as declined, because the pref
+        // was explicitly changed to false.
+        // This will be reflected in meta/global in the next stage.
+        this._log.trace("Engine " + engineName + " was disabled locally. Marking as declined.");
+        toDecline.add(engineName);
       } else {
         // The engine was enabled remotely. Enable it locally.
+        this._log.trace("Engine " + engineName + " was enabled. Marking as non-declined.");
+        toUndecline.add(engineName);
         this._log.trace(engineName + " engine was enabled remotely.");
         engine.enabled = true;
       }
     }
 
     // Any remaining engines were either enabled locally or disabled remotely.
     for each (let engineName in enabled) {
-      let engine = this.service.engineManager.get(engineName);
+      let engine = engineManager.get(engineName);
       if (Svc.Prefs.get("engineStatusChanged." + engine.prefName, false)) {
         this._log.trace("The " + engineName + " engine was enabled locally.");
+        toUndecline.add(engineName);
       } else {
         this._log.trace("The " + engineName + " engine was disabled remotely.");
+
+        // Don't automatically mark it as declined!
         engine.enabled = false;
       }
     }
 
+    this.service.engineManager.decline(toDecline);
+    this.service.engineManager.undecline(toUndecline);
+
     Svc.Prefs.resetBranch("engineStatusChanged.");
     this.service._ignorePrefObserver = false;
   },
 
+  _updateEnabledEngines: function () {
+    let meta = this.service.recordManager.get(this.service.metaURL);
+    let numClients = this.service.scheduler.numClients;
+    let engineManager = this.service.engineManager;
+
+    this._updateEnabledFromMeta(meta, numClients, engineManager);
+  },
 };
 Object.freeze(EngineSynchronizer.prototype);
--- a/services/sync/services-sync.js
+++ b/services/sync/services-sync.js
@@ -52,16 +52,17 @@ pref("services.sync.addons.trustedSource
 pref("services.sync.log.appender.console", "Warn");
 pref("services.sync.log.appender.dump", "Error");
 pref("services.sync.log.appender.file.level", "Trace");
 pref("services.sync.log.appender.file.logOnError", true);
 pref("services.sync.log.appender.file.logOnSuccess", false);
 pref("services.sync.log.appender.file.maxErrorAge", 864000); // 10 days
 pref("services.sync.log.rootLogger", "Debug");
 pref("services.sync.log.logger.addonutils", "Debug");
+pref("services.sync.log.logger.declined", "Debug");
 pref("services.sync.log.logger.service.main", "Debug");
 pref("services.sync.log.logger.status", "Debug");
 pref("services.sync.log.logger.authenticator", "Debug");
 pref("services.sync.log.logger.network.resources", "Debug");
 pref("services.sync.log.logger.service.jpakeclient", "Debug");
 pref("services.sync.log.logger.engine.bookmarks", "Debug");
 pref("services.sync.log.logger.engine.clients", "Debug");
 pref("services.sync.log.logger.engine.forms", "Debug");
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/unit/test_declined.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource://services-sync/stages/declined.js");
+Cu.import("resource://services-sync/stages/enginesync.js");
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/service.js");
+Cu.import("resource://services-common/observers.js");
+
+function run_test() {
+  run_next_test();
+}
+
+function PetrolEngine() {}
+PetrolEngine.prototype.name = "petrol";
+
+function DieselEngine() {}
+DieselEngine.prototype.name = "diesel";
+
+function DummyEngine() {}
+DummyEngine.prototype.name = "dummy";
+
+function ActualEngine() {}
+ActualEngine.prototype = {__proto__: Engine.prototype,
+                          name: 'actual'};
+
+function getEngineManager() {
+  let manager = new EngineManager(Service);
+  Service.engineManager = manager;
+  manager._engines = {
+    "petrol": new PetrolEngine(),
+    "diesel": new DieselEngine(),
+    "dummy": new DummyEngine(),
+    "actual": new ActualEngine(),
+  };
+  return manager;
+}
+
+/**
+ * 'Fetch' a meta/global record that doesn't mention declined.
+ *
+ * Push it into the EngineSynchronizer to set enabled; verify that those are
+ * correct.
+ *
+ * Then push it into DeclinedEngines to set declined; verify that none are
+ * declined, and a notification is sent for our locally disabled-but-not-
+ * declined engines.
+ */
+add_test(function testOldMeta() {
+  let meta = {
+    payload: {
+      engines: {
+        "petrol": 1,
+        "diesel": 2,
+        "nonlocal": 3,             // Enabled but not supported.
+      },
+    },
+  };
+
+  _("Record: " + JSON.stringify(meta));
+
+  let manager = getEngineManager();
+
+  // Update enabled from meta/global.
+  let engineSync = new EngineSynchronizer(Service);
+  engineSync._updateEnabledFromMeta(meta, 3, manager);
+
+  Assert.ok(manager._engines["petrol"].enabled, "'petrol' locally enabled.");
+  Assert.ok(manager._engines["diesel"].enabled, "'diesel' locally enabled.");
+  Assert.ok(!("nonlocal" in manager._engines), "We don't know anything about the 'nonlocal' engine.");
+  Assert.ok(!manager._engines["actual"].enabled, "'actual' not locally enabled.");
+  Assert.ok(!manager.isDeclined("actual"), "'actual' not declined, though.");
+
+  let declinedEngines = new DeclinedEngines(Service);
+
+  function onNotDeclined(subject, topic, data) {
+    Observers.remove("weave:engines:notdeclined", onNotDeclined);
+    Assert.ok(subject.undecided.has("actual"), "EngineManager observed that 'actual' was undecided.");
+
+    let declined = manager.getDeclined();
+    _("Declined: " + JSON.stringify(declined));
+
+    Assert.ok(!meta.changed, "No need to upload a new meta/global.");
+    run_next_test();
+  }
+
+  Observers.add("weave:engines:notdeclined", onNotDeclined);
+
+  declinedEngines.updateDeclined(meta, manager);
+});
+
+/**
+ * 'Fetch' a meta/global that declines an engine we don't
+ * recognize. Ensure that we track that declined engine along
+ * with any we locally declined, and that the meta/global
+ * record is marked as changed and includes all declined
+ * engines.
+ */
+add_test(function testDeclinedMeta() {
+  let meta = {
+    payload: {
+      engines: {
+        "petrol": 1,
+        "diesel": 2,
+        "nonlocal": 3,             // Enabled but not supported.
+      },
+      declined: ["nonexistent"],   // Declined and not supported.
+    },
+  };
+
+  _("Record: " + JSON.stringify(meta));
+
+  let manager = getEngineManager();
+  manager._engines["petrol"].enabled = true;
+  manager._engines["diesel"].enabled = true;
+  manager._engines["dummy"].enabled = true;
+  manager._engines["actual"].enabled = false;   // Disabled but not declined.
+
+  manager.decline(["localdecline"]);            // Declined and not supported.
+
+  let declinedEngines = new DeclinedEngines(Service);
+
+  function onNotDeclined(subject, topic, data) {
+    Observers.remove("weave:engines:notdeclined", onNotDeclined);
+    Assert.ok(subject.undecided.has("actual"), "EngineManager observed that 'actual' was undecided.");
+
+    let declined = manager.getDeclined();
+    _("Declined: " + JSON.stringify(declined));
+
+    Assert.equal(declined.indexOf("actual"), -1, "'actual' is locally disabled, but not marked as declined.");
+
+    Assert.equal(declined.indexOf("clients"), -1, "'clients' is enabled and not remotely declined.");
+    Assert.equal(declined.indexOf("petrol"), -1, "'petrol' is enabled and not remotely declined.");
+    Assert.equal(declined.indexOf("diesel"), -1, "'diesel' is enabled and not remotely declined.");
+    Assert.equal(declined.indexOf("dummy"), -1, "'dummy' is enabled and not remotely declined.");
+
+    Assert.ok(0 <= declined.indexOf("nonexistent"), "'nonexistent' was declined on the server.");
+
+    Assert.ok(0 <= declined.indexOf("localdecline"), "'localdecline' was declined locally.");
+
+    // The meta/global is modified, too.
+    Assert.ok(0 <= meta.payload.declined.indexOf("nonexistent"), "meta/global's declined contains 'nonexistent'.");
+    Assert.ok(0 <= meta.payload.declined.indexOf("localdecline"), "meta/global's declined contains 'localdecline'.");
+    Assert.strictEqual(true, meta.changed, "meta/global was changed.");
+
+    run_next_test();
+  }
+
+  Observers.add("weave:engines:notdeclined", onNotDeclined);
+
+  declinedEngines.updateDeclined(meta, manager);
+});
+
--- a/services/sync/tests/unit/test_enginemanager.js
+++ b/services/sync/tests/unit/test_enginemanager.js
@@ -1,44 +1,54 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/service.js");
 
 function run_test() {
+  run_next_test();
+}
+
+function PetrolEngine() {}
+PetrolEngine.prototype.name = "petrol";
+
+function DieselEngine() {}
+DieselEngine.prototype.name = "diesel";
+
+function DummyEngine() {}
+DummyEngine.prototype.name = "dummy";
+
+function ActualEngine() {}
+ActualEngine.prototype = {__proto__: Engine.prototype,
+                          name: 'actual'};
+
+add_test(function test_basics() {
   _("We start out with a clean slate");
 
   let manager = new EngineManager(Service);
 
   let engines = manager.getAll();
   do_check_eq(engines.length, 0);
   do_check_eq(manager.get('dummy'), undefined);
 
   _("Register an engine");
-  function DummyEngine() {}
-  DummyEngine.prototype.name = "dummy";
   manager.register(DummyEngine);
   let dummy = manager.get('dummy');
   do_check_true(dummy instanceof DummyEngine);
 
   engines = manager.getAll();
   do_check_eq(engines.length, 1);
   do_check_eq(engines[0], dummy);
 
   _("Register an already registered engine is ignored");
   manager.register(DummyEngine);
   do_check_eq(manager.get('dummy'), dummy);
 
   _("Register multiple engines in one go");
-  function PetrolEngine() {}
-  PetrolEngine.prototype.name = "petrol";
-  function DieselEngine() {}
-  DieselEngine.prototype.name = "diesel";
-
   manager.register([PetrolEngine, DieselEngine]);
   let petrol = manager.get('petrol');
   let diesel = manager.get('diesel');
   do_check_true(petrol instanceof PetrolEngine);
   do_check_true(diesel instanceof DieselEngine);
 
   engines = manager.getAll();
   do_check_eq(engines.length, 3);
@@ -69,19 +79,19 @@ function run_test() {
   manager.unregister('dummy');
   do_check_eq(manager.get('dummy'), undefined);
   engines = manager.getAll();
   do_check_eq(engines.length, 2);
   do_check_eq(engines.indexOf(dummy), -1);
 
   _("Unregister an engine by value");
   // manager.unregister() checks for instanceof Engine, so let's make one:
-  function ActualEngine() {}
-  ActualEngine.prototype = {__proto__: Engine.prototype,
-                            name: 'actual'};
   manager.register(ActualEngine);
   let actual = manager.get('actual');
   do_check_true(actual instanceof ActualEngine);
   do_check_true(actual instanceof Engine);
 
   manager.unregister(actual);
   do_check_eq(manager.get('actual'), undefined);
-}
+
+  run_next_test();
+});
+
--- a/services/sync/tests/unit/test_load_modules.js
+++ b/services/sync/tests/unit/test_load_modules.js
@@ -21,16 +21,17 @@ const modules = [
   "main.js",
   "notifications.js",
   "policies.js",
   "record.js",
   "resource.js",
   "rest.js",
   "service.js",
   "stages/cluster.js",
+  "stages/declined.js",
   "stages/enginesync.js",
   "status.js",
   "userapi.js",
   "util.js",
 ];
 
 const testingModules = [
   "fakeservices.js",
--- a/services/sync/tests/unit/xpcshell.ini
+++ b/services/sync/tests/unit/xpcshell.ini
@@ -97,16 +97,17 @@ skip-if = os == "android"
 skip-if = os == "android"
 [test_service_verifyLogin.js]
 [test_service_wipeClient.js]
 [test_service_wipeServer.js]
 # Bug 752243: Profile cleanup frequently fails
 skip-if = os == "mac" || os == "linux"
 
 [test_corrupt_keys.js]
+[test_declined.js]
 [test_errorhandler.js]
 [test_errorhandler_filelog.js]
 # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini)
 skip-if = os == "android"
 [test_errorhandler_sync_checkServerError.js]
 # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini)
 skip-if = os == "android"
 [test_errorhandler_eol.js]
--- a/toolkit/components/crashes/CrashManager.jsm
+++ b/toolkit/components/crashes/CrashManager.jsm
@@ -511,16 +511,24 @@ this.CrashManager.prototype = Object.fre
    */
   getCrashes: function () {
     return Task.spawn(function* () {
       let store = yield this._getStore();
 
       return store.crashes;
     }.bind(this));
   },
+
+  getCrashCountsByDay: function () {
+    return Task.spawn(function* () {
+      let store = yield this._getStore();
+
+      return store._countsByDay;
+    }.bind(this));
+  },
 });
 
 let gCrashManager;
 
 /**
  * Interface to storage of crash data.
  *
  * This type handles storage of crash metadata. It exists as a separate type
--- a/toolkit/components/crashes/docs/index.rst
+++ b/toolkit/components/crashes/docs/index.rst
@@ -1,8 +1,10 @@
+.. _crashes_crashmanager:
+
 =============
 Crash Manager
 =============
 
 The **Crash Manager** is a service and interface for managing crash
 data within the Gecko application.
 
 From JavaScript, the service can be accessed via::
--- a/toolkit/devtools/LayoutHelpers.jsm
+++ b/toolkit/devtools/LayoutHelpers.jsm
@@ -19,87 +19,83 @@ this.LayoutHelpers = LayoutHelpers = fun
   this._topDocShell = aTopLevelWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                      .getInterface(Ci.nsIWebNavigation)
                                      .QueryInterface(Ci.nsIDocShell);
 };
 
 LayoutHelpers.prototype = {
 
   /**
-   * Compute the position and the dimensions for the visible portion
-   * of a node, relativalely to the root window.
+   * Get box quads adjusted for iframes and zoom level.
    *
-   * @param nsIDOMNode aNode
-   *        a DOM element to be highlighted
+   * @param {DOMNode} node
+   *        The node for which we are to get the box model region quads
+   * @param  {String} region
+   *         The box model region to return:
+   *         "content", "padding", "border" or "margin"
    */
-  getDirtyRect: function LH_getDirectyRect(aNode) {
-    let frameWin = aNode.ownerDocument.defaultView;
-    let clientRect = aNode.getBoundingClientRect();
-
-    // Go up in the tree of frames to determine the correct rectangle.
-    // clientRect is read-only, we need to be able to change properties.
-    rect = {top: clientRect.top,
-            left: clientRect.left,
-            width: clientRect.width,
-            height: clientRect.height};
-
-    // We iterate through all the parent windows.
-    while (true) {
-
-      // Does the selection overflow on the right of its window?
-      let diffx = frameWin.innerWidth - (rect.left + rect.width);
-      if (diffx < 0) {
-        rect.width += diffx;
-      }
-
-      // Does the selection overflow on the bottom of its window?
-      let diffy = frameWin.innerHeight - (rect.top + rect.height);
-      if (diffy < 0) {
-        rect.height += diffy;
-      }
+  getAdjustedQuads: function(node, region) {
+    if (!node) {
+      return;
+    }
 
-      // Does the selection overflow on the left of its window?
-      if (rect.left < 0) {
-        rect.width += rect.left;
-        rect.left = 0;
-      }
-
-      // Does the selection overflow on the top of its window?
-      if (rect.top < 0) {
-        rect.height += rect.top;
-        rect.top = 0;
-      }
-
-      // Selection has been clipped to fit in its own window.
+    let [quads] = node.getBoxQuads({
+      box: region
+    });
 
-      // Are we in the top-level window?
-      if (this.isTopLevelWindow(frameWin)) {
-        break;
-      }
-
-      let frameElement = this.getFrameElement(frameWin);
-      if (!frameElement) {
-        break;
-      }
-
-      // We are in an iframe.
-      // We take into account the parent iframe position and its
-      // offset (borders and padding).
-      let frameRect = frameElement.getBoundingClientRect();
-
-      let [offsetTop, offsetLeft] =
-        this.getIframeContentOffset(frameElement);
-
-      rect.top += frameRect.top + offsetTop;
-      rect.left += frameRect.left + offsetLeft;
-
-      frameWin = this.getParentWindow(frameWin);
+    if (!quads) {
+      return;
     }
 
-    return rect;
+    let [xOffset, yOffset] = this._getNodeOffsets(node);
+    let scale = this.calculateScale(node);
+
+    return {
+      p1: {
+        w: quads.p1.w * scale,
+        x: quads.p1.x * scale + xOffset,
+        y: quads.p1.y * scale + yOffset,
+        z: quads.p1.z * scale
+      },
+      p2: {
+        w: quads.p2.w * scale,
+        x: quads.p2.x * scale + xOffset,
+        y: quads.p2.y * scale + yOffset,
+        z: quads.p2.z * scale
+      },
+      p3: {
+        w: quads.p3.w * scale,
+        x: quads.p3.x * scale + xOffset,
+        y: quads.p3.y * scale + yOffset,
+        z: quads.p3.z * scale
+      },
+      p4: {
+        w: quads.p4.w * scale,
+        x: quads.p4.x * scale + xOffset,
+        y: quads.p4.y * scale + yOffset,
+        z: quads.p4.z * scale
+      },
+      bounds: {
+        bottom: quads.bounds.bottom * scale + yOffset,
+        height: quads.bounds.height * scale,
+        left: quads.bounds.left * scale + xOffset,
+        right: quads.bounds.right * scale + xOffset,
+        top: quads.bounds.top * scale + yOffset,
+        width: quads.bounds.width * scale,
+        x: quads.bounds.x * scale + xOffset,
+        y: quads.bounds.y * scale + yOffset
+      }
+    };
+  },
+
+  calculateScale: function(node) {
+    let win = node.ownerDocument.defaultView;
+    let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                      .getInterface(Ci.nsIDOMWindowUtils);
+    return winUtils.fullZoom;
   },
 
   /**
    * Compute the absolute position and the dimensions of a node, relativalely
    * to the root window.
    *
    * @param nsIDOMNode aNode
    *        a DOM element to get the bounds for
@@ -107,17 +103,17 @@ LayoutHelpers.prototype = {
    *        the content window holding the node
    */
   getRect: function LH_getRect(aNode, aContentWindow) {
     let frameWin = aNode.ownerDocument.defaultView;
     let clientRect = aNode.getBoundingClientRect();
 
     // Go up in the tree of frames to determine the correct rectangle.
     // clientRect is read-only, we need to be able to change properties.
-    rect = {top: clientRect.top + aContentWindow.pageYOffset,
+    let rect = {top: clientRect.top + aContentWindow.pageYOffset,
             left: clientRect.left + aContentWindow.pageXOffset,
             width: clientRect.width,
             height: clientRect.height};
 
     // We iterate through all the parent windows.
     while (true) {
 
       // Are we in the top-level window?
@@ -174,36 +170,16 @@ LayoutHelpers.prototype = {
 
     let borderTop = parseInt(style.getPropertyValue("border-top-width"));
     let borderLeft = parseInt(style.getPropertyValue("border-left-width"));
 
     return [borderTop + paddingTop, borderLeft + paddingLeft];
   },
 
   /**
-   * Apply the page zoom factor.
-   */
-  getZoomedRect: function LH_getZoomedRect(aWin, aRect) {
-    // get page zoom factor, if any
-    let zoom =
-      aWin.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-        .getInterface(Components.interfaces.nsIDOMWindowUtils)
-        .fullZoom;
-
-    // adjust rect for zoom scaling
-    let aRectScaled = {};
-    for (let prop in aRect) {
-      aRectScaled[prop] = aRect[prop] * zoom;
-    }
-
-    return aRectScaled;
-  },
-
-
-  /**
    * Find an element from the given coordinates. This method descends through
    * frames to find the element the user clicked inside frames.
    *
    * @param DOMDocument aDocument the document to look into.
    * @param integer aX
    * @param integer aY
    * @returns Node|null the element node found at the given coordinates.
    */
@@ -238,18 +214,17 @@ LayoutHelpers.prototype = {
   /**
    * Scroll the document so that the element "elem" appears in the viewport.
    *
    * @param Element elem the element that needs to appear in the viewport.
    * @param bool centered true if you want it centered, false if you want it to
    * appear on the top of the viewport. It is true by default, and that is
    * usually what you want.
    */
-  scrollIntoViewIfNeeded:
-  function LH_scrollIntoViewIfNeeded(elem, centered) {
+  scrollIntoViewIfNeeded: function(elem, centered) {
     // We want to default to centering the element in the page,
     // so as to keep the context of the element.
     centered = centered === undefined? true: !!centered;
 
     let win = elem.ownerDocument.defaultView;
     let clientRect = elem.getBoundingClientRect();
 
     // The following are always from the {top, bottom, left, right}
@@ -392,9 +367,143 @@ LayoutHelpers.prototype = {
     }
 
     let winUtils = win.
       QueryInterface(Components.interfaces.nsIInterfaceRequestor).
       getInterface(Components.interfaces.nsIDOMWindowUtils);
 
     return winUtils.containerElement;
   },
+
+  /**
+   * Get the x and y offsets for a node taking iframes into account.
+   *
+   * @param {DOMNode} node
+   *        The node for which we are to get the offset
+   */
+  _getNodeOffsets: function(node) {
+    let xOffset = 0;
+    let yOffset = 0;
+    let frameWin = node.ownerDocument.defaultView;
+    let scale = this.calculateScale(node);
+
+    while (true) {
+      // Are we in the top-level window?
+      if (this.isTopLevelWindow(frameWin)) {
+        break;
+      }
+
+      let frameElement = this.getFrameElement(frameWin);
+      if (!frameElement) {
+        break;
+      }
+
+      // We are in an iframe.
+      // We take into account the parent iframe position and its
+      // offset (borders and padding).
+      let frameRect = frameElement.getBoundingClientRect();
+
+      let [offsetTop, offsetLeft] =
+        this.getIframeContentOffset(frameElement);
+
+      xOffset += frameRect.left + offsetLeft;
+      yOffset += frameRect.top + offsetTop;
+
+      frameWin = this.getParentWindow(frameWin);
+    }
+
+    return [xOffset * scale, yOffset * scale];
+  },
+
+
+
+  /********************************************************************
+   * GetBoxQuads POLYFILL START TODO: Remove this when bug 917755 is fixed.
+   ********************************************************************/
+  _getBoxQuadsFromRect: function(rect, node) {
+    let scale = this.calculateScale(node);
+    let [xOffset, yOffset] = this._getNodeOffsets(node);
+
+    let out = {
+      p1: {
+        x: rect.left * scale + xOffset,
+        y: rect.top * scale + yOffset
+      },
+      p2: {
+        x: (rect.left + rect.width) * scale + xOffset,
+        y: rect.top * scale + yOffset
+      },
+      p3: {
+        x: (rect.left + rect.width) * scale + xOffset,
+        y: (rect.top + rect.height) * scale + yOffset
+      },
+      p4: {
+        x: rect.left * scale + xOffset,
+        y: (rect.top + rect.height) * scale + yOffset
+      }
+    };
+
+    out.bounds = {
+      bottom: out.p4.y,
+      height: out.p4.y - out.p1.y,
+      left: out.p1.x,
+      right: out.p2.x,
+      top: out.p1.y,
+      width: out.p2.x - out.p1.x,
+      x: out.p1.x,
+      y: out.p1.y
+    };
+
+    return out;
+  },
+
+  _parseNb: function(distance) {
+    let nb = parseFloat(distance, 10);
+    return isNaN(nb) ? 0 : nb;
+  },
+
+  getAdjustedQuadsPolyfill: function(node, region) {
+    // Get the border-box rect
+    // Note that this is relative to the node's viewport, so before we can use
+    // it, will need to go back up the frames like getRect
+    let borderRect = node.getBoundingClientRect();
+
+    // If the boxType is border, no need to go any further, we're done
+    if (region === "border") {
+      return this._getBoxQuadsFromRect(borderRect, node);
+    }
+
+    // Else, need to get margin/padding/border distances
+    let style = node.ownerDocument.defaultView.getComputedStyle(node);
+    let camel = s => s.substring(0, 1).toUpperCase() + s.substring(1);
+    let distances = {border:{}, padding:{}, margin: {}};
+
+    for (let side of ["top", "right", "bottom", "left"]) {
+      distances.border[side] = this._parseNb(style["border" + camel(side) + "Width"]);
+      distances.padding[side] = this._parseNb(style["padding" + camel(side)]);
+      distances.margin[side] = this._parseNb(style["margin" + camel(side)]);
+    }
+
+    // From the border-box rect, calculate the content-box, padding-box and
+    // margin-box rects
+    function offsetRect(rect, offsetType, dir=1) {
+      return {
+        top: rect.top + (dir * distances[offsetType].top),
+        left: rect.left + (dir * distances[offsetType].left),
+        width: rect.width - (dir * (distances[offsetType].left + distances[offsetType].right)),
+        height: rect.height - (dir * (distances[offsetType].top + distances[offsetType].bottom))
+      };
+    }
+
+    if (region === "margin") {
+      return this._getBoxQuadsFromRect(offsetRect(borderRect, "margin", -1), node);
+    } else if (region === "padding") {
+      return this._getBoxQuadsFromRect(offsetRect(borderRect, "border"), node);
+    } else if (region === "content") {
+      let paddingRect = offsetRect(borderRect, "border");
+      return this._getBoxQuadsFromRect(offsetRect(paddingRect, "padding"), node);
+    }
+  },
+
+  /********************************************************************
+   * GetBoxQuads POLYFILL END
+   ********************************************************************/
 };
--- a/toolkit/devtools/server/actors/highlighter.js
+++ b/toolkit/devtools/server/actors/highlighter.js
@@ -4,52 +4,66 @@
 
 "use strict";
 
 const {Cu, Cc, Ci} = require("chrome");
 const Services = require("Services");
 const protocol = require("devtools/server/protocol");
 const {Arg, Option, method} = protocol;
 const events = require("sdk/event/core");
+
+const EventEmitter = require("devtools/toolkit/event-emitter");
+const GUIDE_STROKE_WIDTH = 1;
+
 // Make sure the domnode type is known here
 require("devtools/server/actors/inspector");
 
 Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 // FIXME: add ":visited" and ":link" after bug 713106 is fixed
 const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
 const HIGHLIGHTED_PSEUDO_CLASS = ":-moz-devtools-highlighted";
 let HELPER_SHEET = ".__fx-devtools-hide-shortcut__ { visibility: hidden !important } ";
 HELPER_SHEET += ":-moz-devtools-highlighted { outline: 2px dashed #F06!important; outline-offset: -2px!important } ";
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
+const SVG_NS = "http://www.w3.org/2000/svg";
 const HIGHLIGHTER_PICKED_TIMER = 1000;
+const INFO_BAR_OFFSET = 5;
 
 /**
  * The HighlighterActor is the server-side entry points for any tool that wishes
  * to highlight elements in the content document.
  *
  * The highlighter can be retrieved via the inspector's getHighlighter method.
  */
 
 /**
  * The HighlighterActor class
  */
 let HighlighterActor = protocol.ActorClass({
   typeName: "highlighter",
 
-  initialize: function(inspector) {
+  initialize: function(inspector, autohide) {
     protocol.Actor.prototype.initialize.call(this, null);
 
+    this._autohide = autohide;
     this._inspector = inspector;
     this._walker = this._inspector.walker;
     this._tabActor = this._inspector.tabActor;
 
+    this._highlighterReady = this._highlighterReady.bind(this);
+    this._highlighterHidden = this._highlighterHidden.bind(this);
+
     if (this._supportsBoxModelHighlighter()) {
-      this._boxModelHighlighter = new BoxModelHighlighter(this._tabActor);
+      this._boxModelHighlighter =
+        new BoxModelHighlighter(this._tabActor, this._inspector);
+
+        this._boxModelHighlighter.on("ready", this._highlighterReady);
+        this._boxModelHighlighter.on("hide", this._highlighterHidden);
     } else {
       this._boxModelHighlighter = new SimpleOutlineHighlighter(this._tabActor);
     }
   },
 
   get conn() this._inspector && this._inspector.conn,
 
   /**
@@ -58,45 +72,48 @@ let HighlighterActor = protocol.ActorCla
    */
   _supportsBoxModelHighlighter: function() {
     return this._tabActor.browser && !!this._tabActor.browser.parentNode;
   },
 
   destroy: function() {
     protocol.Actor.prototype.destroy.call(this);
     if (this._boxModelHighlighter) {
+      this._boxModelHighlighter.off("ready", this._highlighterReady);
+      this._boxModelHighlighter.off("hide", this._highlighterHidden);
       this._boxModelHighlighter.destroy();
       this._boxModelHighlighter = null;
     }
+    this._autohide = null;
     this._inspector = null;
     this._walker = null;
     this._tabActor = null;
   },
 
   /**
    * Display the box model highlighting on a given NodeActor.
    * There is only one instance of the box model highlighter, so calling this
    * method several times won't display several highlighters, it will just move
    * the highlighter instance to these nodes.
    *
    * @param NodeActor The node to be highlighted
    * @param Options See the request part for existing options. Note that not
-   * all options may be supported by all types of highlighters. The simple
-   * outline highlighter for instance does not scrollIntoView
+   * all options may be supported by all types of highlighters.
    */
   showBoxModel: method(function(node, options={}) {
     if (node && this._isNodeValidForHighlighting(node.rawNode)) {
       this._boxModelHighlighter.show(node.rawNode, options);
     } else {
       this._boxModelHighlighter.hide();
     }
   }, {
     request: {
       node: Arg(0, "domnode"),
-      scrollIntoView: Option(1)
+      scrollIntoView: Option(1),
+      region: Option(1)
     }
   }),
 
   _isNodeValidForHighlighting: function(node) {
     // Is it null or dead?
     let isNotDead = node && !Cu.isDeadWrapper(node);
 
     // Is it connected to the document?
@@ -130,34 +147,37 @@ let HighlighterActor = protocol.ActorCla
    * mousemove, and click listeners on the content document to fire
    * events and let connected clients know when nodes are hovered over or
    * clicked.
    *
    * Once a node is picked, events will cease, and listeners will be removed.
    */
   _isPicking: false,
   _hoveredNode: null,
+
   pick: method(function() {
     if (this._isPicking) {
       return null;
     }
     this._isPicking = true;
 
     this._preventContentEvent = event => {
       event.stopPropagation();
       event.preventDefault();
     };
 
     this._onPick = event => {
       this._preventContentEvent(event);
       this._stopPickerListeners();
       this._isPicking = false;
-      this._tabActor.window.setTimeout(() => {
-        this._boxModelHighlighter.hide();
-      }, HIGHLIGHTER_PICKED_TIMER);
+      if (this._autohide) {
+        this._tabActor.window.setTimeout(() => {
+          this._boxModelHighlighter.hide();
+        }, HIGHLIGHTER_PICKED_TIMER);
+      }
       events.emit(this._walker, "picker-node-picked", this._findAndAttachElement(event));
     };
 
     this._onHovered = event => {
       this._preventContentEvent(event);
       let res = this._findAndAttachElement(event);
       if (this._hoveredNode !== res.node) {
         this._boxModelHighlighter.show(res.node.rawNode);
@@ -212,16 +232,24 @@ let HighlighterActor = protocol.ActorCla
     let target = this._getPickerListenerTarget();
     target.removeEventListener("mousemove", this._onHovered, true);
     target.removeEventListener("click", this._onPick, true);
     target.removeEventListener("mousedown", this._preventContentEvent, true);
     target.removeEventListener("mouseup", this._preventContentEvent, true);
     target.removeEventListener("dblclick", this._preventContentEvent, true);
   },
 
+  _highlighterReady: function() {
+    events.emit(this._inspector.walker, "highlighter-ready");
+  },
+
+  _highlighterHidden: function() {
+    events.emit(this._inspector.walker, "highlighter-hide");
+  },
+
   cancelPick: method(function() {
     if (this._isPicking) {
       this._boxModelHighlighter.hide();
       this._stopPickerListeners();
       this._isPicking = false;
       this._hoveredNode = null;
     }
   })
@@ -242,69 +270,108 @@ let HighlighterFront = protocol.FrontCla
  * Usage example:
  *
  * let h = new BoxModelHighlighter(browser);
  * h.show(node);
  * h.hide();
  * h.destroy();
  *
  * Structure:
- *  <stack class="highlighter-container">
- *    <box class="highlighter-outline-container">
- *      <box class="highlighter-outline" />
- *    </box>
- *    <box class="highlighter-nodeinfobar-container">
- *      <box class="highlighter-nodeinfobar-positioner" position="top/bottom">
- *        <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top"/>
- *        <hbox class="highlighter-nodeinfobar">
- *          <hbox class="highlighter-nodeinfobar-text">tagname#id.class1.class2</hbox>
- *        </hbox>
- *        <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom"/>
- *      </box>
- *    </box>
- *  </stack>
+ * <stack class="highlighter-container">
+ *   <svg class="box-model-root" hidden="true">
+ *     <g class="box-model-container">
+ *       <polygon class="box-model-margin" points="317,122 747,36 747,181 317,267" />
+ *       <polygon class="box-model-border" points="317,128 747,42 747,161 317,247" />
+ *       <polygon class="box-model-padding" points="323,127 747,42 747,161 323,246" />
+ *       <polygon class="box-model-content" points="335,137 735,57 735,152 335,232" />
+ *     </g>
+ *     <line class="box-model-guide-top" x1="0" y1="592" x2="99999" y2="592" />
+ *     <line class="box-model-guide-right" x1="735" y1="0" x2="735" y2="99999" />
+ *     <line class="box-model-guide-bottom" x1="0" y1="612" x2="99999" y2="612" />
+ *     <line class="box-model-guide-left" x1="334" y1="0" x2="334" y2="99999" />
+ *   </svg>
+ *   <box class="highlighter-nodeinfobar-container">
+ *     <box class="highlighter-nodeinfobar-positioner" position="top" />
+ *       <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top" />
+ *       <hbox class="highlighter-nodeinfobar">
+ *         <hbox class="highlighter-nodeinfobar-text" align="center" flex="1">
+ *           <span class="highlighter-nodeinfobar-tagname">Node name</span>
+ *           <span class="highlighter-nodeinfobar-id">Node id</span>
+ *           <span class="highlighter-nodeinfobar-classes">.someClass</span>
+ *           <span class="highlighter-nodeinfobar-pseudo-classes">:hover</span>
+ *         </hbox>
+ *       </hbox>
+ *       <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom"/>
+ *     </box>
+ *   </box>
+ * </stack>
  */
-function BoxModelHighlighter(tabActor) {
+function BoxModelHighlighter(tabActor, inspector) {
   this.browser = tabActor.browser;
   this.win = tabActor.window;
   this.chromeDoc = this.browser.ownerDocument;
   this.chromeWin = this.chromeDoc.defaultView;
+  this._inspector = inspector;
 
   this.layoutHelpers = new LayoutHelpers(this.win);
   this.chromeLayoutHelper = new LayoutHelpers(this.chromeWin);
 
   this.transitionDisabler = null;
   this.pageEventsMuter = null;
   this._update = this._update.bind(this);
+  this.handleEvent = this.handleEvent.bind(this);
   this.currentNode = null;
 
+  EventEmitter.decorate(this);
   this._initMarkup();
 }
 
 BoxModelHighlighter.prototype = {
+  get zoom() {
+    return this.win.QueryInterface(Ci.nsIInterfaceRequestor)
+               .getInterface(Ci.nsIDOMWindowUtils).fullZoom;
+  },
+
   _initMarkup: function() {
     let stack = this.browser.parentNode;
 
-    this.highlighterContainer = this.chromeDoc.createElement("stack");
-    this.highlighterContainer.className = "highlighter-container";
+    this._highlighterContainer = this.chromeDoc.createElement("stack");
+    this._highlighterContainer.className = "highlighter-container";
+
+    this._svgRoot = this._createSVGNode("root", "svg", this._highlighterContainer);
+
+    this._boxModelContainer = this._createSVGNode("container", "g", this._svgRoot);
+
+    this._boxModelNodes = {
+      margin: this._createSVGNode("margin", "polygon", this._boxModelContainer),
+      border: this._createSVGNode("border", "polygon", this._boxModelContainer),
+      padding: this._createSVGNode("padding", "polygon", this._boxModelContainer),
+      content: this._createSVGNode("content", "polygon", this._boxModelContainer)
+    };
 
-    this.outline = this.chromeDoc.createElement("box");
-    this.outline.className = "highlighter-outline";
+    this._guideNodes = {
+      top: this._createSVGNode("guide-top", "line", this._svgRoot),
+      right: this._createSVGNode("guide-right", "line", this._svgRoot),
+      bottom: this._createSVGNode("guide-bottom", "line", this._svgRoot),
+      left: this._createSVGNode("guide-left", "line", this._svgRoot)
+    };
 
-    let outlineContainer = this.chromeDoc.createElement("box");
-    outlineContainer.appendChild(this.outline);
-    outlineContainer.className = "highlighter-outline-container";
-    this.highlighterContainer.appendChild(outlineContainer);
+    this._guideNodes.top.setAttribute("stroke-width", GUIDE_STROKE_WIDTH);
+    this._guideNodes.right.setAttribute("stroke-width", GUIDE_STROKE_WIDTH);
+    this._guideNodes.bottom.setAttribute("stroke-width", GUIDE_STROKE_WIDTH);
+    this._guideNodes.left.setAttribute("stroke-width", GUIDE_STROKE_WIDTH);
+
+    this._highlighterContainer.appendChild(this._svgRoot);
 
     let infobarContainer = this.chromeDoc.createElement("box");
     infobarContainer.className = "highlighter-nodeinfobar-container";
-    this.highlighterContainer.appendChild(infobarContainer);
+    this._highlighterContainer.appendChild(infobarContainer);
 
     // Insert the highlighter right after the browser
-    stack.insertBefore(this.highlighterContainer, stack.childNodes[1]);
+    stack.insertBefore(this._highlighterContainer, stack.childNodes[1]);
 
     // Building the infobar
     let infobarPositioner = this.chromeDoc.createElement("box");
     infobarPositioner.className = "highlighter-nodeinfobar-positioner";
     infobarPositioner.setAttribute("position", "top");
     infobarPositioner.setAttribute("disabled", "true");
 
     let nodeInfobar = this.chromeDoc.createElement("hbox");
@@ -357,66 +424,74 @@ BoxModelHighlighter.prototype = {
       idLabel: idLabel,
       classesBox: classesBox,
       pseudoClassesBox: pseudoClassesBox,
       positioner: infobarPositioner,
       barHeight: barHeight,
     };
   },
 
+  _createSVGNode: function(classPostfix, nodeType, parent) {
+    let node = this.chromeDoc.createElementNS(SVG_NS, nodeType);
+    node.setAttribute("class", "box-model-" + classPostfix);
+
+    parent.appendChild(node);
+
+    return node;
+  },
+
   /**
    * Destroy the nodes. Remove listeners.
    */
   destroy: function() {
     this.hide();
 
     this.chromeWin.clearTimeout(this.transitionDisabler);
     this.chromeWin.clearTimeout(this.pageEventsMuter);
 
-    this._contentRect = null;
-    this._highlightRect = null;
-    this.outline = null;
     this.nodeInfo = null;
 
-    this.highlighterContainer.remove();
-    this.highlighterContainer = null;
+    this._highlighterContainer.remove();
+    this._highlighterContainer = null;
 
-    this.win = null
+    this.rect = null;
+    this.win = null;
     this.browser = null;
     this.chromeDoc = null;
     this.chromeWin = null;
     this.currentNode = null;
   },
 
   /**
    * Show the highlighter on a given node
    *
    * @param {DOMNode} node
+   * @param {Object} options
+   *        Object used for passing options
    */
   show: function(node, options={}) {
-    if (!this.currentNode || node !== this.currentNode) {
-      this.currentNode = node;
+    this.currentNode = node;
 
-      this._showInfobar();
-      this._computeZoomFactor();
-      this._detachPageListeners();
-      this._attachPageListeners();
-      this._update();
-      this._trackMutations();
+    this._showInfobar();
+    this._detachPageListeners();
+    this._attachPageListeners();
+    this._update();
+    this._trackMutations();
 
-      if (options.scrollIntoView) {
-        this.chromeLayoutHelper.scrollIntoViewIfNeeded(node);
-      }
+    if (options.scrollIntoView) {
+      this.chromeLayoutHelper.scrollIntoViewIfNeeded(node);
     }
   },
 
   _trackMutations: function() {
     if (this.currentNode) {
       let win = this.currentNode.ownerDocument.defaultView;
-      this.currentNodeObserver = new win.MutationObserver(this._update);
+      this.currentNodeObserver = new win.MutationObserver(() => {
+        this._update();
+      });
       this.currentNodeObserver.observe(this.currentNode, {attributes: true});
     }
   },
 
   _untrackMutations: function() {
     if (this.currentNode) {
       if (this.currentNodeObserver) {
         // The following may fail with a "can't access dead object" exception
@@ -428,136 +503,208 @@ BoxModelHighlighter.prototype = {
       }
     }
   },
 
   /**
    * Update the highlighter on the current highlighted node (the one that was
    * passed as an argument to show(node)).
    * Should be called whenever node size or attributes change
-   * @param {Boolean} brieflyDisableTransitions
-   *        In case _update is called during scrolling or repaint, set this
-   *        to true to avoid transitions
+   * @param {Object} options
+   *        Object used for passing options. Valid options are:
+   *          - box: "content", "padding", "border" or "margin." This specifies
+   *            the box that the guides should outline. Default is content.
    */
-  _update: function(brieflyDisableTransitions) {
+  _update: function(options={}) {
     if (this.currentNode) {
-      let rect = this.layoutHelpers.getDirtyRect(this.currentNode);
-
-      if (this._highlightRectangle(rect, brieflyDisableTransitions)) {
-        this._moveInfobar();
-        this._updateInfobar();
+      if (this._highlightBoxModel(options)) {
+        this._showInfobar();
       } else {
         // Nothing to highlight (0px rectangle like a <script> tag for instance)
         this.hide();
       }
+      this.emit("ready");
     }
   },
 
   /**
    * Hide the highlighter, the outline and the infobar.
    */
   hide: function() {
     if (this.currentNode) {
       this._untrackMutations();
       this.currentNode = null;
-      this._hideOutline();
+      this._hideBoxModel();
       this._hideInfobar();
       this._detachPageListeners();
     }
+    this.emit("hide");
   },
 
   /**
    * Hide the infobar
    */
   _hideInfobar: function() {
-    this.nodeInfo.positioner.setAttribute("force-transitions", "true");
     this.nodeInfo.positioner.setAttribute("hidden", "true");
   },
 
   /**
    * Show the infobar
    */
   _showInfobar: function() {
     this.nodeInfo.positioner.removeAttribute("hidden");
-    this._moveInfobar();
-    this.nodeInfo.positioner.removeAttribute("force-transitions");
+    this._updateInfobar();
   },
 
   /**
-   * Hide the outline
+   * Hide the box model
    */
-  _hideOutline: function() {
-    this.outline.setAttribute("hidden", "true");
+  _hideBoxModel: function() {
+    this._svgRoot.setAttribute("hidden", "true");
   },
 
   /**
-   * Show the outline
+   * Show the box model
    */
-  _showOutline: function() {
-    this.outline.removeAttribute("hidden");
+  _showBoxModel: function() {
+    this._svgRoot.removeAttribute("hidden");
   },
 
   /**
-   * Highlight a rectangular region.
+   * Highlight the box model.
    *
-   * @param {object} aRect
-   *        The rectangle region to highlight.
-   * @param {boolean} brieflyDisableTransitions
-   *        Set to true to avoid transitions during the highlighting
+   * @param {Object} options
+   *        Object used for passing options. Valid options are:
+   *          - region: "content", "padding", "border" or "margin." This specifies
+   *            the region that the guides should outline. Default is content.
    * @return {boolean}
    *         True if the rectangle was highlighted, false otherwise.
    */
-  _highlightRectangle: function(aRect, brieflyDisableTransitions) {
-    if (!aRect) {
-      return false;
-    }
-
-    let oldRect = this._contentRect;
-
-    if (oldRect && aRect.top == oldRect.top && aRect.left == oldRect.left &&
-        aRect.width == oldRect.width && aRect.height == oldRect.height) {
-      this._showOutline();
-      return true; // same rectangle
-    }
-
-    let aRectScaled = this.layoutHelpers.getZoomedRect(this.win, aRect);
+  _highlightBoxModel: function(options) {
     let isShown = false;
 
-    if (aRectScaled.left >= 0 && aRectScaled.top >= 0 &&
-        aRectScaled.width > 0 && aRectScaled.height > 0) {
+    options.region = options.region || "content";
+
+    // TODO: Remove this polyfill
+    this.rect =
+      this.layoutHelpers.getAdjustedQuadsPolyfill(this.currentNode, "margin");
+
+    if (!this.rect) {
+      return null;
+    }
 
-      // The bottom div and the right div are flexibles (flex=1).
-      // We don't need to resize them.
-      let top = "top:" + aRectScaled.top + "px;";
-      let left = "left:" + aRectScaled.left + "px;";
-      let width = "width:" + aRectScaled.width + "px;";
-      let height = "height:" + aRectScaled.height + "px;";
+    if (this.rect.bounds.width > 0 && this.rect.bounds.height > 0) {
+      for (let boxType in this._boxModelNodes) {
+        // TODO: Remove this polyfill
+        let {p1, p2, p3, p4} = boxType === "margin" ? this.rect :
+          this.layoutHelpers.getAdjustedQuadsPolyfill(this.currentNode, boxType);
 
-      if (brieflyDisableTransitions) {
-        this._brieflyDisableTransitions();
+        let boxNode = this._boxModelNodes[boxType];
+        boxNode.setAttribute("points",
+                             p1.x + "," + p1.y + " " +
+                             p2.x + "," + p2.y + " " +
+                             p3.x + "," + p3.y + " " +
+                             p4.x + "," + p4.y);
+
+        if (boxType === options.region) {
+          this._showGuides(p1, p2, p3, p4);
+        }
       }
 
-      this.outline.setAttribute("style", top + left + width + height);
-
       isShown = true;
-      this._showOutline();
+      this._showBoxModel();
     } else {
       // Only return false if the element really is invisible.
       // A height of 0 and a non-0 width corresponds to a visible element that
       // is below the fold for instance
-      if (aRectScaled.width > 0 || aRectScaled.height > 0) {
+      if (this.rect.width > 0 || this.rect.height > 0) {
         isShown = true;
-        this._hideOutline();
+        this._hideBoxModel();
+      }
+    }
+    return isShown;
+  },
+
+  /**
+   * We only want to show guides for horizontal and vertical edges as this helps
+   * to line them up. This method finds these edges and displays a guide there.
+   *
+   * @param  {DOMPoint} p1
+   *                    Point 1
+   * @param  {DOMPoint} p2
+   *                    Point 2
+   * @param  {DOMPoint} p3 [description]
+   *                    Point 3
+   * @param  {DOMPoint} p4 [description]
+   *                    Point 4
+   */
+  _showGuides: function(p1, p2, p3, p4) {
+    let allX = [p1.x, p2.x, p3.x, p4.x].sort();
+    let allY = [p1.y, p2.y, p3.y, p4.y].sort();
+    let toShowX = [];
+    let toShowY = [];
+
+    for (let arr of [allX, allY]) {
+      for (let i = 0; i < arr.length; i++) {
+        let val = arr[i];
+
+        if (i !== arr.lastIndexOf(val)) {
+          if (arr === allX) {
+            toShowX.push(val);
+          } else {
+            toShowY.push(val);
+          }
+          arr.splice(arr.lastIndexOf(val), 1);
+        }
       }
     }
 
-    this._contentRect = aRect; // save orig (non-scaled) rect
-    this._highlightRect = aRectScaled; // and save the scaled rect.
+    // Move guide into place or hide it if no valid co-ordinate was found.
+    this._updateGuide(this._guideNodes.top, toShowY[0]);
+    this._updateGuide(this._guideNodes.right, toShowX[1]);
+    this._updateGuide(this._guideNodes.bottom, toShowY[1]);
+    this._updateGuide(this._guideNodes.left, toShowX[0]);
+  },
+
+  /**
+   * Move a guide to the appropriate position and display it. If no point is
+   * passed then the guide is hidden.
+   *
+   * @param  {SVGLine} guide
+   *         The guide to update
+   * @param  {Integer} point
+   *         x or y co-ordinate. If this is undefined we hide the guide.
+   */
+  _updateGuide: function(guide, point=-1) {
+    if (point > 0) {
+      let offset = GUIDE_STROKE_WIDTH / 2;
 
-    return isShown;
+      if (guide === this._guideNodes.top || guide === this._guideNodes.left) {
+        point -= offset;
+      } else {
+        point += offset;
+      }
+
+      if (guide === this._guideNodes.top || guide === this._guideNodes.bottom) {
+        guide.setAttribute("x1", 0);
+        guide.setAttribute("y1", point);
+        guide.setAttribute("x2", "100%");
+        guide.setAttribute("y2", point);
+      } else {
+        guide.setAttribute("x1", point);
+        guide.setAttribute("y1", 0);
+        guide.setAttribute("x2", point);
+        guide.setAttribute("y2", "100%");
+      }
+      guide.removeAttribute("hidden");
+      return true;
+    } else {
+      guide.setAttribute("hidden", "true");
+      return false;
+    }
   },
 
   /**
    * Update node information (tagName#id.class)
    */
   _updateInfobar: function() {
     if (this.currentNode) {
       // Tag name
@@ -574,63 +721,52 @@ BoxModelHighlighter.prototype = {
 
       // Pseudo-classes
       let pseudos = PSEUDO_CLASSES.filter(pseudo => {
         return DOMUtils.hasPseudoClassLock(this.currentNode, pseudo);
       }, this);
 
       let pseudoBox = this.nodeInfo.pseudoClassesBox;
       pseudoBox.textContent = pseudos.join("");
+
+      this._moveInfobar();
     }
   },
 
   /**
    * Move the Infobar to the right place in the highlighter.
    */
   _moveInfobar: function() {
-    if (this._highlightRect) {
+    if (this.rect) {
+      let bounds = this.rect.bounds;
       let winHeight = this.win.innerHeight * this.zoom;
       let winWidth = this.win.innerWidth * this.zoom;
 
-      let rect = {top: this._highlightRect.top,
-                  left: this._highlightRect.left,
-                  width: this._highlightRect.width,
-                  height: this._highlightRect.height};
-
-      rect.top = Math.max(rect.top, 0);
-      rect.left = Math.max(rect.left, 0);
-      rect.width = Math.max(rect.width, 0);
-      rect.height = Math.max(rect.height, 0);
-
-      rect.top = Math.min(rect.top, winHeight);
-      rect.left = Math.min(rect.left, winWidth);
-
       this.nodeInfo.positioner.removeAttribute("disabled");
       // Can the bar be above the node?
-      if (rect.top < this.nodeInfo.barHeight) {
+      if (bounds.top < this.nodeInfo.barHeight) {
         // No. Can we move the toolbar under the node?
-        if (rect.top + rect.height +
-            this.nodeInfo.barHeight > winHeight) {
+        if (bounds.bottom + this.nodeInfo.barHeight > winHeight) {
           // No. Let's move it inside.
-          this.nodeInfo.positioner.style.top = rect.top + "px";
+          this.nodeInfo.positioner.style.top = bounds.top + "px";
           this.nodeInfo.positioner.setAttribute("position", "overlap");
         } else {
           // Yes. Let's move it under the node.
-          this.nodeInfo.positioner.style.top = rect.top + rect.height + "px";
+          this.nodeInfo.positioner.style.top = bounds.bottom - INFO_BAR_OFFSET + "px";
           this.nodeInfo.positioner.setAttribute("position", "bottom");
         }
       } else {
         // Yes. Let's move it on top of the node.
         this.nodeInfo.positioner.style.top =
-          rect.top - this.nodeInfo.barHeight + "px";
+          bounds.top + INFO_BAR_OFFSET - this.nodeInfo.barHeight + "px";
         this.nodeInfo.positioner.setAttribute("position", "top");
       }
 
       let barWidth = this.nodeInfo.positioner.getBoundingClientRect().width;
-      let left = rect.left + rect.width / 2 - barWidth / 2;
+      let left = bounds.right - bounds.width / 2 - barWidth / 2;
 
       // Make sure the whole infobar is visible
       if (left < 0) {
         left = 0;
         this.nodeInfo.positioner.setAttribute("hide-arrow", "true");
       } else {
         if (left + barWidth > winWidth) {
           left = winWidth - barWidth;
@@ -643,74 +779,51 @@ BoxModelHighlighter.prototype = {
     } else {
       this.nodeInfo.positioner.style.left = "0";
       this.nodeInfo.positioner.style.top = "0";
       this.nodeInfo.positioner.setAttribute("position", "top");
       this.nodeInfo.positioner.setAttribute("hide-arrow", "true");
     }
   },
 
-  /**
-   * Store page zoom factor.
-   */
-  _computeZoomFactor: function() {
-    this.zoom =
-      this.win.QueryInterface(Ci.nsIInterfaceRequestor)
-      .getInterface(Ci.nsIDOMWindowUtils)
-      .fullZoom;
-  },
+  _attachPageListeners: function() {
+    if (this.currentNode) {
+      let win = this.currentNode.ownerGlobal;
 
-  _attachPageListeners: function() {
-    this.browser.addEventListener("resize", this, true);
-    this.browser.addEventListener("scroll", this, true);
-    this.browser.addEventListener("MozAfterPaint", this, true);
+      win.addEventListener("scroll", this, false);
+      win.addEventListener("resize", this, false);
+      win.addEventListener("MozAfterPaint", this, false);
+    }
   },
 
   _detachPageListeners: function() {
-    this.browser.removeEventListener("resize", this, true);
-    this.browser.removeEventListener("scroll", this, true);
-    this.browser.removeEventListener("MozAfterPaint", this, true);
+    if (this.currentNode) {
+      let win = this.currentNode.ownerGlobal;
+
+      win.removeEventListener("scroll", this, false);
+      win.removeEventListener("resize", this, false);
+      win.removeEventListener("MozAfterPaint", this, false);
+    }
   },
 
   /**
    * Generic event handler.
    *
    * @param nsIDOMEvent aEvent
    *        The DOM event object.
    */
   handleEvent: function(event) {
     switch (event.type) {
       case "resize":
-        this._computeZoomFactor();
-        break;
       case "MozAfterPaint":
       case "scroll":
-        this._update(true);
+        this._update();
         break;
     }
   },
-
-  /**
-   * Disable the CSS transitions for a short time to avoid laggy animations
-   * during scrolling or resizing.
-   */
-  _brieflyDisableTransitions: function() {
-    if (this.transitionDisabler) {
-      this.chromeWin.clearTimeout(this.transitionDisabler);
-    } else {
-      this.outline.setAttribute("disable-transitions", "true");
-      this.nodeInfo.positioner.setAttribute("disable-transitions", "true");
-    }
-    this.transitionDisabler =
-      this.chromeWin.setTimeout(() => {
-        this.outline.removeAttribute("disable-transitions");
-        this.nodeInfo.positioner.removeAttribute("disable-transitions");
-        this.transitionDisabler = null;
-      }, 500);
-  }
 };
 
 /**
  * The SimpleOutlineHighlighter is a class that has the same API than the
  * BoxModelHighlighter, but adds a pseudo-class on the target element itself
  * to draw a simple outline.
  * It is used by the HighlighterActor too, but in case the more complex
  * BoxModelHighlighter can't be attached (which is the case for FirefoxOS and
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -843,16 +843,22 @@ var WalkerActor = protocol.ActorClass({
     },
     "picker-node-picked" : {
       type: "pickerNodePicked",
       node: Arg(0, "disconnectedNode")
     },
     "picker-node-hovered" : {
       type: "pickerNodeHovered",
       node: Arg(0, "disconnectedNode")
+    },
+    "highlighter-ready" : {
+      type: "highlighter-ready"
+    },
+    "highlighter-hide" : {
+      type: "highlighter-hide"
     }
   },
 
   /**
    * Create the WalkerActor
    * @param DebuggerServerConnection conn
    *    The server connection.
    */
@@ -1188,17 +1194,17 @@ var WalkerActor = protocol.ActorClass({
     if (maxNodes == -1) {
       maxNodes = Number.MAX_VALUE;
     }
 
     // We're going to create a few document walkers with the same filter,
     // make it easier.
     let filteredWalker = (node) => {
       return documentWalker(node, this.rootWin, options.whatToShow);
-    }
+    };
 
     // Need to know the first and last child.
     let rawNode = node.rawNode;
     let firstChild = filteredWalker(rawNode).firstChild();
     let lastChild = filteredWalker(rawNode).lastChild();
 
     if (!firstChild) {
       // No children, we're done.
@@ -1379,17 +1385,25 @@ var WalkerActor = protocol.ActorClass({
   /**
    * Return a NodeListActor with all nodes that match the given selector.
    * See https://developer.mozilla.org/en-US/docs/Web/API/Element.querySelectorAll
    *
    * @param NodeActor baseNode
    * @param string selector
    */
   querySelectorAll: method(function(baseNode, selector) {
-    return new NodeListActor(this, baseNode.rawNode.querySelectorAll(selector));
+    let nodeList = null;
+
+    try {
+      nodeList = baseNode.rawNode.querySelectorAll(selector);
+    } catch(e) {
+      // Bad selector. Do nothing as the selector can come from a searchbox.
+    }
+
+    return new NodeListActor(this, nodeList);
   }, {
     request: {
       node: Arg(0, "domnode"),
       selector: Arg(1)
     },
     response: {
       list: RetVal("domnodelist")
     }
@@ -2532,27 +2546,27 @@ var InspectorActor = protocol.ActorClass
     return this._pageStylePromise;
   }, {
     request: {},
     response: {
       pageStyle: RetVal("pagestyle")
     }
   }),
 
-  getHighlighter: method(function () {
+  getHighlighter: method(function (autohide) {
     if (this._highlighterPromise) {
       return this._highlighterPromise;
     }
 
     this._highlighterPromise = this.getWalker().then(walker => {
-      return HighlighterActor(this);
+      return HighlighterActor(this, autohide);
     });
     return this._highlighterPromise;
   }, {
-    request: {},
+    request: { autohide: Arg(0, "boolean") },
     response: {
       highligter: RetVal("highlighter")
     }
   }),
 
   /**
    * Get the node's image data if any (for canvas and img nodes).
    * Returns an imageData object with the actual data being a LongStringActor