Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 05 Jun 2014 15:12:17 +0200
changeset 206064 87dbdac934351cd3d7b5d0bd597abc0c09b9ad19
parent 206063 a26ff9fadb0d246fd2110240d930f8c5e6629bbe (current diff)
parent 206024 219b3ed1b9968ea9d6eea0bfc21dc73107821b1a (diff)
child 206065 189492a9a1151073e187dd62440011c8fee2b5fe
push id3741
push userasasaki@mozilla.com
push dateMon, 21 Jul 2014 20:25:18 +0000
treeherdermozilla-beta@4d6f46f5af68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
dom/mobilemessage/tests/header_helpers.js
dom/mobilemessage/tests/mochitest.ini
dom/mobilemessage/tests/test_mms_pdu_helper.js
dom/mobilemessage/tests/test_mms_service.js
dom/mobilemessage/tests/test_sms_basics.html
dom/mobilemessage/tests/test_smsfilter.html
dom/mobilemessage/tests/test_smsservice_createsmsmessage.js
dom/mobilemessage/tests/test_wsp_pdu_helper.js
dom/mobilemessage/tests/xpcshell.ini
--- 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="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="d2cfef555dabab415085e548ed44c48a99be5c32"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8e4420c0c5c8e8c8e58a000278a7129403769f96"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9100fa82fc355f5201e23e400fc6b40e875304ed"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="994fa9a1f7ce0e63c880a48d571c3ab3e01884a3"/>
   <!-- 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="6e2a3b589d1e8cc1d9df25f5e630ce30a0aa39f3">
     <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="d2cfef555dabab415085e548ed44c48a99be5c32"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="994fa9a1f7ce0e63c880a48d571c3ab3e01884a3"/>
   <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="276ce45e78b09c4a4ee643646f691d22804754c1">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="d2cfef555dabab415085e548ed44c48a99be5c32"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <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="994fa9a1f7ce0e63c880a48d571c3ab3e01884a3"/>
--- 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="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="d2cfef555dabab415085e548ed44c48a99be5c32"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8e4420c0c5c8e8c8e58a000278a7129403769f96"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9100fa82fc355f5201e23e400fc6b40e875304ed"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="994fa9a1f7ce0e63c880a48d571c3ab3e01884a3"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="6e2a3b589d1e8cc1d9df25f5e630ce30a0aa39f3">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="d2cfef555dabab415085e548ed44c48a99be5c32"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="994fa9a1f7ce0e63c880a48d571c3ab3e01884a3"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "7f0af1e164a39efb732c0c341c2a8e51f681d913", 
+    "revision": "c3d40600c0090c5ca6ca4427f3a870ff443a109d", 
     "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="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="d2cfef555dabab415085e548ed44c48a99be5c32"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="994fa9a1f7ce0e63c880a48d571c3ab3e01884a3"/>
   <!-- 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="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="d2cfef555dabab415085e548ed44c48a99be5c32"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="6e2a3b589d1e8cc1d9df25f5e630ce30a0aa39f3">
     <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="d2cfef555dabab415085e548ed44c48a99be5c32"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="994fa9a1f7ce0e63c880a48d571c3ab3e01884a3"/>
   <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="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="d2cfef555dabab415085e548ed44c48a99be5c32"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="994fa9a1f7ce0e63c880a48d571c3ab3e01884a3"/>
   <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/highlighter.css
+++ b/browser/base/content/highlighter.css
@@ -40,16 +40,17 @@ svg|line.box-model-guide-bottom[hidden] 
   white-space: nowrap;
   text-overflow: ellipsis;
   direction: ltr;
 }
 
 html|*.highlighter-nodeinfobar-id,
 html|*.highlighter-nodeinfobar-classes,
 html|*.highlighter-nodeinfobar-pseudo-classes,
+html|*.highlighter-nodeinfobar-dimensions,
 html|*.highlighter-nodeinfobar-tagname {
   -moz-user-select: text;
   -moz-user-focus: normal;
   cursor: text;
 }
 
 .highlighter-nodeinfobar-arrow {
   display: none;
--- a/browser/base/content/newtab/page.js
+++ b/browser/base/content/newtab/page.js
@@ -205,17 +205,29 @@ let gPage = {
     let directoryCount = {};
     for (let type of DirectoryLinksProvider.linkTypes) {
       directoryCount[type] = 0;
     }
 
     for (let site of gGrid.sites) {
       if (site) {
         site.captureIfMissing();
-        let {type} = site.link;
+
+        // Record which tile index a directory link was shown
+        let {directoryIndex, type} = site.link;
+        if (directoryIndex !== undefined) {
+          let tileIndex = site.cell.index;
+          // For telemetry, only handle the first 9 links in the first 9 cells
+          if (directoryIndex < 9) {
+            let shownId = "NEWTAB_PAGE_DIRECTORY_LINK" + directoryIndex + "_SHOWN";
+            Services.telemetry.getHistogramById(shownId).add(Math.min(9, tileIndex));
+          }
+        }
+
+        // Aggregate tile impression counts into directory types
         if (type in directoryCount) {
           directoryCount[type]++;
         }
       }
     }
 
     DirectoryLinksProvider.reportShownCount(directoryCount);
     // Record how many directory sites were shown, but place counts over the
--- a/browser/devtools/commandline/test/browser_cmd_appcache_invalid.js
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_invalid.js
@@ -7,30 +7,95 @@
 const TEST_URI = "http://sub1.test1.example.com/browser/browser/devtools/commandline/" +
                  "test/browser_cmd_appcache_invalid_index.html";
 
 function test() {
   return Task.spawn(spawnTest).then(finish, helpers.handleError);
 }
 
 function spawnTest() {
+  let lines = [
+    'Manifest has a character encoding of ISO-8859-1. Manifests must have the ' +
+      'utf-8 character encoding.',
+    'The first line of the manifest must be "CACHE MANIFEST" at line 1.',
+    '"CACHE MANIFEST" is only valid on the first line but was found at line 3.',
+    'images/sound-icon.png points to a resource that is not available at line 9.',
+    'images/background.png points to a resource that is not available at line 10.',
+    '/checking.cgi points to a resource that is not available at line 13.',
+    'Asterisk (*) incorrectly used in the NETWORK section at line 14. If a line ' +
+      'in the NETWORK section contains only a single asterisk character, then any ' +
+      'URI not listed in the manifest will be treated as if the URI was listed in ' +
+      'the NETWORK section. Otherwise such URIs will be treated as unavailable. ' +
+    'Other uses of the * character are prohibited',
+    '../rel.html points to a resource that is not available at line 17.',
+    '../../rel.html points to a resource that is not available at line 18.',
+    '../../../rel.html points to a resource that is not available at line 19.',
+    '../../../../rel.html points to a resource that is not available at line 20.',
+    '../../../../../rel.html points to a resource that is not available at line 21.',
+    '/../ is not a valid URI prefix at line 22.',
+    '/test.css points to a resource that is not available at line 23.',
+    '/test.js points to a resource that is not available at line 24.',
+    'test.png points to a resource that is not available at line 25.',
+    '/main/features.js points to a resource that is not available at line 27.',
+    '/main/settings/index.css points to a resource that is not available at line 28.',
+    'http://example.com/scene.jpg points to a resource that is not available at line 29.',
+    '/section1/blockedbyfallback.html points to a resource that is not available at line 30.',
+    'http://example.com/images/world.jpg points to a resource that is not available at line 31.',
+    '/section2/blockedbyfallback.html points to a resource that is not available at line 32.',
+    '/main/home points to a resource that is not available at line 34.',
+    'main/app.js points to a resource that is not available at line 35.',
+    '/settings/home points to a resource that is not available at line 37.',
+    '/settings/app.js points to a resource that is not available at line 38.',
+    'The file http://sub1.test1.example.com/browser/browser/devtools/' +
+      'commandline/test/browser_cmd_appcache_invalid_page3.html was modified ' +
+      'after http://sub1.test1.example.com/browser/browser/devtools/' +
+      'commandline/test/browser_cmd_appcache_invalid_appcache.appcache. Unless ' +
+      'the text in the manifest file is changed the cached version will be used ' +
+      'instead at line 39.',
+    'browser_cmd_appcache_invalid_page3.html has cache-control set to no-store. ' +
+      'This will prevent the application cache from storing the file at line 39.',
+    'http://example.com/logo.png points to a resource that is not available at line 40.',
+    'http://example.com/check.png points to a resource that is not available at line 41.',
+    'Spaces in URIs need to be replaced with % at line 42.',
+    'http://example.com/cr oss.png points to a resource that is not available at line 42.',
+    'Asterisk (*) incorrectly used in the CACHE section at line 43. If a line ' +
+      'in the NETWORK section contains only a single asterisk character, then ' +
+      'any URI not listed in the manifest will be treated as if the URI was ' +
+      'listed in the NETWORK section. Otherwise such URIs will be treated as ' +
+      'unavailable. Other uses of the * character are prohibited',
+    'The SETTINGS section may only contain a single value, "prefer-online" or "fast" at line 47.',
+    'FALLBACK section line 50 (/section1/ /offline1.html) prevents caching of ' +
+      'line 30 (/section1/blockedbyfallback.html) in the CACHE section.',
+    '/offline1.html points to a resource that is not available at line 50.',
+    'FALLBACK section line 51 (/section2/ offline2.html) prevents caching of ' +
+      'line 32 (/section2/blockedbyfallback.html) in the CACHE section.',
+    'offline2.html points to a resource that is not available at line 51.',
+    'Only two URIs separated by spaces are allowed in the FALLBACK section at line 52.',
+    'Asterisk (*) incorrectly used in the FALLBACK section at line 53. URIs ' +
+      'in the FALLBACK section simply need to match a prefix of the request URI.',
+    'offline3.html points to a resource that is not available at line 53.',
+    'Invalid section name (BLAH) at line 55.',
+    'Only two URIs separated by spaces are allowed in the FALLBACK section at line 55.'
+  ];
+
   let options = yield helpers.openTab(TEST_URI);
   info("window open");
 
   // Wait for site to be cached.
   yield helpers.listenOnce(gBrowser.contentWindow.applicationCache, 'error');
   info("applicationCache error happened");
 
   yield helpers.openToolbar(options);
   info("toolbar open");
 
   // Pages containing an appcache the notification bar gives options to allow
   // or deny permission for the app to save data offline. Let's click Allow.
   let notificationID = "offline-app-requested-sub1.test1.example.com";
-  let notification = PopupNotifications.getNotification(notificationID, gBrowser.selectedBrowser);
+  let notification =
+    PopupNotifications.getNotification(notificationID, gBrowser.selectedBrowser);
 
   if (notification) {
     info("Authorizing offline storage.");
     notification.mainAction.callback();
   } else {
     info("No notification box is available.");
   }
 
@@ -40,61 +105,16 @@ function spawnTest() {
       setup: 'appcache validate',
       check: {
         input:  'appcache validate',
         markup: 'VVVVVVVVVVVVVVVVV',
         status: 'VALID',
         args: {}
       },
       exec: {
-        output: [
-          /Manifest has a character encoding of ISO-8859-1\. Manifests must have the utf-8 character encoding\./,
-          /The first line of the manifest must be "CACHE MANIFEST" at line 1\./,
-          /"CACHE MANIFEST" is only valid on the first line but was found at line 3\./,
-          /images\/sound-icon\.png points to a resource that is not available at line 9\./,
-          /images\/background\.png points to a resource that is not available at line 10\./,
-          /NETWORK section line 13 \(\/checking\.cgi\) prevents caching of line 13 \(\/checking\.cgi\) in the NETWORK section\./,
-          /\/checking\.cgi points to a resource that is not available at line 13\./,
-          /Asterisk \(\*\) incorrectly used in the NETWORK section at line 14\. If a line in the NETWORK section contains only a single asterisk character, then any URI not listed in the manifest will be treated as if the URI was listed in the NETWORK section\. Otherwise such URIs will be treated as unavailable\. Other uses of the \* character are prohibited/,
-          /\.\.\/rel\.html points to a resource that is not available at line 17\./,
-          /\.\.\/\.\.\/rel\.html points to a resource that is not available at line 18\./,
-          /\.\.\/\.\.\/\.\.\/rel\.html points to a resource that is not available at line 19\./,
-          /\.\.\/\.\.\/\.\.\/\.\.\/rel\.html points to a resource that is not available at line 20\./,
-          /\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/rel\.html points to a resource that is not available at line 21\./,
-          /\/\.\.\/ is not a valid URI prefix at line 22\./,
-          /\/test\.css points to a resource that is not available at line 23\./,
-          /\/test\.js points to a resource that is not available at line 24\./,
-          /test\.png points to a resource that is not available at line 25\./,
-          /\/main\/features\.js points to a resource that is not available at line 27\./,
-          /\/main\/settings\/index\.css points to a resource that is not available at line 28\./,
-          /http:\/\/example\.com\/scene\.jpg points to a resource that is not available at line 29\./,
-          /\/section1\/blockedbyfallback\.html points to a resource that is not available at line 30\./,
-          /http:\/\/example\.com\/images\/world\.jpg points to a resource that is not available at line 31\./,
-          /\/section2\/blockedbyfallback\.html points to a resource that is not available at line 32\./,
-          /\/main\/home points to a resource that is not available at line 34\./,
-          /main\/app\.js points to a resource that is not available at line 35\./,
-          /\/settings\/home points to a resource that is not available at line 37\./,
-          /\/settings\/app\.js points to a resource that is not available at line 38\./,
-          /The file http:\/\/sub1\.test1\.example\.com\/browser\/browser\/devtools\/commandline\/test\/browser_cmd_appcache_invalid_page3\.html was modified after http:\/\/sub1\.test1\.example\.com\/browser\/browser\/devtools\/commandline\/test\/browser_cmd_appcache_invalid_appcache\.appcache\. Unless the text in the manifest file is changed the cached version will be used instead at line 39\./,
-          /browser_cmd_appcache_invalid_page3\.html has cache-control set to no-store\. This will prevent the application cache from storing the file at line 39\./,
-          /http:\/\/example\.com\/logo\.png points to a resource that is not available at line 40\./,
-          /http:\/\/example\.com\/check\.png points to a resource that is not available at line 41\./,
-          /Spaces in URIs need to be replaced with % at line 42\./,
-          /http:\/\/example\.com\/cr oss\.png points to a resource that is not available at line 42\./,
-          /Asterisk \(\*\) incorrectly used in the CACHE section at line 43\. If a line in the NETWORK section contains only a single asterisk character, then any URI not listed in the manifest will be treated as if the URI was listed in the NETWORK section\. Otherwise such URIs will be treated as unavailable\. Other uses of the \* character are prohibited/,
-          /The SETTINGS section may only contain a single value, "prefer-online" or "fast" at line 47\./,
-          /FALLBACK section line 50 \(\/section1\/ \/offline1\.html\) prevents caching of line 30 \(\/section1\/blockedbyfallback\.html\) in the CACHE section\./,
-          /\/offline1\.html points to a resource that is not available at line 50\./,
-          /FALLBACK section line 51 \(\/section2\/ offline2\.html\) prevents caching of line 32 \(\/section2\/blockedbyfallback\.html\) in the CACHE section\./,
-          /offline2\.html points to a resource that is not available at line 51\./,
-          /Only two URIs separated by spaces are allowed in the FALLBACK section at line 52\./,
-          /Asterisk \(\*\) incorrectly used in the FALLBACK section at line 53\. URIs in the FALLBACK section simply need to match a prefix of the request URI\./,
-          /offline3\.html points to a resource that is not available at line 53\./,
-          /Invalid section name \(BLAH\) at line 55\./,
-          /Only two URIs separated by spaces are allowed in the FALLBACK section at line 55\./
-        ]
+        output: lines.map(getRegexForString)
       },
     },
   ]);
 
   yield helpers.closeToolbar(options);
   yield helpers.closeTab(options);
 }
--- a/browser/devtools/commandline/test/head.js
+++ b/browser/devtools/commandline/test/head.js
@@ -23,16 +23,30 @@ function whenDelayedStartupFinished(aWin
     if (aWindow == aSubject) {
       Services.obs.removeObserver(observer, aTopic);
       executeSoon(aCallback);
     }
   }, "browser-delayed-startup-finished", false);
 }
 
 /**
+ * Creates a regular expression that matches a string. This greatly simplifies
+ * matching and debugging long strings.
+ *
+ * @param {String} text
+ *        Text to convert
+ * @return {RegExp}
+ *         Regular expression matching text
+ */
+function getRegexForString(str) {
+  str = str.replace(/(\.|\\|\/|\(|\)|\[|\]|\*|\+|\?|\$|\^|\|)/g, "\\$1");
+  return new RegExp(str);
+}
+
+/**
  * Force GC on shutdown, because it seems that GCLI can outrun the garbage
  * collector in some situations, which causes test failures in later tests
  * Bug 774619 is an example.
  */
 registerCleanupFunction(function tearDown() {
   window.QueryInterface(Ci.nsIInterfaceRequestor)
       .getInterface(Ci.nsIDOMWindowUtils)
       .garbageCollect();
--- a/browser/devtools/inspector/test/browser_inspector_highlighter.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter.js
@@ -1,115 +1,80 @@
 /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 let doc;
-let h1;
+let div;
+let rotated;
 let inspector;
+let contentViewer;
 
 function createDocument() {
-  let div = doc.createElement("div");
-  h1 = doc.createElement("h1");
-  let p1 = doc.createElement("p");
-  let p2 = doc.createElement("p");
-  let div2 = doc.createElement("div");
-  let p3 = doc.createElement("p");
-  doc.title = "Inspector Highlighter Meatballs";
-  h1.textContent = "Inspector Tree Selection Test";
-  p1.textContent = "This is some example text";
-  p2.textContent = "Lorem ipsum dolor sit amet, consectetur adipisicing " +
-    "elit, sed do eiusmod tempor incididunt ut labore et dolore magna " +
-    "aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
-    "laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
-    "dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
-    "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " +
-    "proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
-  p3.textContent = "Lorem ipsum dolor sit amet, consectetur adipisicing " +
-    "elit, sed do eiusmod tempor incididunt ut labore et dolore magna " +
-    "aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
-    "laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
-    "dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
-    "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " +
-    "proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
-  let div3 = doc.createElement("div");
-  div3.id = "checkOutThisWickedSpread";
-  div3.setAttribute("style", "position: absolute; top: 20px; right: 20px; height: 20px; width: 20px; background-color: yellow; border: 1px dashed black;");
-  let p4 = doc.createElement("p");
-  p4.setAttribute("style", "font-weight: 200; font-size: 8px; text-align: center;");
-  p4.textContent = "Smörgåsbord!";
-  div.appendChild(h1);
-  div.appendChild(p1);
-  div.appendChild(p2);
-  div2.appendChild(p3);
-  div3.appendChild(p4);
+  div = doc.createElement("div");
+  div.setAttribute("style",
+                   "padding:5px; border:7px solid red; margin: 9px; " +
+                   "position:absolute; top:30px; left:150px;");
+  let textNode = doc.createTextNode("Gort! Klaatu barada nikto!");
+  rotated = doc.createElement("div");
+  rotated.setAttribute("style",
+                       "padding:5px; border:7px solid red; margin: 9px; " +
+                       "transform:rotate(45deg); " +
+                       "position:absolute; top:30px; left:80px;");
+  div.appendChild(textNode);
   doc.body.appendChild(div);
-  doc.body.appendChild(div2);
-  doc.body.appendChild(div3);
+  doc.body.appendChild(rotated);
 
   openInspector(aInspector => {
     inspector = aInspector;
     inspector.selection.setNode(div, null);
-    inspector.once("inspector-updated", () => {
-      inspector.toolbox.highlighterUtils.startPicker().then(testMouseOverH1Highlights);
-    });
+    inspector.once("inspector-updated", testMouseOverDivHighlights);
   });
 }
 
-function testMouseOverH1Highlights() {
-  inspector.toolbox.once("highlighter-ready", () => {
-    ok(isHighlighting(), "Highlighter is shown");
-    is(getHighlitNode(), h1, "Highlighter's outline correspond to the selected node");
-    testBoxModelDimensions();
-  });
+function testMouseOverDivHighlights() {
+  ok(isHighlighting(), "Highlighter is shown");
+  is(getHighlitNode(), div, "Highlighter's outline correspond to the non-rotated div");
+  testNonTransformedBoxModelDimensionsNoZoom();
+}
 
-  EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
+function testNonTransformedBoxModelDimensionsNoZoom() {
+  info("Highlighted the non-rotated div");
+  isNodeCorrectlyHighlighted(div, "non-zoomed");
+
+  inspector.toolbox.once("highlighter-ready", testNonTransformedBoxModelDimensionsZoomed);
+  contentViewer = gBrowser.selectedBrowser.docShell.contentViewer
+                          .QueryInterface(Ci.nsIMarkupDocumentViewer);
+  contentViewer.fullZoom = 2;
 }
 
-function testBoxModelDimensions() {
-  let h1Dims = h1.getBoundingClientRect();
-  let h1Width = Math.ceil(h1Dims.width);
-  let h1Height = Math.ceil(h1Dims.height);
+function testNonTransformedBoxModelDimensionsZoomed() {
+  info("Highlighted the zoomed, non-rotated div");
+  isNodeCorrectlyHighlighted(div, "zoomed");
 
-  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)");
+  inspector.toolbox.once("highlighter-ready", testMouseOverRotatedHighlights);
+  contentViewer.fullZoom = 1;
+}
 
-  // zoom the page by a factor of 2
-  let contentViewer = gBrowser.selectedBrowser.docShell.contentViewer
-                             .QueryInterface(Ci.nsIMarkupDocumentViewer);
-  contentViewer.fullZoom = 2;
+function testMouseOverRotatedHighlights() {
+  inspector.toolbox.once("highlighter-ready", () => {
+    ok(isHighlighting(), "Highlighter is shown");
+    info("Highlighted the rotated div");
+    isNodeCorrectlyHighlighted(rotated, "rotated");
 
-  // 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 = getSimpleBorderRect();
-  let outlineWidth = Math.floor(outlineDims.width);
-  let outlineHeight = Math.floor(outlineDims.height);
-
-  is(outlineWidth, h1Width, "outline width matches dimensions of element (zoomed)");
-
-  is(outlineHeight, h1Height, "outline height matches dimensions of element (zoomed)");
-
-  executeSoon(finishUp);
+    executeSoon(finishUp);
+  });
+  inspector.selection.setNode(rotated);
 }
 
 function finishUp() {
   inspector.toolbox.highlighterUtils.stopPicker().then(() => {
-    doc = h1 = inspector = null;
+    doc = div = rotated = inspector = contentViewer = null;
     let target = TargetFactory.forTab(gBrowser.selectedTab);
     gDevTools.closeToolbox(target);
     gBrowser.removeCurrentTab();
     finish();
   });
 }
 
 function test() {
--- a/browser/devtools/inspector/test/browser_inspector_infobar.js
+++ b/browser/devtools/inspector/test/browser_inspector_infobar.js
@@ -12,29 +12,68 @@ function test() {
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     doc = content.document;
     waitForFocus(setupInfobarTest, content);
   }, true);
 
-  let style = "body{width:100%;height: 100%} div {position: absolute;height: 100px;width: 500px}#bottom {bottom: 0px}#vertical {height: 100%}#farbottom{bottom: -200px}";
-  let html = "<style>" + style + "</style><div id=vertical></div><div id=top class='class1 class2'></div><div id=bottom></div><div id=farbottom></div>"
+  let style = "body{width:100%;height: 100%} div {position: absolute;" +
+              "height: 100px;width: 500px}#bottom {bottom: 0px}#vertical {"+
+              "height: 100%}#farbottom{bottom: -200px}";
+  let html = "<style>" + style + "</style><div id=vertical></div>" +
+             "<div id=top class='class1 class2'></div><div id=bottom></div>" +
+             "<div id=farbottom></div>"
 
-  content.location = "data:text/html," + encodeURIComponent(html);
+  content.location = "data:text/html;charset=utf-8," + encodeURIComponent(html);
 
   function setupInfobarTest() {
     nodes = [
-      {node: doc.querySelector("#top"), position: "bottom", tag: "DIV", id: "#top", classes: ".class1.class2"},
-      {node: doc.querySelector("#vertical"), position: "overlap", tag: "DIV", id: "#vertical", classes: ""},
-      {node: doc.querySelector("#bottom"), position: "top", tag: "DIV", id: "#bottom", classes: ""},
-      {node: doc.querySelector("body"), position: "overlap", tag: "BODY", id: "", classes: ""},
-      {node: doc.querySelector("#farbottom"), position: "top", tag: "DIV", id: "#farbottom", classes: ""},
-    ]
+      {
+        node: doc.querySelector("#top"),
+        position: "bottom",
+        tag: "DIV",
+        id: "#top",
+        classes: ".class1.class2",
+        dims: "500 x 100"
+      },
+      {
+        node: doc.querySelector("#vertical"),
+        position: "overlap",
+        tag: "DIV",
+        id: "#vertical",
+        classes: ""
+        // No dims as they will vary between computers
+      },
+      {
+        node: doc.querySelector("#bottom"),
+        position: "top",
+        tag: "DIV",
+        id: "#bottom",
+        classes: "",
+        dims: "500 x 100"
+      },
+      {
+        node: doc.querySelector("body"),
+        position: "overlap",
+        tag: "BODY",
+        id: "",
+        classes: ""
+        // No dims as they will vary between computers
+      },
+      {
+        node: doc.querySelector("#farbottom"),
+        position: "top",
+        tag: "DIV",
+        id: "#farbottom",
+        classes: "",
+        dims: "500 x 100"
+      },
+    ];
 
     for (let i = 0; i < nodes.length; i++) {
       ok(nodes[i].node, "node " + i + " found");
     }
 
     openInspector(runTests);
   }
 
@@ -69,26 +108,34 @@ function test() {
     });
   }
 
   function performTest() {
     let browser = gBrowser.selectedBrowser;
     let stack = browser.parentNode;
 
     let container = stack.querySelector(".highlighter-nodeinfobar-positioner");
-    is(container.getAttribute("position"), nodes[cursor].position, "node " + cursor + ": position matches.");
+    is(container.getAttribute("position"),
+      nodes[cursor].position, "node " + cursor + ": position matches.");
 
     let tagNameLabel = stack.querySelector(".highlighter-nodeinfobar-tagname");
-    is(tagNameLabel.textContent, nodes[cursor].tag, "node " + cursor  + ": tagName matches.");
+    is(tagNameLabel.textContent, nodes[cursor].tag,
+      "node " + cursor  + ": tagName matches.");
 
     let idLabel = stack.querySelector(".highlighter-nodeinfobar-id");
     is(idLabel.textContent, nodes[cursor].id, "node " + cursor  + ": id matches.");
 
     let classesBox = stack.querySelector(".highlighter-nodeinfobar-classes");
-    is(classesBox.textContent, nodes[cursor].classes, "node " + cursor  + ": classes match.");
+    is(classesBox.textContent, nodes[cursor].classes,
+      "node " + cursor  + ": classes match.");
+
+    if (nodes[cursor].dims) {
+      let dimBox = stack.querySelector(".highlighter-nodeinfobar-dimensions");
+      is(dimBox.textContent, nodes[cursor].dims, "node " + cursor  + ": dims match.");
+    }
   }
 
   function finishUp() {
     doc = nodes = null;
     gBrowser.removeCurrentTab();
     finish();
   }
 }
--- a/browser/devtools/inspector/test/head.js
+++ b/browser/devtools/inspector/test/head.js
@@ -395,17 +395,17 @@ function focusSearchBoxUsingShortcut(pan
   isnot(name, null, "Successfully retrieved keycode/key");
 
   let modifiers = {
     shiftKey: modifiersAttr.match("shift"),
     ctrlKey: modifiersAttr.match("ctrl"),
     altKey: modifiersAttr.match("alt"),
     metaKey: modifiersAttr.match("meta"),
     accelKey: modifiersAttr.match("accel")
-  }
+  };
 
   let searchBox = panelWin.document.getElementById("inspector-searchbox");
   searchBox.addEventListener("focus", function onFocus() {
     searchBox.removeEventListener("focus", onFocus, false);
     callback && callback();
   }, false);
   EventUtils.synthesizeKey(name, modifiers);
 }
@@ -420,16 +420,70 @@ function getComputedPropertyValue(aName)
 
     if (name.textContent === aName) {
       let value = prop.querySelector(".property-value");
       return value.textContent;
     }
   }
 }
 
+function isNodeCorrectlyHighlighted(node, prefix="") {
+  let boxModel = getBoxModelStatus();
+  let helper = new LayoutHelpers(window.content);
+
+  prefix += (prefix ? " " : "") + node.nodeName;
+  prefix += (node.id ? "#" + node.id : "");
+  prefix += (node.classList.length ? "." + [...node.classList].join(".") : "");
+  prefix += " ";
+
+  let quads = helper.getAdjustedQuads(node, "content");
+  let {p1:cp1, p2:cp2, p3:cp3, p4:cp4} = boxModel.content.points;
+  is(cp1.x, quads.p1.x, prefix + "content point 1 x co-ordinate is correct");
+  is(cp1.y, quads.p1.y, prefix + "content point 1 y co-ordinate is correct");
+  is(cp2.x, quads.p2.x, prefix + "content point 2 x co-ordinate is correct");
+  is(cp2.y, quads.p2.y, prefix + "content point 2 y co-ordinate is correct");
+  is(cp3.x, quads.p3.x, prefix + "content point 3 x co-ordinate is correct");
+  is(cp3.y, quads.p3.y, prefix + "content point 3 y co-ordinate is correct");
+  is(cp4.x, quads.p4.x, prefix + "content point 4 x co-ordinate is correct");
+  is(cp4.y, quads.p4.y, prefix + "content point 4 y co-ordinate is correct");
+
+  quads = helper.getAdjustedQuads(node, "padding");
+  let {p1:pp1, p2:pp2, p3:pp3, p4:pp4} = boxModel.padding.points;
+  is(pp1.x, quads.p1.x, prefix + "padding point 1 x co-ordinate is correct");
+  is(pp1.y, quads.p1.y, prefix + "padding point 1 y co-ordinate is correct");
+  is(pp2.x, quads.p2.x, prefix + "padding point 2 x co-ordinate is correct");
+  is(pp2.y, quads.p2.y, prefix + "padding point 2 y co-ordinate is correct");
+  is(pp3.x, quads.p3.x, prefix + "padding point 3 x co-ordinate is correct");
+  is(pp3.y, quads.p3.y, prefix + "padding point 3 y co-ordinate is correct");
+  is(pp4.x, quads.p4.x, prefix + "padding point 4 x co-ordinate is correct");
+  is(pp4.y, quads.p4.y, prefix + "padding point 4 y co-ordinate is correct");
+
+  quads = helper.getAdjustedQuads(node, "border");
+  let {p1:bp1, p2:bp2, p3:bp3, p4:bp4} = boxModel.border.points;
+  is(bp1.x, quads.p1.x, prefix + "border point 1 x co-ordinate is correct");
+  is(bp1.y, quads.p1.y, prefix + "border point 1 y co-ordinate is correct");
+  is(bp2.x, quads.p2.x, prefix + "border point 2 x co-ordinate is correct");
+  is(bp2.y, quads.p2.y, prefix + "border point 2 y co-ordinate is correct");
+  is(bp3.x, quads.p3.x, prefix + "border point 3 x co-ordinate is correct");
+  is(bp3.y, quads.p3.y, prefix + "border point 3 y co-ordinate is correct");
+  is(bp4.x, quads.p4.x, prefix + "border point 4 x co-ordinate is correct");
+  is(bp4.y, quads.p4.y, prefix + "border point 4 y co-ordinate is correct");
+
+  quads = helper.getAdjustedQuads(node, "margin");
+  let {p1:mp1, p2:mp2, p3:mp3, p4:mp4} = boxModel.margin.points;
+  is(mp1.x, quads.p1.x, prefix + "margin point 1 x co-ordinate is correct");
+  is(mp1.y, quads.p1.y, prefix + "margin point 1 y co-ordinate is correct");
+  is(mp2.x, quads.p2.x, prefix + "margin point 2 x co-ordinate is correct");
+  is(mp2.y, quads.p2.y, prefix + "margin point 2 y co-ordinate is correct");
+  is(mp3.x, quads.p3.x, prefix + "margin point 3 x co-ordinate is correct");
+  is(mp3.y, quads.p3.y, prefix + "margin point 3 y co-ordinate is correct");
+  is(mp4.x, quads.p4.x, prefix + "margin point 4 x co-ordinate is correct");
+  is(mp4.y, quads.p4.y, prefix + "margin point 4 y co-ordinate is correct");
+}
+
 function getContainerForRawNode(markupView, rawNode)
 {
   let front = markupView.walker.frontForRawNode(rawNode);
   let container = markupView.getContainer(front);
   return container;
 }
 
 SimpleTest.registerCleanupFunction(function () {
--- a/browser/devtools/shared/AppCacheUtils.jsm
+++ b/browser/devtools/shared/AppCacheUtils.jsm
@@ -113,17 +113,18 @@ AppCacheUtils.prototype = {
       }
     }
 
     // Loop through network entries making sure that fallback and cache don't
     // contain uris starting with the network uri.
     for (let neturi of parsed.uris) {
       if (neturi.section == "NETWORK") {
         for (let parsedUri of parsed.uris) {
-          if (parsedUri.uri.startsWith(neturi.uri)) {
+          if (parsedUri.section !== "NETWORK" &&
+              parsedUri.uri.startsWith(neturi.uri)) {
             this._addError(neturi.line, "networkBlocksURI", neturi.line,
                            neturi.original, parsedUri.line, parsedUri.original,
                            parsedUri.section);
           }
         }
       }
     }
 
@@ -159,17 +160,17 @@ AppCacheUtils.prototype = {
           }
 
           // If cache-control: no-store the file will not be added to the
           // appCache.
           if (uriInfo.nocache) {
             this._addError(parsedUri.line, "cacheControlNoStore",
                            parsedUri.original, parsedUri.line);
           }
-        } else {
+        } else if (parsedUri.original !== "*") {
           this._addError(parsedUri.line, "notAvailable",
                          parsedUri.original, parsedUri.line);
         }
 
         if (current == len - 1) {
           deferred.resolve();
         }
       });
@@ -177,17 +178,16 @@ AppCacheUtils.prototype = {
 
     return deferred.promise;
   },
 
   _getURIInfo: function ACU__getURIInfo(uri) {
     let inputStream = Cc["@mozilla.org/scriptableinputstream;1"]
                         .createInstance(Ci.nsIScriptableInputStream);
     let deferred = promise.defer();
-    let channelCharset = "";
     let buffer = "";
     let channel = Services.io.newChannel(uri, null, null);
 
     // Avoid the cache:
     channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
     channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
 
     channel.asyncOpen({
@@ -198,17 +198,17 @@ AppCacheUtils.prototype = {
 
       onDataAvailable: function (request, context, stream, offset, count) {
         request.QueryInterface(Ci.nsIHttpChannel);
         inputStream.init(stream);
         buffer = buffer.concat(inputStream.read(count));
       },
 
       onStopRequest: function onStartRequest(request, context, statusCode) {
-        if (statusCode == 0) {
+        if (statusCode === 0) {
           request.QueryInterface(Ci.nsIHttpChannel);
 
           let result = {
             name: request.name,
             success: request.requestSucceeded,
             status: request.responseStatus + " - " + request.responseStatusText,
             charset: request.contentCharset || "utf-8",
             mimeType: request.contentType,
@@ -274,17 +274,17 @@ AppCacheUtils.prototype = {
             entry[key] = value;
           }
           entries.push(entry);
         }
         return true;
       }
     });
 
-    if (entries.length == 0) {
+    if (entries.length === 0) {
       throw new Error(l10n.GetStringFromName("noResults"));
     }
     return entries;
   },
 
   viewEntry: function ACU_viewEntry(key) {
     let uri;
 
@@ -315,35 +315,41 @@ AppCacheUtils.prototype = {
 
   clearAll: function ACU_clearAll() {
     Services.cache.evictEntries(Ci.nsICache.STORE_OFFLINE);
   },
 
   _getManifestURI: function ACU__getManifestURI() {
     let deferred = promise.defer();
 
-    let getURI = node => {
+    let getURI = () => {
       let htmlNode = this.doc.querySelector("html[manifest]");
       if (htmlNode) {
         let pageUri = this.doc.location ? this.doc.location.href : this.uri;
         let origin = pageUri.substr(0, pageUri.lastIndexOf("/") + 1);
-        return origin + htmlNode.getAttribute("manifest");
+        let manifestURI = htmlNode.getAttribute("manifest");
+
+        if (manifestURI.startsWith("/")) {
+          manifestURI = manifestURI.substr(1);
+        }
+
+        return origin + manifestURI;
       }
     };
 
     if (this.doc) {
-      let uri = getURI(this.doc);
+      let uri = getURI();
       return promise.resolve(uri);
     } else {
       this._getURIInfo(this.uri).then(uriInfo => {
         if (uriInfo.success) {
           let html = uriInfo.text;
           let parser = _DOMParser;
           this.doc = parser.parseFromString(html, "text/html");
-          let uri = getURI(this.doc);
+          let uri = getURI();
           deferred.resolve(uri);
         } else {
           this.errors.push({
             line: 0,
             msg: l10n.GetStringFromName("invalidURI")
           });
         }
       });
@@ -389,20 +395,20 @@ ManifestParser.prototype = {
     let fallbacks = this.fallbacks = [];
     let settings = this.settings = [];
     let errors = this.errors = [];
     let uris = this.uris = [];
 
     this.currSection = "CACHE";
 
     for (let i = 0; i < lines.length; i++) {
-      let text = this.text = lines[i].replace(/^\s+|\s+$/g);
+      let text = this.text = lines[i].trim();
       this.currentLine = i + 1;
 
-      if (i == 0 && text != "CACHE MANIFEST") {
+      if (i === 0 && text !== "CACHE MANIFEST") {
         this._addError(1, "firstLineMustBeCacheManifest", 1);
       }
 
       // Ignore comments
       if (/^#/.test(text) || !text.length) {
         continue;
       }
 
@@ -448,17 +454,17 @@ ManifestParser.prototype = {
         this._addError(this.currentLine, "asteriskInWrongSection2",
                        this.currSection, this.currentLine);
         return;
       }
     }
 
     if (/\s/.test(text)) {
       this._addError(this.currentLine, "escapeSpaces", this.currentLine);
-      text = text.replace(/\s/g, "%20")
+      text = text.replace(/\s/g, "%20");
     }
 
     if (text[0] == "/") {
       if (text.substr(0, 4) == "/../") {
         this._addError(this.currentLine, "slashDotDotSlashBad", this.currentLine);
       } else {
         this.uris.push(this._wrapURI(this.origin + text.substring(1)));
       }
@@ -501,17 +507,17 @@ ManifestParser.prototype = {
     let [ namespace, fallback ] = split;
 
     if (namespace.indexOf("*") != -1) {
       this._addError(this.currentLine, "fallbackAsterisk2", this.currentLine);
     }
 
     if (/\s/.test(namespace)) {
       this._addError(this.currentLine, "escapeSpaces", this.currentLine);
-      namespace = namespace.replace(/\s/g, "%20")
+      namespace = namespace.replace(/\s/g, "%20");
     }
 
     if (namespace.substr(0, 4) == "/../") {
       this._addError(this.currentLine, "slashDotDotSlashBad", this.currentLine);
     }
 
     if (namespace.substr(0, 2) == "./") {
       namespace = this.origin + namespace.substring(2);
--- a/browser/devtools/styleeditor/StyleEditorUI.jsm
+++ b/browser/devtools/styleeditor/StyleEditorUI.jsm
@@ -59,17 +59,17 @@ function StyleEditorUI(debuggee, target,
   this._panelDoc = panelDoc;
   this._window = this._panelDoc.defaultView;
   this._root = this._panelDoc.getElementById("style-editor-chrome");
 
   this.editors = [];
   this.selectedEditor = null;
   this.savedLocations = {};
 
-  this._updateContextMenu = this._updateContextMenu.bind(this);
+  this._updateOptionsMenu = this._updateOptionsMenu.bind(this);
   this._onStyleSheetCreated = this._onStyleSheetCreated.bind(this);
   this._onNewDocument = this._onNewDocument.bind(this);
   this._onMediaPrefChanged = this._onMediaPrefChanged.bind(this);
   this._updateMediaList = this._updateMediaList.bind(this);
   this._clear = this._clear.bind(this);
   this._onError = this._onError.bind(this);
 
   this._prefObserver = new PrefObserver("devtools.styleeditor.");
@@ -137,46 +137,36 @@ StyleEditorUI.prototype = {
     wire(this._view.rootElement, ".style-editor-newButton", function onNew() {
       this._debuggee.addStyleSheet(null).then(this._onStyleSheetCreated);
     }.bind(this));
 
     wire(this._view.rootElement, ".style-editor-importButton", function onImport() {
       this._importFromFile(this._mockImportFile || null, this._window);
     }.bind(this));
 
-    this._contextMenu = this._panelDoc.getElementById("sidebar-context");
-    this._contextMenu.addEventListener("popupshowing",
-                                       this._updateContextMenu);
+    this._optionsMenu = this._panelDoc.getElementById("style-editor-options-popup");
+    this._optionsMenu.addEventListener("popupshowing",
+                                       this._updateOptionsMenu);
 
-    this._sourcesItem = this._panelDoc.getElementById("context-origsources");
+    this._sourcesItem = this._panelDoc.getElementById("options-origsources");
     this._sourcesItem.addEventListener("command",
                                        this._toggleOrigSources);
-    this._mediaItem = this._panelDoc.getElementById("context-show-media");
+    this._mediaItem = this._panelDoc.getElementById("options-show-media");
     this._mediaItem.addEventListener("command",
                                      this._toggleMediaSidebar);
   },
 
   /**
-   * Update text of context menu option to reflect current preference
-   * settings
+   * Update options menu items to reflect current preference settings.
    */
-  _updateContextMenu: function() {
-    let sourceString = "showOriginalSources";
-    if (Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
-      sourceString = "showCSSSources";
-    }
-    this._sourcesItem.setAttribute("label", _(sourceString + ".label"));
-    this._sourcesItem.setAttribute("accesskey", _(sourceString + ".accesskey"));
-
-    let mediaString = "showMediaSidebar"
-    if (Services.prefs.getBoolPref(PREF_MEDIA_SIDEBAR)) {
-      mediaString = "hideMediaSidebar";
-    }
-    this._mediaItem.setAttribute("label", _(mediaString + ".label"));
-    this._mediaItem.setAttribute("accesskey", _(mediaString + ".accesskey"));
+  _updateOptionsMenu: function() {
+    this._sourcesItem.setAttribute("checked",
+      Services.prefs.getBoolPref(PREF_ORIG_SOURCES));
+    this._mediaItem.setAttribute("checked",
+      Services.prefs.getBoolPref(PREF_MEDIA_SIDEBAR));
   },
 
   /**
    * Refresh editors to reflect the stylesheets in the document.
    *
    * @param {string} event
    *        Event name
    * @param {StyleSheet} styleSheet
@@ -766,13 +756,16 @@ StyleEditorUI.prototype = {
    */
   _jumpToMediaRule: function(rule) {
     this.selectStyleSheet(rule.parentStyleSheet, rule.line - 1, rule.column - 1);
   },
 
   destroy: function() {
     this._clearStyleSheetEditors();
 
+    this._optionsMenu.removeEventListener("popupshowing",
+                                          this._updateOptionsMenu);
+
     this._prefObserver.off(PREF_ORIG_SOURCES, this._onNewDocument);
     this._prefObserver.off(PREF_MEDIA_SIDEBAR, this._onMediaPrefChanged);
     this._prefObserver.destroy();
   }
 }
--- a/browser/devtools/styleeditor/styleeditor.css
+++ b/browser/devtools/styleeditor/styleeditor.css
@@ -10,16 +10,20 @@
 .stylesheet-error-message {
   display: none;
 }
 
 li.error > .stylesheet-info > .stylesheet-more  > .stylesheet-error-message {
   display: block;
 }
 
+.devtools-toolbar > spacer {
+  -moz-box-flex: 1;
+}
+
 .splitview-nav > li,
 .stylesheet-info,
 .stylesheet-more {
   display: -moz-box;
 }
 
 .stylesheet-details-container {
   -moz-box-flex: 1;
--- a/browser/devtools/styleeditor/styleeditor.xul
+++ b/browser/devtools/styleeditor/styleeditor.xul
@@ -56,19 +56,26 @@
       <xul:menuitem id="cMenu_findAgain"/>
       <xul:menuseparator/>
       <xul:menuitem id="se-menu-gotoLine"
           label="&gotoLineCmd.label;"
           accesskey="&gotoLineCmd.accesskey;"
           key="key_gotoLine"
           command="cmd_gotoLine"/>
     </xul:menupopup>
-    <xul:menupopup id="sidebar-context">
-      <xul:menuitem id="context-origsources"/>
-      <xul:menuitem id="context-show-media"/>
+    <xul:menupopup id="style-editor-options-popup"
+                   position="before_start">
+      <xul:menuitem id="options-origsources"
+                    type="checkbox"
+                    label="&showOriginalSources.label;"
+                    accesskey="&showOriginalSources.accesskey;"/>
+      <xul:menuitem id="options-show-media"
+                    type="checkbox"
+                    label="&showMediaSidebar.label;"
+                    accesskey="&showMediaSidebar.accesskey;"/>
     </xul:menupopup>
   </xul:popupset>
 
   <xul:commandset id="editMenuCommands"/>
 
   <xul:commandset id="sourceEditorCommands">
     <xul:command id="cmd_gotoLine" oncommand="goDoCommand('cmd_gotoLine')"/>
     <xul:command id="cmd_find" oncommand="goDoCommand('cmd_find')"/>
@@ -86,16 +93,21 @@
             <xul:toolbarbutton class="style-editor-newButton devtools-toolbarbutton"
                         accesskey="&newButton.accesskey;"
                         tooltiptext="&newButton.tooltip;"
                         label="&newButton.label;"/>
             <xul:toolbarbutton class="style-editor-importButton devtools-toolbarbutton"
                         accesskey="&importButton.accesskey;"
                         tooltiptext="&importButton.tooltip;"
                         label="&importButton.label;"/>
+            <xul:spacer/>
+            <xul:toolbarbutton id="style-editor-options"
+                        class="devtools-option-toolbarbutton"
+                        tooltiptext="&optionsButton.tooltip;"
+                        popup="style-editor-options-popup"/>
           </xul:toolbar>
         </xul:box>
         <xul:box id="splitview-resizer-target" class="theme-sidebar splitview-nav-container"
                 persist="height">
           <ol class="splitview-nav" tabindex="0"></ol>
           <div class="splitview-nav placeholder empty">
             <p><strong>&noStyleSheet.label;</strong></p>
             <p>&noStyleSheet-tip-start.label;
--- a/browser/locales/en-US/chrome/browser/devtools/styleeditor.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/styleeditor.dtd
@@ -19,16 +19,34 @@
 <!ENTITY importButton.accesskey     "I">
 
 <!ENTITY visibilityToggle.tooltip   "Toggle style sheet visibility">
 
 <!ENTITY saveButton.label           "Save">
 <!ENTITY saveButton.tooltip         "Save this style sheet to a file">
 <!ENTITY saveButton.accesskey       "S">
 
+<!ENTITY optionsButton.tooltip      "Style Editor options">
+
+<!-- LOCALIZATION NOTE  (showOriginalSources.label): This is the label on the context
+     menu item to toggle showing original sources in the editor. -->
+<!ENTITY showOriginalSources.label     "Show original sources">
+
+<!-- LOCALIZATION NOTE  (showOriginalSources.accesskey): This is the access key for
+     the menu item to toggle showing original sources in the editor. -->
+<!ENTITY showOriginalSources.accesskey  "o">
+
+<!-- LOCALIZATION NOTE  (showMediaSidebar.label): This is the label on the context
+     menu item to toggle showing @media rule shortcuts in a sidebar. -->
+<!ENTITY showMediaSidebar.label     "Show @media sidebar">
+
+<!-- LOCALIZATION NOTE  (showMediaSidebar.accesskey): This is the access key for
+     the menu item to toggle showing the @media sidebar. -->
+<!ENTITY showMediaSidebar.accesskey     "m">
+
 <!-- LOCALICATION NOTE  (mediaRules.label): This is shown above the list of @media rules
      in each stylesheet editor sidebar. -->
 <!ENTITY mediaRules.label           "@media rules">
 
 <!ENTITY editorTextbox.placeholder  "Type CSS here.">
 
 <!-- LOCALICATION NOTE  (noStyleSheet.label): This is shown when a page has no
      stylesheet. -->
--- a/browser/locales/en-US/chrome/browser/devtools/styleeditor.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/styleeditor.properties
@@ -63,48 +63,16 @@ open.commandkey=VK_F7
 # LOCALIZATION NOTE (open.accesskey): The access key used to open the style
 # editor.
 open.accesskey=l
 
 # LOCALIZATION NOTE  (saveStyleSheet.commandkey): This the key to use in
 # conjunction with accel (Command on Mac or Ctrl on other platforms) to Save
 saveStyleSheet.commandkey=S
 
-# LOCALIZATION NOTE  (showOriginalSources.label): This is the label on the context
-# menu item to toggle showing original sources in the editor.
-showOriginalSources.label=Show original sources
-
-# LOCALIZATION NOTE  (showOriginalSources.accesskey): This is the access key for
-# the menu item to toggle showing original sources in the editor.
-showOriginalSources.accesskey=O
-
-# LOCALIZATION NOTE  (showCSSSources.label): This is the label on the context
-# menu item to toggle back to showing only CSS sources in the editor.
-showCSSSources.label=Show CSS sources
-
-# LOCALIZATION NOTE  (showCSSSources.accesskey): This is the access key for the
-# menu item to toggle back to showing only CSS sources in the editor.
-showCSSSources.accesskey=C
-
-# LOCALIZATION NOTE  (showMediaSidebar.label): This is the label on the context
-# menu item to toggle showing @media rule shortcuts in a sidebar.
-showMediaSidebar.label=Show @media sidebar
-
-# LOCALIZATION NOTE  (showMediaSidebar.accesskey): This is the access key for
-# the menu item to toggle showing the @media sidebar.
-showMediaSidebar.accesskey=M
-
-# LOCALIZATION NOTE  (hideMediaSidebar.label): This is the label on the context
-# menu item to stop showing @media rule shortcuts in a sidebar.
-hideMediaSidebar.label=Hide @media sidebar
-
-# LOCALIZATION NOTE  (hideMediaSidebar.accesskey): This is the access key for
-# the menu item to stop showing the @media sidebar.
-hideMediaSidebar.accesskey=H
-
 # LOCALIZATION NOTE (ToolboxStyleEditor.label):
 # This string is displayed in the title of the tab when the style editor is
 # displayed inside the developer tools window and in the Developer Tools Menu.
 ToolboxStyleEditor.label=Style Editor
 
 # LOCALIZATION NOTE (ToolboxStyleEditor.tooltip2):
 # This string is displayed in the tooltip of the tab when the style editor is
 # displayed inside the developer tools window.
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -280,16 +280,17 @@ browser.jar:
   skin/classic/browser/devtools/responsive-horizontal-resizer@2x.png  (../shared/devtools/images/responsivemode/responsive-horizontal-resizer@2x.png)
   skin/classic/browser/devtools/responsiveui-rotate.png          (../shared/devtools/images/responsivemode/responsiveui-rotate.png)
   skin/classic/browser/devtools/responsiveui-rotate@2x.png       (../shared/devtools/images/responsivemode/responsiveui-rotate@2x.png)
   skin/classic/browser/devtools/responsiveui-touch.png           (../shared/devtools/images/responsivemode/responsiveui-touch.png)
   skin/classic/browser/devtools/responsiveui-touch@2x.png        (../shared/devtools/images/responsivemode/responsiveui-touch@2x.png)
   skin/classic/browser/devtools/responsiveui-screenshot.png      (../shared/devtools/images/responsivemode/responsiveui-screenshot.png)
   skin/classic/browser/devtools/responsiveui-screenshot@2x.png   (../shared/devtools/images/responsivemode/responsiveui-screenshot@2x.png)
   skin/classic/browser/devtools/toggle-tools.png          (../shared/devtools/images/toggle-tools.png)
+  skin/classic/browser/devtools/toggle-tools@2x.png       (../shared/devtools/images/toggle-tools@2x.png)
   skin/classic/browser/devtools/dock-bottom@2x.png        (../shared/devtools/images/dock-bottom@2x.png)
   skin/classic/browser/devtools/dock-side@2x.png          (../shared/devtools/images/dock-side@2x.png)
   skin/classic/browser/devtools/floating-scrollbars.css   (devtools/floating-scrollbars.css)
   skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
 * skin/classic/browser/devtools/inspector.css               (../shared/devtools/inspector.css)
   skin/classic/browser/devtools/profiler-stopwatch.svg      (../shared/devtools/images/profiler-stopwatch.svg)
   skin/classic/browser/devtools/profiler-stopwatch-checked.svg      (../shared/devtools/images/profiler-stopwatch-checked.svg)
   skin/classic/browser/devtools/tool-options.svg            (../shared/devtools/images/tool-options.svg)
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -400,16 +400,17 @@ browser.jar:
   skin/classic/browser/devtools/responsive-horizontal-resizer@2x.png  (../shared/devtools/images/responsivemode/responsive-horizontal-resizer@2x.png)
   skin/classic/browser/devtools/responsiveui-rotate.png          (../shared/devtools/images/responsivemode/responsiveui-rotate.png)
   skin/classic/browser/devtools/responsiveui-rotate@2x.png       (../shared/devtools/images/responsivemode/responsiveui-rotate@2x.png)
   skin/classic/browser/devtools/responsiveui-touch.png           (../shared/devtools/images/responsivemode/responsiveui-touch.png)
   skin/classic/browser/devtools/responsiveui-touch@2x.png        (../shared/devtools/images/responsivemode/responsiveui-touch@2x.png)
   skin/classic/browser/devtools/responsiveui-screenshot.png      (../shared/devtools/images/responsivemode/responsiveui-screenshot.png)
   skin/classic/browser/devtools/responsiveui-screenshot@2x.png   (../shared/devtools/images/responsivemode/responsiveui-screenshot@2x.png)
   skin/classic/browser/devtools/toggle-tools.png            (../shared/devtools/images/toggle-tools.png)
+  skin/classic/browser/devtools/toggle-tools@2x.png         (../shared/devtools/images/toggle-tools@2x.png)
   skin/classic/browser/devtools/dock-bottom@2x.png          (../shared/devtools/images/dock-bottom@2x.png)
   skin/classic/browser/devtools/dock-side@2x.png          (../shared/devtools/images/dock-side@2x.png)
 * skin/classic/browser/devtools/inspector.css               (../shared/devtools/inspector.css)
   skin/classic/browser/devtools/profiler-stopwatch.svg      (../shared/devtools/images/profiler-stopwatch.svg)
   skin/classic/browser/devtools/profiler-stopwatch-checked.svg      (../shared/devtools/images/profiler-stopwatch-checked.svg)
   skin/classic/browser/devtools/tool-options.svg            (../shared/devtools/images/tool-options.svg)
   skin/classic/browser/devtools/tool-webconsole.svg         (../shared/devtools/images/tool-webconsole.svg)
   skin/classic/browser/devtools/tool-debugger.svg           (../shared/devtools/images/tool-debugger.svg)
--- a/browser/themes/shared/devtools/commandline.inc.css
+++ b/browser/themes/shared/devtools/commandline.inc.css
@@ -23,16 +23,22 @@
   padding: 0 10px;
   width: 32px;
 }
 
 .developer-toolbar-button > image {
   margin: auto 10px;
 }
 
+.developer-toolbar-button > .toolbarbutton-icon,
+#developer-toolbar-closebutton > .toolbarbutton-icon {
+  width: 16px;
+  height: 16px;
+}
+
 #developer-toolbar-toolbox-button {
   list-style-image: url("chrome://browser/skin/devtools/toggle-tools.png");
   -moz-image-region: rect(0px, 16px, 16px, 0px);
 }
 
 #developer-toolbar-toolbox-button > label {
   display: none;
 }
@@ -44,26 +50,51 @@
 #developer-toolbar-toolbox-button:hover:active {
   -moz-image-region: rect(0px, 48px, 16px, 32px);
 }
 
 #developer-toolbar-toolbox-button[checked=true] {
   -moz-image-region: rect(0px, 64px, 16px, 48px);
 }
 
+@media (min-resolution: 2dppx) {
+  #developer-toolbar-toolbox-button {
+    list-style-image: url("chrome://browser/skin/devtools/toggle-tools@2x.png");
+    -moz-image-region: rect(0px, 32px, 32px, 0px);
+  }
+
+  #developer-toolbar-toolbox-button:hover {
+    -moz-image-region: rect(0px, 64px, 32px, 32px);
+  }
+
+  #developer-toolbar-toolbox-button:hover:active {
+    -moz-image-region: rect(0px, 96px, 32px, 64px);
+  }
+
+  #developer-toolbar-toolbox-button[checked=true] {
+    -moz-image-region: rect(0px, 128px, 32px, 96px);
+  }
+}
+
 #developer-toolbar-closebutton {
   list-style-image: url("chrome://browser/skin/devtools/close.png");
   -moz-appearance: none;
   border: none;
   margin: 0 4px;
   min-width: 16px;
   width: 16px;
   opacity: 0.6;
 }
 
+@media (min-resolution: 2dppx) {
+  #developer-toolbar-closebutton {
+    list-style-image: url("chrome://browser/skin/devtools/close@2x.png");
+  }
+}
+
 #developer-toolbar-closebutton > .toolbarbutton-icon {
   /* XXX Buttons have padding in widget/ that we don't want here but can't override with good CSS, so we must
      use evil CSS to give the impression of smaller content */
   margin: -4px;
 }
 
 #developer-toolbar-closebutton > .toolbarbutton-text {
   display: none;
@@ -104,33 +135,53 @@ html|*#gcli-output-frame {
   box-shadow: none;
   border-width: 0;
   background-color: transparent;
 }
 
 .gclitoolbar-input-node {
   -moz-appearance: none;
   color: hsl(210,30%,85%);
-  padding-left: 20px;
   background-color: #242b33;
   background-repeat: no-repeat;
   background-position: 4px center;
   box-shadow: 0 1px 1px hsla(206,37%,4%,.2) inset,
               1px 0 0 hsla(206,37%,4%,.2) inset,
               -1px 0 0 hsla(206,37%,4%,.2) inset;
   line-height: 32px;
   outline-style: none;
-  background-image: -moz-image-rect(url("chrome://browser/skin/devtools/commandline-icon.png"), 0, 16, 16, 0);
+  padding: 0;
 }
 
 .gclitoolbar-input-node[focused="true"] {
-  background-image: -moz-image-rect(url("chrome://browser/skin/devtools/commandline-icon.png"), 0, 32, 16, 16);
   background-color: #232e38;
 }
 
+.gclitoolbar-input-node::before {
+  content: "";
+  display: inline-block;
+  -moz-box-ordinal-group: 0;
+  width: 16px;
+  height: 16px;
+  margin: 0 2px;
+  background-image: url("chrome://browser/skin/devtools/commandline-icon.png");
+  background-position: 0 center;
+  background-size: 32px 16px;
+}
+
+.gclitoolbar-input-node[focused="true"]::before {
+  background-position: -16px center;
+}
+
+@media (min-resolution: 2dppx) {
+  .gclitoolbar-input-node::before {
+    background-image: url("chrome://browser/skin/devtools/commandline-icon@2x.png");
+  }
+}
+
 .gclitoolbar-input-node > .textbox-input-box > html|*.textbox-input::-moz-selection {
   background-color: hsl(210,30%,85%);
   color: hsl(210,24%,16%);
   text-shadow: none;
 }
 
 .gclitoolbar-complete-node {
   padding-left: 21px;
--- a/browser/themes/shared/devtools/highlighter.inc.css
+++ b/browser/themes/shared/devtools/highlighter.inc.css
@@ -69,16 +69,23 @@ html|*.highlighter-nodeinfobar-id {
   color: hsl(103,46%,54%);
 }
 
 html|*.highlighter-nodeinfobar-classes,
 html|*.highlighter-nodeinfobar-pseudo-classes {
   color: hsl(200,74%,57%);
 }
 
+html|*.highlighter-nodeinfobar-dimensions {
+  color: hsl(210,30%,85%);
+  -moz-border-start: 1px solid #5a6169;
+  -moz-margin-start: 6px;
+  -moz-padding-start: 6px;
+}
+
 /* Highlighter - Node Infobar - box & arrow */
 
 .highlighter-nodeinfobar-arrow {
   width: 14px;
   height: 14px;
   -moz-margin-start: calc(50% - 7px);
   transform: rotate(-45deg);
   background-clip: padding-box;
index 8a40d0d116d76c6ec4a77a459797f9f5e112df69..495439391c7bb38528ca205248f14554325de0b7
GIT binary patch
literal 883
zc$@)q1C0EMP)<h;3K|Lk000e1NJLTq002M$000mO1^@s6rssJn0009zNkl<Zc-rlj
z-%C_M6vsD6`$hEJOT8E&T2Tt=?vsx__c!zsy%k7NL_{Km5G2`;Cp8JzyM~JP5GsZb
z3K_JZ5*k9aKXk#&duQ&<++C(~Y>yeT)1BL^1BY{WcE7NnGv9mW#KJDd^l{(!Pin2J
zViDF4&<deXfxbG5e(k5I$-A<CN$Ueq^v%g{HSxi(^_Rw$?DSH~S|-#}#^nfxFN0e)
zddAcRCW0}~SD!JsP@OjV)y*JLV5NmMR@CJfw9z-EGKuC?cA?qHY7BS|uZI&q0e{{h
zc;?Fo+TsL+&@~>Hp9r4$svGnN!|HRctOH<1@C9BR&uV=*tUlr7>HwM%d_0vIZy3pj
z*2g>^v=Y3>WE;U_W`1yIWyK8R3H6xZxvzQ%9y1i<8RSDo+w)@o4)_Uz$6R2=;k?#I
zjJ9_&{oC+Pc7otB!*|1Hd-6to%=N`fDgd4)!J`v??}^hUJrwC36~L9Ueg}Lv?kT0u
znF~-_-Yx)BjRcQQz`Z<IO(uOjm0hL+B%Ev`!J`wl_EO(LZvx<|bK3!aa8pWs1}xo9
zd-#eN^{6BJbM=ICbrGEKiuSOm#saWXnhF4JaB^<Y0(pw|@D(vTAe-Ld00)3u;Cbp#
zQF4{_TI>B>s1AbN6~5|%-|RE!tGEaT%CNfv-0{|=pNHE5Y(7h{yMmV%P5Qdzd^I$@
zSp=;Ri!snwgokc_yCS>@lCCRjP4HNtits{xWJ;a?2mDZF_~Pa&58@{Hcq%t!fZuE$
z$iyw+RVP^gAvQIat7|wD*ua-xh1ZbZxJ=&1nZORdlm_T&$a8QdKF68B5<U$v4UU+D
zzVIApg3$0DR~_1T^_bx~xTR1i95u*{@bGzNeO(_j3eE5{4aqNm>G$A6+1?M@BEnPj
zMueyLeDXixClNm6z$32X(BpC0A7$d>LO#O&z=Zk`W#WT-y|+B{Y($y(;EL-l4?P=^
zCO)ugJ7xg5Y6VYvU-e-I?LA9)n)u#hh6dMV2~P#s(gS>#9XtWZ;$^V>UCx6W1aGqd
z`U=Dg>=^`aRRAw&g_9oz_&$QSD!@Rd6^bwlUA}J%es6_ezW{VdF*q5<m|Oq=002ov
JPDHLkV1h=;q`3e9
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..971f414316f576158c83eb021122b9835f336801
GIT binary patch
literal 1834
zc$@($2i5qAP)<h;3K|Lk000e1NJLTq004jh001Be1^@s6+9Gfz000K@Nkl<Zc-rln
ze~4676vwU16r!*+HBD1Q`)NkjI;#s&L?mQIp%7$(SzzRkeqc!p8WdS5)kaut80sp`
z&ZyWQN)e)hzfw#|G8C8Cnc3N=ZL>S~-Cu9k+xI}a<L;Z8_T7Cm|ICHM&b)VdUuHk&
z-gnMDXHa!i$AArE%ynH#-6f^>gzL(=LU^+oGo#9>&$OP-xq5*Ky}*_7DbD%0GU^X*
z=Uyc3>P2a$mpR$oytEJ#%ccM5UNg8hnutW=$wiS^YT3+KV%{UsbolzGPoEybRc<~P
zdb7*bk9|z2H3TQbfBX%u)Jt5h4+8w3z0DQ6F--jyV}`c7UcKYy(lFy=yU9611HZ9l
zXrwt&ABm^Z0B?F5uJ49(grlF$nvqQE&wRnd;1pY=G?T*m@vbprptFNO7l3~#n^pIO
zrJoXJyyJEsC(s4Vaa{diSo*W0sqp}O+!s8oHkNWCv1B;<jxjg7LT@MrUkEcH#Fc*&
zc9%WH;7ci|gr%R!nBflZK2r>SnyLFkgWvSmZ~#726n?Z7`XLR@y|;tF2e@knLZ5NH
zE(m<+Upo-`7kI9z6#OvtvGSV&!CyT9`ha_<E4^L)g-_RA;jOXN?~-yVbk6h_K3&KC
z?`-wknVJU3m--8zuH*h5Tm70?>okDA)L-~?9doSp0W!DH4?YuWYccrb95jz}bBEpP
z_ueT0|40e=hiG1Br<`oJ`n%T_fPb+Bd<SOQEaQ^ec-vb4)t|gr3_dvr&8vyF*~LXz
z#UJ@O0Ht8Q?@K8s5&RxU@gBEw0Aw7#4j}cb{=urc`!akoWG&BG#Xmf^*Vh4<_pa}J
zrl%A9o_TtqjXogmEwKIj>LWdzei=SlZ9D;ei@~M$xt{}Mg?Oy*$}>|KS3g`eSE*;L
z98h{YeI3A6&-QT^rXPS~9J_k19RNiBC;S|so#htvS@~So;9e$wUYh9@Ry#l}dBWEL
zniGBWXU1A<a4(ZT4`&1`EFD0v^K$?S%sM(cZY~O+Oy=fTI$*P(1K@hJv$OM-67XT_
zMXOhUn>*s?0Jwg*4XJT4_+;%_y~HvAJW^l>I9wNP8(9oKS#5K2iCzBB^-==qZ@R&i
z@@ZG-R|u5f9Bj$=+P#9*hXpeL?(Gsnzr=+1p3C)b{yT-9VdntdjfFFS?vqYdFGZZN
z9^aYooG!1<(g7L?q`xIkV!L08&+B2#Z_cddRGnQEF1$Jd8N?V@TWn9}TvyH^kU@+K
zebDw~4#&Ln31mQPhyL|8`ZJ@+`2;ecwLkRrvEcf&#bafC8Pox={m-@e@wCO$a|mWY
zd)Y>xws@@aQvu=A`+vEOK5g-Ucq$-#iW8Ps3_h1?Ev^1a!RJD6rPW_4_{bg((CV)g
z{MvZx0ImLtz-L^qr)N|OJ~D?GJ)=_a5g;C=XH*J4>?l1W*gL<=+-d=T2L*)i{2_t!
ze#S-xg%9oDDIjcVX&D++{~|YIgTe=RMFC-}F~frD;}B<TaQI0@0U@3-EFk=@5w6hR
z6oX9BKJ@_zq*si~w}kRmxl+GQ>0Tz28BWi!fIk|m_ot$e5$)3<6qou9F4d=8!dZp-
zfYLoU-Uu80zquKQqR-h9pc(Z6AUjx%8o_3K-;U24DBW|I8Etj!8&8Y_#Iq$pGwK7t
z3E^sF0Gp8|Y=>>2bnoG))1w~-e9H4F-w!BZ4<C6x<@**gw(v8Q?_0>&!p|VzFQ{LI
zUxi<VUxi<VUxokwPT0&Cz~4xaOSE}S4&%rFBLtO<_YvfxZC*;<7lyurGBMz`mVobE
zC~=f*5$#U~{MKUdsj4?CmYPv9_*B=gBz&stR}wze^}~i=_BO9<HhWto;lufEuO$2w
zepe;o<AiR4HGIqJ+WT};#+ZDPDs^0XAD6Si6L7y`T%F8vHIFKF&}#|+-=@5~6M$Xh
z@{6fb=kVPAayEDZ?oL2<@pmy*>hO1eSsOeV)$FByt*7DBjLi%9LWNI*f;u>1eNX$C
zn3!L^3llyK3hEH}eR0Jhy5!XS>Rp)dX;4syL!2x6v!cnV`PI7~_~EU<?>YD0%x~l%
z`1D$!*kDk|1CaM~sXobX<oFN%K8g*()(7MSPVhGMYUD7mR*DV6*2ngrz-wSruSO0-
zuLZz|e%Jt1?s)*;MY{l%YXQE|2Y#3ixR^`*hfrQUm1_b1mvX_U;V^F{V0U8|XrOW}
z&i&7q3qB1T*{%fe?nu0?fy%Y`d_F|@{=NFtr<WD-TQ2lAY{#o8FvP90=~JJcE4-gw
zu7Bk+*Fb?`IrXVe4-Rk+;5!fl#8dRzE7yQN_32?3I9D4_cEADR6c`Swj_Rn6>IiN8
Y3rJqitcexp<p2Nx07*qoM6N<$g3TP1MgRZ+
--- a/browser/themes/shared/devtools/styleeditor.css
+++ b/browser/themes/shared/devtools/styleeditor.css
@@ -114,16 +114,21 @@
     background-image: url(itemToggle@2x.png);
   }
 }
 
 .disabled > .stylesheet-enabled {
   background-position: -24px 8px;
 }
 
+#style-editor-options {
+  width: 20px;
+  overflow: hidden;
+}
+
 /* Invert all toggle icons but the one in the active row for light theme */
 .theme-light .splitview-nav > li:not(.splitview-active) .stylesheet-enabled {
   filter: url(filters.svg#invert);
 }
 
 .splitview-nav > li > .stylesheet-enabled:focus,
 .splitview-nav > li:hover > .stylesheet-enabled {
   outline: 0;
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -318,16 +318,17 @@ browser.jar:
         skin/classic/browser/devtools/responsive-horizontal-resizer@2x.png  (../shared/devtools/images/responsivemode/responsive-horizontal-resizer@2x.png)
         skin/classic/browser/devtools/responsiveui-rotate.png       (../shared/devtools/images/responsivemode/responsiveui-rotate.png)
         skin/classic/browser/devtools/responsiveui-rotate@2x.png       (../shared/devtools/images/responsivemode/responsiveui-rotate@2x.png)
         skin/classic/browser/devtools/responsiveui-touch.png        (../shared/devtools/images/responsivemode/responsiveui-touch.png)
         skin/classic/browser/devtools/responsiveui-touch@2x.png        (../shared/devtools/images/responsivemode/responsiveui-touch@2x.png)
         skin/classic/browser/devtools/responsiveui-screenshot.png   (../shared/devtools/images/responsivemode/responsiveui-screenshot.png)
         skin/classic/browser/devtools/responsiveui-screenshot@2x.png   (../shared/devtools/images/responsivemode/responsiveui-screenshot@2x.png)
         skin/classic/browser/devtools/toggle-tools.png              (../shared/devtools/images/toggle-tools.png)
+        skin/classic/browser/devtools/toggle-tools@2x.png              (../shared/devtools/images/toggle-tools@2x.png)
         skin/classic/browser/devtools/dock-bottom@2x.png            (../shared/devtools/images/dock-bottom@2x.png)
         skin/classic/browser/devtools/dock-side@2x.png              (../shared/devtools/images/dock-side@2x.png)
         skin/classic/browser/devtools/floating-scrollbars.css       (devtools/floating-scrollbars.css)
         skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
 *       skin/classic/browser/devtools/inspector.css                 (../shared/devtools/inspector.css)
         skin/classic/browser/devtools/profiler-stopwatch.svg        (../shared/devtools/images/profiler-stopwatch.svg)
         skin/classic/browser/devtools/profiler-stopwatch-checked.svg  (../shared/devtools/images/profiler-stopwatch-checked.svg)
         skin/classic/browser/devtools/tool-options.svg              (../shared/devtools/images/tool-options.svg)
@@ -718,16 +719,17 @@ browser.jar:
         skin/classic/aero/browser/devtools/responsive-horizontal-resizer@2x.png  (../shared/devtools/images/responsivemode/responsive-horizontal-resizer@2x.png)
         skin/classic/aero/browser/devtools/responsiveui-rotate.png          (../shared/devtools/images/responsivemode/responsiveui-rotate.png)
         skin/classic/aero/browser/devtools/responsiveui-rotate@2x.png       (../shared/devtools/images/responsivemode/responsiveui-rotate@2x.png)
         skin/classic/aero/browser/devtools/responsiveui-touch.png           (../shared/devtools/images/responsivemode/responsiveui-touch.png)
         skin/classic/aero/browser/devtools/responsiveui-touch@2x.png        (../shared/devtools/images/responsivemode/responsiveui-touch@2x.png)
         skin/classic/aero/browser/devtools/responsiveui-screenshot.png      (../shared/devtools/images/responsivemode/responsiveui-screenshot.png)
         skin/classic/aero/browser/devtools/responsiveui-screenshot@2x.png   (../shared/devtools/images/responsivemode/responsiveui-screenshot@2x.png)
         skin/classic/aero/browser/devtools/toggle-tools.png          (../shared/devtools/images/toggle-tools.png)
+        skin/classic/aero/browser/devtools/toggle-tools@2x.png       (../shared/devtools/images/toggle-tools@2x.png)
         skin/classic/aero/browser/devtools/dock-bottom@2x.png        (../shared/devtools/images/dock-bottom@2x.png)
         skin/classic/aero/browser/devtools/dock-side@2x.png          (../shared/devtools/images/dock-side@2x.png)
         skin/classic/aero/browser/devtools/floating-scrollbars.css   (devtools/floating-scrollbars.css)
         skin/classic/aero/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
 *       skin/classic/aero/browser/devtools/inspector.css             (../shared/devtools/inspector.css)
         skin/classic/aero/browser/devtools/profiler-stopwatch.svg    (../shared/devtools/images/profiler-stopwatch.svg)
         skin/classic/aero/browser/devtools/profiler-stopwatch-checked.svg      (../shared/devtools/images/profiler-stopwatch-checked.svg)
         skin/classic/aero/browser/devtools/tool-options.svg          (../shared/devtools/images/tool-options.svg)
--- a/configure.in
+++ b/configure.in
@@ -3933,16 +3933,17 @@ if test -n "$MOZ_RTSP"; then
 fi
 USE_ARM_KUSER=
 BUILD_CTYPES=1
 MOZ_USE_NATIVE_POPUP_WINDOWS=
 MOZ_ANDROID_HISTORY=
 MOZ_WEBSMS_BACKEND=
 MOZ_ANDROID_BEAM=
 MOZ_ANDROID_SYNTHAPKS=
+MOZ_LOCALE_SWITCHER=
 ACCESSIBILITY=1
 MOZ_TIME_MANAGER=
 MOZ_PAY=
 MOZ_AUDIO_CHANNEL_MANAGER=
 NSS_NO_LIBPKIX=
 MOZ_CONTENT_SANDBOX=
 MOZ_CONTENT_SANDBOX_REPORTER=1
 JSGC_USE_EXACT_ROOTING=
@@ -4943,16 +4944,23 @@ MOZ_ARG_DISABLE_BOOL(websms-backend,
     MOZ_WEBSMS_BACKEND=,
     MOZ_WEBSMS_BACKEND=1)
 
 if test -n "$MOZ_WEBSMS_BACKEND"; then
     AC_DEFINE(MOZ_WEBSMS_BACKEND)
 fi
 
 dnl ========================================================
+dnl = Enable runtime locale switching on Android
+dnl ========================================================
+if test -n "$MOZ_LOCALE_SWITCHER"; then
+    AC_DEFINE(MOZ_LOCALE_SWITCHER)
+fi
+
+dnl ========================================================
 dnl = Enable NFC permission on Android
 dnl ========================================================
 if test -n "$MOZ_ANDROID_BEAM"; then
     AC_DEFINE(MOZ_ANDROID_BEAM)
 fi
 
 dnl ========================================================
 dnl = Synthesized Webapp APKs on Android
@@ -8562,16 +8570,17 @@ AC_SUBST(MOZ_D3DCOMPILER_XP_DLL)
 AC_SUBST(MOZ_D3DCOMPILER_XP_CAB)
 
 AC_SUBST(MOZ_METRO)
 
 AC_SUBST(MOZ_ANDROID_HISTORY)
 AC_SUBST(MOZ_WEBSMS_BACKEND)
 AC_SUBST(MOZ_ANDROID_BEAM)
 AC_SUBST(MOZ_ANDROID_SYNTHAPKS)
+AC_SUBST(MOZ_LOCALE_SWITCHER)
 AC_SUBST(MOZ_DISABLE_GECKOVIEW)
 AC_SUBST(ENABLE_STRIP)
 AC_SUBST(PKG_SKIP_STRIP)
 AC_SUBST(STRIP_FLAGS)
 AC_SUBST(USE_ELF_HACK)
 AC_SUBST(INCREMENTAL_LINKER)
 AC_SUBST(MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS)
 AC_SUBST(MOZ_COMPONENT_NSPR_LIBS)
--- a/dom/bluetooth2/BluetoothAdapter.cpp
+++ b/dom/bluetooth2/BluetoothAdapter.cpp
@@ -19,16 +19,19 @@
 #include "mozilla/LazyIdleThread.h"
 
 #include "BluetoothAdapter.h"
 #include "BluetoothDevice.h"
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "BluetoothUtils.h"
 
+#define ERR_INVALID_ADAPTER_STATE "InvalidAdapterStateError"
+#define ERR_CHANGE_ADAPTER_STATE  "ChangeAdapterStateError"
+
 using namespace mozilla;
 using namespace mozilla::dom;
 
 USING_BLUETOOTH_NAMESPACE
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothAdapter)
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(BluetoothAdapter,
@@ -116,16 +119,17 @@ public:
   }
 
   void
   ReleaseMembers()
   {
     BluetoothReplyRunnable::ReleaseMembers();
     mAdapterPtr = nullptr;
   }
+
 private:
   nsRefPtr<BluetoothAdapter> mAdapterPtr;
 };
 
 class GetScoConnectionStatusTask : public BluetoothReplyRunnable
 {
 public:
   GetScoConnectionStatusTask(nsIDOMDOMRequest* aReq) :
@@ -151,30 +155,63 @@ public:
 
   void
   ReleaseMembers()
   {
     BluetoothReplyRunnable::ReleaseMembers();
   }
 };
 
+class EnableDisableAdapterTask : public BluetoothReplyRunnable
+{
+public:
+  EnableDisableAdapterTask(Promise* aPromise)
+    : BluetoothReplyRunnable(nullptr)
+    , mPromise(aPromise)
+  { }
+
+  bool
+  ParseSuccessfulReply(JS::MutableHandle<JS::Value> aValue)
+  {
+    /*
+     * It is supposed to be Promise<void> according to BluetoothAdapter.webidl,
+     * but we have to pass "true" since it is mandatory to pass an
+     * argument while calling MaybeResolve.
+     */
+    mPromise->MaybeResolve(true);
+    aValue.setUndefined();
+    return true;
+  }
+
+  void
+  ReleaseMembers()
+  {
+    BluetoothReplyRunnable::ReleaseMembers();
+    mPromise = nullptr;
+  }
+
+private:
+  nsRefPtr<Promise> mPromise;
+};
+
 static int kCreatePairedDeviceTimeout = 50000; // unit: msec
 
 BluetoothAdapter::BluetoothAdapter(nsPIDOMWindow* aWindow,
                                    const BluetoothValue& aValue)
   : DOMEventTargetHelper(aWindow)
   , BluetoothPropertyContainer(BluetoothObjectType::TYPE_ADAPTER)
   , mJsUuids(nullptr)
   , mJsDeviceAddresses(nullptr)
+  // TODO: Change to Disabled after Bug 1006309 landed
+  , mState(BluetoothAdapterState::Enabled)
   , mDiscoverable(false)
   , mDiscovering(false)
   , mPairable(false)
   , mPowered(false)
   , mIsRooted(false)
-  , mState(BluetoothAdapterState::Disabled)
 {
   MOZ_ASSERT(aWindow);
   MOZ_ASSERT(IsDOMBinding());
 
   const InfallibleTArray<BluetoothNamedValue>& values =
     aValue.get_ArrayOfBluetoothNamedValue();
   for (uint32_t i = 0; i < values.Length(); ++i) {
     SetPropertyByValue(values[i]);
@@ -226,17 +263,21 @@ BluetoothAdapter::Root()
   mIsRooted = true;
 }
 
 void
 BluetoothAdapter::SetPropertyByValue(const BluetoothNamedValue& aValue)
 {
   const nsString& name = aValue.name();
   const BluetoothValue& value = aValue.value();
-  if (name.EqualsLiteral("Name")) {
+  if (name.EqualsLiteral("State")) {
+    bool isEnabled = value.get_bool();
+    mState = isEnabled ? BluetoothAdapterState::Enabled
+                       : BluetoothAdapterState::Disabled;
+  } else if (name.EqualsLiteral("Name")) {
     mName = value.get_nsString();
   } else if (name.EqualsLiteral("Address")) {
     mAddress = value.get_nsString();
   } else if (name.EqualsLiteral("Path")) {
     mPath = value.get_nsString();
   } else if (name.EqualsLiteral("Discoverable")) {
     mDiscoverable = value.get_bool();
   } else if (name.EqualsLiteral("Discovering")) {
@@ -664,29 +705,65 @@ BluetoothAdapter::SetPairingConfirmation
     BT_WARNING("SetPairingConfirmation failed!");
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   return request.forget();
 }
 
-/*
- * TODO: Implement Enable/Disable functions
- */
+already_AddRefed<Promise>
+BluetoothAdapter::EnableDisable(bool aEnable)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  NS_ENSURE_TRUE(global, nullptr);
+
+  nsRefPtr<Promise> promise = new Promise(global);
+
+  // Make sure BluetoothService is available before modifying adapter state
+  BluetoothService* bs = BluetoothService::Get();
+  if (!bs) {
+    promise->MaybeReject(ERR_CHANGE_ADAPTER_STATE);
+    return promise.forget();
+  }
+
+  if (aEnable) {
+    if (mState != BluetoothAdapterState::Disabled) {
+      promise->MaybeReject(ERR_INVALID_ADAPTER_STATE);
+      return promise.forget();
+    }
+    mState = BluetoothAdapterState::Enabling;
+  } else {
+    if (mState != BluetoothAdapterState::Enabled) {
+      promise->MaybeReject(ERR_INVALID_ADAPTER_STATE);
+      return promise.forget();
+    }
+    mState = BluetoothAdapterState::Disabling;
+  }
+
+  // TODO: Fire attr changed event for this state change
+  nsRefPtr<BluetoothReplyRunnable> result = new EnableDisableAdapterTask(promise);
+
+  if(NS_FAILED(bs->EnableDisable(aEnable, result))) {
+    promise->MaybeReject(ERR_CHANGE_ADAPTER_STATE);
+  }
+
+  return promise.forget();
+}
+
 already_AddRefed<Promise>
 BluetoothAdapter::Enable()
 {
-  return nullptr;
+  return EnableDisable(true);
 }
 
 already_AddRefed<Promise>
 BluetoothAdapter::Disable()
 {
-  return nullptr;
+  return EnableDisable(false);
 }
 
 already_AddRefed<DOMRequest>
 BluetoothAdapter::Connect(BluetoothDevice& aDevice,
                           const Optional<short unsigned int>& aServiceUuid,
                           ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindow> win = GetOwner();
--- a/dom/bluetooth2/BluetoothAdapter.h
+++ b/dom/bluetooth2/BluetoothAdapter.h
@@ -117,20 +117,19 @@ public:
                ErrorResult& aRv);
   already_AddRefed<DOMRequest>
     SetPairingConfirmation(const nsAString& aDeviceAddress, bool aConfirmation,
                            ErrorResult& aRv);
   already_AddRefed<DOMRequest>
     SetAuthorization(const nsAString& aDeviceAddress, bool aAllow,
                      ErrorResult& aRv);
 
-  already_AddRefed<Promise>
-    Enable();
-  already_AddRefed<Promise>
-    Disable();
+  already_AddRefed<Promise> EnableDisable(bool aEnable);
+  already_AddRefed<Promise> Enable();
+  already_AddRefed<Promise> Disable();
 
   already_AddRefed<DOMRequest>
     Connect(BluetoothDevice& aDevice,
             const Optional<short unsigned int>& aServiceUuid, ErrorResult& aRv);
   already_AddRefed<DOMRequest>
     Disconnect(BluetoothDevice& aDevice,
                const Optional<short unsigned int>& aServiceUuid,
                ErrorResult& aRv);
--- a/dom/bluetooth2/BluetoothService.cpp
+++ b/dom/bluetooth2/BluetoothService.cpp
@@ -158,24 +158,18 @@ BluetoothService::ToggleBtAck::Run()
     return NS_OK;
   }
 
   // Update mEnabled of BluetoothService object since
   // StartInternal/StopInternal have been already done.
   sBluetoothService->SetEnabled(mEnabled);
   sToggleInProgress = false;
 
-  nsAutoString signalName;
-  signalName = mEnabled ? NS_LITERAL_STRING("Enabled")
-                        : NS_LITERAL_STRING("Disabled");
-  BluetoothSignal signal(signalName, NS_LITERAL_STRING(KEY_MANAGER), true);
-  sBluetoothService->DistributeSignal(signal);
-
-  // Event 'AdapterAdded' has to be fired after firing 'Enabled'
   sBluetoothService->TryFiringAdapterAdded();
+  sBluetoothService->FireAdapterStateChanged(mEnabled);
 
   return NS_OK;
 }
 
 class BluetoothService::StartupTask : public nsISettingsServiceCallback
 {
 public:
   NS_DECL_ISUPPORTS
@@ -215,29 +209,16 @@ BluetoothService::IsToggling() const
   return sToggleInProgress;
 }
 
 BluetoothService::~BluetoothService()
 {
   Cleanup();
 }
 
-PLDHashOperator
-RemoveObserversExceptBluetoothManager
-  (const nsAString& key,
-   nsAutoPtr<BluetoothSignalObserverList>& value,
-   void* arg)
-{
-  if (!key.EqualsLiteral(KEY_MANAGER)) {
-    return PL_DHASH_REMOVE;
-  }
-
-  return PL_DHASH_NEXT;
-}
-
 // static
 BluetoothService*
 BluetoothService::Create()
 {
 #if defined(MOZ_B2G_BT)
   if (!IsMainProcess()) {
     return BluetoothServiceChildProcess::Create();
   }
@@ -379,17 +360,18 @@ BluetoothService::DistributeSignal(const
                NS_ConvertUTF16toUTF8(aSignal.path()).get());
     return;
   }
   MOZ_ASSERT(ol->Length());
   ol->Broadcast(aSignal);
 }
 
 nsresult
-BluetoothService::StartBluetooth(bool aIsStartup)
+BluetoothService::StartBluetooth(bool aIsStartup,
+                                 BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (sInShutdown) {
     // Don't try to start if we're already shutting down.
     MOZ_ASSERT(false, "Start called while in shutdown!");
     return NS_ERROR_FAILURE;
   }
@@ -400,32 +382,33 @@ BluetoothService::StartBluetooth(bool aI
    * send ToggleBtAck task. One special case happens at startup stage. At
    * startup, the initialization of BluetoothService still has to be done
    * even if Bluetooth is already enabled.
    *
    * Please see bug 892392 for more information.
    */
   if (aIsStartup || !sBluetoothService->IsEnabled()) {
     // Switch Bluetooth on
-    if (NS_FAILED(sBluetoothService->StartInternal())) {
+    if (NS_FAILED(sBluetoothService->StartInternal(aRunnable))) {
       BT_WARNING("Bluetooth service failed to start!");
     }
   } else {
     BT_WARNING("Bluetooth has already been enabled before.");
     nsRefPtr<nsRunnable> runnable = new BluetoothService::ToggleBtAck(true);
     if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
       BT_WARNING("Failed to dispatch to main thread!");
     }
   }
 
   return NS_OK;
 }
 
 nsresult
-BluetoothService::StopBluetooth(bool aIsStartup)
+BluetoothService::StopBluetooth(bool aIsStartup,
+                                BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothProfileManagerBase* profile;
   profile = BluetoothHfpManager::Get();
   NS_ENSURE_TRUE(profile, NS_ERROR_FAILURE);
   if (profile->IsConnected()) {
     profile->Disconnect(nullptr);
@@ -461,65 +444,56 @@ BluetoothService::StopBluetooth(bool aIs
    * send ToggleBtAck task. One special case happens at startup stage. At
    * startup, the initialization of BluetoothService still has to be done
    * even if Bluetooth is disabled.
    *
    * Please see bug 892392 for more information.
    */
   if (aIsStartup || sBluetoothService->IsEnabled()) {
     // Switch Bluetooth off
-    if (NS_FAILED(sBluetoothService->StopInternal())) {
+    if (NS_FAILED(sBluetoothService->StopInternal(aRunnable))) {
       BT_WARNING("Bluetooth service failed to stop!");
     }
   } else {
     BT_WARNING("Bluetooth has already been enabled/disabled before.");
     nsRefPtr<nsRunnable> runnable = new BluetoothService::ToggleBtAck(false);
     if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
       BT_WARNING("Failed to dispatch to main thread!");
     }
   }
 
   return NS_OK;
 }
 
 nsresult
-BluetoothService::StartStopBluetooth(bool aStart, bool aIsStartup)
+BluetoothService::StartStopBluetooth(bool aStart,
+                                     bool aIsStartup,
+                                     BluetoothReplyRunnable* aRunnable)
 {
   nsresult rv;
   if (aStart) {
-    rv = StartBluetooth(aIsStartup);
+    rv = StartBluetooth(aIsStartup, aRunnable);
   } else {
-    rv = StopBluetooth(aIsStartup);
+    rv = StopBluetooth(aIsStartup, aRunnable);
   }
   return rv;
 }
 
 void
 BluetoothService::SetEnabled(bool aEnabled)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   AutoInfallibleTArray<BluetoothParent*, 10> childActors;
   GetAllBluetoothActors(childActors);
 
   for (uint32_t index = 0; index < childActors.Length(); index++) {
     unused << childActors[index]->SendEnabled(aEnabled);
   }
 
-  if (!aEnabled) {
-    /**
-     * Remove all handlers except BluetoothManager when turning off bluetooth
-     * since it is possible that the event 'onAdapterAdded' would be fired after
-     * BluetoothManagers of child process are registered. Please see Bug 827759
-     * for more details.
-     */
-    mBluetoothSignalObserverTable.Enumerate(
-      RemoveObserversExceptBluetoothManager, nullptr);
-  }
-
   /**
    * mEnabled: real status of bluetooth
    * aEnabled: expected status of bluetooth
    */
   if (mEnabled == aEnabled) {
     BT_WARNING("Bluetooth has already been enabled/disabled before "
                "or the toggling is failed.");
   }
@@ -548,17 +522,17 @@ BluetoothService::HandleStartup()
   sToggleInProgress = true;
   return NS_OK;
 }
 
 nsresult
 BluetoothService::HandleStartupSettingsCheck(bool aEnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  return StartStopBluetooth(aEnable, true);
+  return StartStopBluetooth(aEnable, true, nullptr);
 }
 
 nsresult
 BluetoothService::HandleSettingsChanged(const nsAString& aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // The string that we're interested in will be a JSON string that looks like:
@@ -605,47 +579,16 @@ BluetoothService::HandleSettingsChanged(
     }
 
     if (!value.isBoolean()) {
       MOZ_ASSERT(false, "Expecting a boolean for 'bluetooth.debugging.enabled'!");
       return NS_ERROR_UNEXPECTED;
     }
 
     SWITCH_BT_DEBUG(value.toBoolean());
-
-    return NS_OK;
-  }
-
-  // Second, check if the string is BLUETOOTH_ENABLED_SETTING
-  if (!JS_StringEqualsAscii(cx, key.toString(), BLUETOOTH_ENABLED_SETTING, &match)) {
-    MOZ_ASSERT(!JS_IsExceptionPending(cx));
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (match) {
-    JS::Rooted<JS::Value> value(cx);
-    if (!JS_GetProperty(cx, obj, "value", &value)) {
-      MOZ_ASSERT(!JS_IsExceptionPending(cx));
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    if (!value.isBoolean()) {
-      MOZ_ASSERT(false, "Expecting a boolean for 'bluetooth.enabled'!");
-      return NS_ERROR_UNEXPECTED;
-    }
-
-    if (sToggleInProgress || value.toBoolean() == IsEnabled()) {
-      // Nothing to do here.
-      return NS_OK;
-    }
-
-    sToggleInProgress = true;
-
-    nsresult rv = StartStopBluetooth(value.toBoolean(), false);
-    NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 nsresult
 BluetoothService::HandleShutdown()
 {
@@ -699,17 +642,17 @@ BluetoothService::HandleShutdown()
         }
       }
       else {
         MOZ_ASSERT(false, "Failed to initialize shutdown timer!");
       }
     }
   }
 
-  if (IsEnabled() && NS_FAILED(StopBluetooth(false))) {
+  if (IsEnabled() && NS_FAILED(StopBluetooth(false, nullptr))) {
     MOZ_ASSERT(false, "Failed to deliver stop message!");
   }
 
   return NS_OK;
 }
 
 // static
 BluetoothService*
@@ -780,16 +723,48 @@ BluetoothService::TryFiringAdapterAdded(
 void
 BluetoothService::AdapterAddedReceived()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mAdapterAddedReceived = true;
 }
 
+/**
+ * Enable/Disable the local adapter.
+ *
+ * There is only one adapter on the mobile in current use cases.
+ * In addition, bluedroid couldn't enable/disable a single adapter.
+ * So currently we will turn on/off BT to enable/disable the adapter.
+ *
+ * TODO: To support enable/disable single adapter in the future,
+ *       we will need to implement EnableDisableInternal for different stacks.
+ */
+nsresult
+BluetoothService::EnableDisable(bool aEnable,
+                                BluetoothReplyRunnable* aRunnable)
+{
+  sToggleInProgress = true;
+  return StartStopBluetooth(aEnable, false, aRunnable);
+}
+
+void
+BluetoothService::FireAdapterStateChanged(bool aEnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  InfallibleTArray<BluetoothNamedValue> props;
+  BT_APPEND_NAMED_VALUE(props, "State", aEnable);
+  BluetoothValue value(props);
+
+  BluetoothSignal signal(NS_LITERAL_STRING("PropertyChanged"),
+                         NS_LITERAL_STRING(KEY_ADAPTER), value);
+  DistributeSignal(signal);
+}
+
 void
 BluetoothService::Notify(const BluetoothSignal& aData)
 {
   nsString type = NS_LITERAL_STRING("bluetooth-pairing-request");
 
   AutoSafeJSContext cx;
   JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(),
                                              JS::NullPtr()));
--- a/dom/bluetooth2/BluetoothService.h
+++ b/dom/bluetooth2/BluetoothService.h
@@ -310,56 +310,62 @@ public:
 
   /**
    * Below 2 function/variable are used for ensuring event 'AdapterAdded' will
    * be fired after event 'Enabled'.
    */
   void TryFiringAdapterAdded();
   void AdapterAddedReceived();
 
+  void FireAdapterStateChanged(bool aEnable);
+  nsresult EnableDisable(bool aEnable,
+                         BluetoothReplyRunnable* aRunnable);
+
+  /**
+   * Platform specific startup functions go here. Usually deals with member
+   * variables, so not static. Guaranteed to be called outside of main thread.
+   *
+   * @return NS_OK on correct startup, NS_ERROR_FAILURE otherwise
+   */
+  virtual nsresult
+  StartInternal(BluetoothReplyRunnable* aRunnable) = 0;
+
+  /**
+   * Platform specific startup functions go here. Usually deals with member
+   * variables, so not static. Guaranteed to be called outside of main thread.
+   *
+   * @return NS_OK on correct startup, NS_ERROR_FAILURE otherwise
+   */
+  virtual nsresult
+  StopInternal(BluetoothReplyRunnable* aRunnable) = 0;
+
 protected:
   BluetoothService() : mEnabled(false)
                      , mAdapterAddedReceived(false)
   {
   }
 
   virtual ~BluetoothService();
 
   bool
   Init();
 
   void
   Cleanup();
 
   nsresult
-  StartBluetooth(bool aIsStartup);
-
-  nsresult
-  StopBluetooth(bool aIsStartup);
+  StartBluetooth(bool aIsStartup, BluetoothReplyRunnable* aRunnable);
 
   nsresult
-  StartStopBluetooth(bool aStart, bool aIsStartup);
+  StopBluetooth(bool aIsStartup, BluetoothReplyRunnable* aRunnable);
 
-  /**
-   * Platform specific startup functions go here. Usually deals with member
-   * variables, so not static. Guaranteed to be called outside of main thread.
-   *
-   * @return NS_OK on correct startup, NS_ERROR_FAILURE otherwise
-   */
-  virtual nsresult
-  StartInternal() = 0;
-
-  /**
-   * Platform specific startup functions go here. Usually deals with member
-   * variables, so not static. Guaranteed to be called outside of main thread.
-   *
-   * @return NS_OK on correct startup, NS_ERROR_FAILURE otherwise
-   */
-  virtual nsresult
-  StopInternal() = 0;
+  nsresult
+  StartStopBluetooth(bool aStart,
+                     bool aIsStartup,
+                     BluetoothReplyRunnable* aRunnable);
 
   /**
    * Called when XPCOM first creates this service.
    */
   virtual nsresult
   HandleStartup();
 
   /**
--- a/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp
@@ -28,44 +28,57 @@
 #include "BluetoothUtils.h"
 #include "BluetoothUuid.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/ipc/UnixSocket.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/unused.h"
 
+#define ENSURE_BLUETOOTH_IS_READY(runnable, result)                    \
+  do {                                                                 \
+    if (!sBtInterface || !IsEnabled()) {                               \
+      NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth is not ready");     \
+      DispatchBluetoothReply(runnable, BluetoothValue(), errorStr);    \
+      return result;                                                   \
+    }                                                                  \
+  } while(0)
+
 using namespace mozilla;
 using namespace mozilla::ipc;
 USING_BLUETOOTH_NAMESPACE
 
-/**
- *  Static variables
- */
-static bluetooth_device_t* sBtDevice;
-static const bt_interface_t* sBtInterface;
-static bool sAdapterDiscoverable = false;
-static bool sIsBtEnabled = false;
+// TODO: Non thread-safe static variables
 static nsString sAdapterBdAddress;
 static nsString sAdapterBdName;
-static uint32_t sAdapterDiscoverableTimeout;
 static InfallibleTArray<nsString> sAdapterBondedAddressArray;
-static InfallibleTArray<BluetoothNamedValue> sRemoteDevicesPack;
+static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sChangeAdapterStateRunnableArray;
+
+// Static variables below should only be used on *main thread*
+static const bt_interface_t* sBtInterface;
 static nsTArray<nsRefPtr<BluetoothProfileController> > sControllerArray;
+static nsTArray<int> sRequestedDeviceCountArray;
+static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sChangeDiscoveryRunnableArray;
+static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sSetPropertyRunnableArray;
+static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sGetDeviceRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sBondingRunnableArray;
-static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sChangeDiscoveryRunnableArray;
-static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sGetDeviceRunnableArray;
-static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sSetPropertyRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sUnbondingRunnableArray;
-static nsTArray<int> sRequestedDeviceCountArray;
+
+// Static variables below should only be used on *callback thread*
+
+
+// Atomic static variables
+static Atomic<bool> sAdapterDiscoverable(false);
+static Atomic<uint32_t> sAdapterDiscoverableTimeout(0);
 
 /**
  *  Classes only used in this file
  */
-class DistributeBluetoothSignalTask : public nsRunnable {
+class DistributeBluetoothSignalTask MOZ_FINAL : public nsRunnable
+{
 public:
   DistributeBluetoothSignalTask(const BluetoothSignal& aSignal) :
     mSignal(aSignal)
   {
   }
 
   NS_IMETHOD
   Run()
@@ -79,22 +92,19 @@ public:
 
     return NS_OK;
   }
 
 private:
   BluetoothSignal mSignal;
 };
 
-class SetupAfterEnabledTask : public nsRunnable
+class SetupAfterEnabledTask MOZ_FINAL : public nsRunnable
 {
 public:
-  SetupAfterEnabledTask()
-  { }
-
   NS_IMETHOD
   Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     // Bluetooth just enabled, clear profile controllers and runnable arrays.
     sControllerArray.Clear();
     sBondingRunnableArray.Clear();
@@ -130,27 +140,47 @@ public:
     if (!opp || !opp->Listen()) {
       BT_LOGR("Fail to start BluetoothOppManager listening");
     }
 
     return NS_OK;
   }
 };
 
-class CleanupTask : public nsRunnable
+class CleanupTask MOZ_FINAL : public nsRunnable
 {
 public:
-  CleanupTask()
-  { }
-
   NS_IMETHOD
   Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
+    /*
+     * Cleanup static adapter properties and notify adapter to clean them
+     *
+     * TODO: clean up and notify Discovering also
+     */
+    sAdapterBdAddress.Truncate();
+    sAdapterBdName.Truncate();
+    sAdapterDiscoverable = false;
+
+    InfallibleTArray<BluetoothNamedValue> props;
+    BT_APPEND_NAMED_VALUE(props, "Name", sAdapterBdName);
+    BT_APPEND_NAMED_VALUE(props, "Address", sAdapterBdAddress);
+    BT_APPEND_NAMED_VALUE(props, "Discoverable",
+                          BluetoothValue(sAdapterDiscoverable));
+    BluetoothValue value(props);
+    BluetoothSignal signal(NS_LITERAL_STRING("PropertyChanged"),
+                           NS_LITERAL_STRING(KEY_ADAPTER), value);
+
+    BluetoothService* bs = BluetoothService::Get();
+    NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
+
+    bs->DistributeSignal(signal);
+
     // Cleanup bluetooth interfaces after BT state becomes BT_STATE_OFF.
     BluetoothHfpManager::DeinitHfpInterface();
     BluetoothA2dpManager::DeinitA2dpInterface();
     sBtInterface->cleanup();
 
     return NS_OK;
   }
 };
@@ -265,55 +295,79 @@ PlayStatusStringToControlPlayStatus(cons
     playStatus = ControlPlayStatus::PLAYSTATUS_REV_SEEK;
   } else if (aPlayStatus.EqualsLiteral("ERROR")) {
     playStatus = ControlPlayStatus::PLAYSTATUS_ERROR;
   }
 
   return playStatus;
 }
 
-static bool
-IsReady()
-{
-  if (!sBtInterface || !sIsBtEnabled) {
-    BT_LOGR("Warning! Bluetooth Service is not ready");
-    return false;
-  }
-  return true;
-}
-
+/**
+ *  Bluedroid HAL callback functions
+ *
+ *  Several callbacks are dispatched to main thread to avoid racing issues.
+ */
 static void
 AdapterStateChangeCallback(bt_state_t aStatus)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
-  BT_LOGR("BT_STATE %d", aStatus);
+  BT_LOGR("BT_STATE: %d", aStatus);
+
+  bool isBtEnabled = (aStatus == BT_STATE_ON);
 
-  sIsBtEnabled = (aStatus == BT_STATE_ON);
-
-  if (!sIsBtEnabled && NS_FAILED(NS_DispatchToMainThread(new CleanupTask()))) {
+  if (!isBtEnabled &&
+      NS_FAILED(NS_DispatchToMainThread(new CleanupTask()))) {
     BT_WARNING("Failed to dispatch to main thread!");
+    return;
   }
 
   nsRefPtr<nsRunnable> runnable =
-    new BluetoothService::ToggleBtAck(sIsBtEnabled);
+    new BluetoothService::ToggleBtAck(isBtEnabled);
   if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
     BT_WARNING("Failed to dispatch to main thread!");
     return;
   }
 
-  if (sIsBtEnabled &&
+  if (isBtEnabled &&
       NS_FAILED(NS_DispatchToMainThread(new SetupAfterEnabledTask()))) {
     BT_WARNING("Failed to dispatch to main thread!");
+    return;
+  }
+
+  // Resolve promise if existed
+  if(!sChangeAdapterStateRunnableArray.IsEmpty()) {
+    DispatchBluetoothReply(sChangeAdapterStateRunnableArray[0],
+                           BluetoothValue(true),
+                           EmptyString());
+    sChangeAdapterStateRunnableArray.RemoveElementAt(0);
   }
 }
 
+class AdapterPropertiesCallbackTask MOZ_FINAL : public nsRunnable
+{
+public:
+  NS_IMETHOD
+  Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (!sSetPropertyRunnableArray.IsEmpty()) {
+      DispatchBluetoothReply(sSetPropertyRunnableArray[0],
+                             BluetoothValue(true), EmptyString());
+      sSetPropertyRunnableArray.RemoveElementAt(0);
+    }
+
+    return NS_OK;
+  }
+};
+
 /**
  * AdapterPropertiesCallback will be called after enable() but before
- * AdapterStateChangeCallback sIsBtEnabled get updated. At that moment, both
+ * AdapterStateChangeCallback is called. At that moment, both
  * BluetoothManager/BluetoothAdapter does not register observer yet.
  */
 static void
 AdapterPropertiesCallback(bt_status_t aStatus, int aNumProperties,
                           bt_property_t *aProperties)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
@@ -380,42 +434,85 @@ AdapterPropertiesCallback(bt_status_t aS
   BluetoothSignal signal(NS_LITERAL_STRING("PropertyChanged"),
                          NS_LITERAL_STRING(KEY_ADAPTER), value);
   nsRefPtr<DistributeBluetoothSignalTask>
     t = new DistributeBluetoothSignalTask(signal);
   if (NS_FAILED(NS_DispatchToMainThread(t))) {
     BT_WARNING("Failed to dispatch to main thread!");
   }
 
-  // bluedroid BTU task was stored in the task queue, see GKI_send_msg
-  if (!sSetPropertyRunnableArray.IsEmpty()) {
-    DispatchBluetoothReply(sSetPropertyRunnableArray[0], BluetoothValue(true),
-                           EmptyString());
-    sSetPropertyRunnableArray.RemoveElementAt(0);
+  // Redirect to main thread to avoid racing problem
+  NS_DispatchToMainThread(new AdapterPropertiesCallbackTask());
+}
+
+class RemoteDevicePropertiesCallbackTask : public nsRunnable
+{
+  const InfallibleTArray<BluetoothNamedValue> mProps;
+  nsString mRemoteDeviceBdAddress;
+public:
+  RemoteDevicePropertiesCallbackTask(
+    const InfallibleTArray<BluetoothNamedValue>& aProps,
+    const nsAString& aRemoteDeviceBdAddress)
+  : mProps(aProps)
+  , mRemoteDeviceBdAddress(aRemoteDeviceBdAddress)
+  { }
+
+  NS_IMETHOD
+  Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (sRequestedDeviceCountArray.IsEmpty()) {
+      // This is possible because the callback would be called after turning
+      // Bluetooth on.
+      return NS_OK;
+    }
+
+    // Update to registered BluetoothDevice objects
+    BluetoothSignal signal(NS_LITERAL_STRING("PropertyChanged"),
+                           mRemoteDeviceBdAddress, mProps);
+    nsRefPtr<DistributeBluetoothSignalTask>
+      t = new DistributeBluetoothSignalTask(signal);
+    if (NS_FAILED(NS_DispatchToMainThread(t))) {
+      BT_WARNING("Failed to dispatch to main thread!");
+      return NS_OK;
+    }
+
+    static InfallibleTArray<BluetoothNamedValue> sRemoteDevicesPack;
+
+    // Use address as the index
+    sRemoteDevicesPack.AppendElement(
+      BluetoothNamedValue(mRemoteDeviceBdAddress, mProps));
+
+    if (--sRequestedDeviceCountArray[0] == 0) {
+      if (!sGetDeviceRunnableArray.IsEmpty()) {
+        DispatchBluetoothReply(sGetDeviceRunnableArray[0],
+                               sRemoteDevicesPack, EmptyString());
+        sGetDeviceRunnableArray.RemoveElementAt(0);
+      }
+
+      sRequestedDeviceCountArray.RemoveElementAt(0);
+      sRemoteDevicesPack.Clear();
+    }
+
+    return NS_OK;
   }
-}
+};
 
 /**
  * RemoteDevicePropertiesCallback will be called, as the following conditions:
  * 1. When BT is turning on, bluedroid automatically execute this callback
  * 2. When get_remote_device_properties()
  */
 static void
 RemoteDevicePropertiesCallback(bt_status_t aStatus, bt_bdaddr_t *aBdAddress,
                                int aNumProperties, bt_property_t *aProperties)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
-  if (sRequestedDeviceCountArray.IsEmpty()) {
-    MOZ_ASSERT(sGetDeviceRunnableArray.IsEmpty());
-    return;
-  }
-
-  sRequestedDeviceCountArray[0]--;
-
   InfallibleTArray<BluetoothNamedValue> props;
 
   nsString remoteDeviceBdAddress;
   BdAddressTypeToString(aBdAddress, remoteDeviceBdAddress);
   BT_APPEND_NAMED_VALUE(props, "Address", remoteDeviceBdAddress);
 
   for (int i = 0; i < aNumProperties; ++i) {
     bt_property_t p = aProperties[i];
@@ -430,46 +527,19 @@ RemoteDevicePropertiesCallback(bt_status
       nsString icon;
       ClassToIcon(cod, icon);
       BT_APPEND_NAMED_VALUE(props, "Icon", icon);
     } else {
       BT_LOGD("Other non-handled device properties. Type: %d", p.type);
     }
   }
 
-  // Update to registered BluetoothDevice objects
-  BluetoothSignal signal(NS_LITERAL_STRING("PropertyChanged"),
-                         remoteDeviceBdAddress, props);
-  nsRefPtr<DistributeBluetoothSignalTask>
-    t = new DistributeBluetoothSignalTask(signal);
-  if (NS_FAILED(NS_DispatchToMainThread(t))) {
-    BT_WARNING("Failed to dispatch to main thread!");
-  }
-
-  // Use address as the index
-  sRemoteDevicesPack.AppendElement(
-    BluetoothNamedValue(remoteDeviceBdAddress, props));
-
-  if (sRequestedDeviceCountArray[0] == 0) {
-    MOZ_ASSERT(!sGetDeviceRunnableArray.IsEmpty());
-
-    if (sGetDeviceRunnableArray.IsEmpty()) {
-      BT_LOGR("No runnable to return");
-      return;
-    }
-
-    DispatchBluetoothReply(sGetDeviceRunnableArray[0],
-                           sRemoteDevicesPack, EmptyString());
-
-    // After firing it, clean up cache
-    sRemoteDevicesPack.Clear();
-
-    sRequestedDeviceCountArray.RemoveElementAt(0);
-    sGetDeviceRunnableArray.RemoveElementAt(0);
-  }
+  // Redirect to main thread to avoid racing problem
+  NS_DispatchToMainThread(
+    new RemoteDevicePropertiesCallbackTask(props, remoteDeviceBdAddress));
 }
 
 static void
 DeviceFoundCallback(int aNumProperties, bt_property_t *aProperties)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
   BluetoothValue propertyValue;
@@ -506,28 +576,43 @@ DeviceFoundCallback(int aNumProperties, 
                          NS_LITERAL_STRING(KEY_ADAPTER), value);
   nsRefPtr<DistributeBluetoothSignalTask>
     t = new DistributeBluetoothSignalTask(signal);
   if (NS_FAILED(NS_DispatchToMainThread(t))) {
     BT_WARNING("Failed to dispatch to main thread!");
   }
 }
 
+class DiscoveryStateChangedCallbackTask MOZ_FINAL : public nsRunnable
+{
+public:
+  NS_IMETHOD
+  Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (!sChangeDiscoveryRunnableArray.IsEmpty()) {
+      BluetoothValue values(true);
+      DispatchBluetoothReply(sChangeDiscoveryRunnableArray[0],
+                             values, EmptyString());
+
+      sChangeDiscoveryRunnableArray.RemoveElementAt(0);
+    }
+
+    return NS_OK;
+  }
+};
+
 static void
 DiscoveryStateChangedCallback(bt_discovery_state_t aState)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
-  if (!sChangeDiscoveryRunnableArray.IsEmpty()) {
-    BluetoothValue values(true);
-    DispatchBluetoothReply(sChangeDiscoveryRunnableArray[0],
-                           values, EmptyString());
-
-    sChangeDiscoveryRunnableArray.RemoveElementAt(0);
-  }
+  // Redirect to main thread to avoid racing problem
+  NS_DispatchToMainThread(new DiscoveryStateChangedCallbackTask());
 }
 
 static void
 PinRequestCallback(bt_bdaddr_t* aRemoteBdAddress,
                    bt_bdname_t* aRemoteBdName, uint32_t aRemoteClass)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
@@ -576,70 +661,101 @@ SspRequestCallback(bt_bdaddr_t* aRemoteB
                          NS_LITERAL_STRING(KEY_LOCAL_AGENT), value);
   nsRefPtr<DistributeBluetoothSignalTask>
     t = new DistributeBluetoothSignalTask(signal);
   if (NS_FAILED(NS_DispatchToMainThread(t))) {
     BT_WARNING("Failed to dispatch to main thread!");
   }
 }
 
+class BondStateChangedCallbackTask : public nsRunnable
+{
+  nsString mRemoteDeviceBdAddress;
+  bool mBonded;
+public:
+  BondStateChangedCallbackTask(const nsAString& aRemoteDeviceBdAddress,
+                               bool aBonded)
+  : mRemoteDeviceBdAddress(aRemoteDeviceBdAddress)
+  , mBonded(aBonded)
+  { }
+
+  NS_IMETHOD
+  Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (mBonded && !sBondingRunnableArray.IsEmpty()) {
+      DispatchBluetoothReply(sBondingRunnableArray[0],
+                             BluetoothValue(true), EmptyString());
+
+      sBondingRunnableArray.RemoveElementAt(0);
+    } else if (!mBonded && !sUnbondingRunnableArray.IsEmpty()) {
+      DispatchBluetoothReply(sUnbondingRunnableArray[0],
+                             BluetoothValue(true), EmptyString());
+
+      sUnbondingRunnableArray.RemoveElementAt(0);
+    }
+
+    // Update bonding status to gaia
+    InfallibleTArray<BluetoothNamedValue> propertiesArray;
+    BT_APPEND_NAMED_VALUE(propertiesArray, "address", mRemoteDeviceBdAddress);
+    BT_APPEND_NAMED_VALUE(propertiesArray, "status", mBonded);
+
+    BluetoothSignal signal(NS_LITERAL_STRING(PAIRED_STATUS_CHANGED_ID),
+                           NS_LITERAL_STRING(KEY_ADAPTER),
+                           BluetoothValue(propertiesArray));
+    NS_DispatchToMainThread(new DistributeBluetoothSignalTask(signal));
+
+    return NS_OK;
+  }
+};
+
 static void
 BondStateChangedCallback(bt_status_t aStatus, bt_bdaddr_t* aRemoteBdAddress,
                          bt_bond_state_t aState)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
-  nsAutoString remoteAddress;
-  BdAddressTypeToString(aRemoteBdAddress, remoteAddress);
+  if (aState == BT_BOND_STATE_BONDING) {
+    // No need to handle bonding state
+    return;
+  }
 
-  // We don't need to handle bonding state
-  NS_ENSURE_TRUE_VOID(aState != BT_BOND_STATE_BONDING);
-  NS_ENSURE_FALSE_VOID(aState == BT_BOND_STATE_BONDED &&
-                       sAdapterBondedAddressArray.Contains(remoteAddress));
+  nsAutoString remoteBdAddress;
+  BdAddressTypeToString(aRemoteBdAddress, remoteBdAddress);
+
+  if (aState == BT_BOND_STATE_BONDED &&
+      sAdapterBondedAddressArray.Contains(remoteBdAddress)) {
+    // See bug 940271 for more details about this case.
+    return;
+  }
+
   bool bonded;
   if (aState == BT_BOND_STATE_NONE) {
     bonded = false;
-    sAdapterBondedAddressArray.RemoveElement(remoteAddress);
+    sAdapterBondedAddressArray.RemoveElement(remoteBdAddress);
   } else if (aState == BT_BOND_STATE_BONDED) {
     bonded = true;
-    sAdapterBondedAddressArray.AppendElement(remoteAddress);
+    sAdapterBondedAddressArray.AppendElement(remoteBdAddress);
   }
 
   // Update bonded address list to BluetoothAdapter
   InfallibleTArray<BluetoothNamedValue> propertiesChangeArray;
   BT_APPEND_NAMED_VALUE(propertiesChangeArray, "Devices",
                         sAdapterBondedAddressArray);
 
   BluetoothValue value(propertiesChangeArray);
   BluetoothSignal signal(NS_LITERAL_STRING("PropertyChanged"),
                          NS_LITERAL_STRING(KEY_ADAPTER),
                          BluetoothValue(propertiesChangeArray));
   NS_DispatchToMainThread(new DistributeBluetoothSignalTask(signal));
 
-  // Update bonding status to gaia
-  InfallibleTArray<BluetoothNamedValue> propertiesArray;
-  BT_APPEND_NAMED_VALUE(propertiesArray, "address", remoteAddress);
-  BT_APPEND_NAMED_VALUE(propertiesArray, "status", bonded);
-
-  BluetoothSignal newSignal(NS_LITERAL_STRING(PAIRED_STATUS_CHANGED_ID),
-                            NS_LITERAL_STRING(KEY_ADAPTER),
-                            BluetoothValue(propertiesArray));
-  NS_DispatchToMainThread(new DistributeBluetoothSignalTask(newSignal));
-
-  if (bonded && !sBondingRunnableArray.IsEmpty()) {
-    DispatchBluetoothReply(sBondingRunnableArray[0],
-                           BluetoothValue(true), EmptyString());
-
-    sBondingRunnableArray.RemoveElementAt(0);
-  } else if (!bonded && !sUnbondingRunnableArray.IsEmpty()) {
-    DispatchBluetoothReply(sUnbondingRunnableArray[0],
-                           BluetoothValue(true), EmptyString());
-
-    sUnbondingRunnableArray.RemoveElementAt(0);
-  }
+  // Redirect to main thread to avoid racing problem
+  NS_DispatchToMainThread(
+    new BondStateChangedCallbackTask(remoteBdAddress, bonded));
 }
 
 static void
 AclStateChangedCallback(bt_status_t aStatus, bt_bdaddr_t* aRemoteBdAddress,
                         bt_acl_state_t aState)
 {
   //FIXME: This will be implemented in the later patchset
 }
@@ -668,25 +784,27 @@ bt_callbacks_t sBluetoothCallbacks =
 /**
  *  Static functions
  */
 static bool
 EnsureBluetoothHalLoad()
 {
   hw_module_t* module;
   hw_device_t* device;
+
   int err = hw_get_module(BT_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
   if (err != 0) {
     BT_LOGR("Error: %s", strerror(err));
     return false;
   }
   module->methods->open(module, BT_HARDWARE_MODULE_ID, &device);
-  sBtDevice = (bluetooth_device_t *)device;
-  NS_ENSURE_TRUE(sBtDevice, false);
-  sBtInterface = sBtDevice->get_bluetooth_interface();
+  bluetooth_device_t* btDevice = (bluetooth_device_t *)device;
+  NS_ENSURE_TRUE(btDevice, false);
+
+  sBtInterface = btDevice->get_bluetooth_interface();
   NS_ENSURE_TRUE(sBtInterface, false);
 
   return true;
 }
 
 static bool
 EnableInternal()
 {
@@ -704,22 +822,26 @@ EnableInternal()
   BluetoothA2dpManager::InitA2dpInterface();
   return sBtInterface->enable();
 }
 
 static nsresult
 StartStopGonkBluetooth(bool aShouldEnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
+
   NS_ENSURE_TRUE(sBtInterface, NS_ERROR_FAILURE);
 
-  if (sIsBtEnabled == aShouldEnable) {
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
+
+  if (bs->IsEnabled() == aShouldEnable) {
     // Keep current enable status
     nsRefPtr<nsRunnable> runnable =
-      new BluetoothService::ToggleBtAck(sIsBtEnabled);
+      new BluetoothService::ToggleBtAck(aShouldEnable);
     if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
       BT_WARNING("Failed to dispatch to main thread!");
     }
     return NS_OK;
   }
 
   int ret = aShouldEnable ? EnableInternal() : sBtInterface->disable();
   NS_ENSURE_TRUE(ret == BT_STATUS_SUCCESS, NS_ERROR_FAILURE);
@@ -767,38 +889,48 @@ BluetoothServiceBluedroid::BluetoothServ
   }
 }
 
 BluetoothServiceBluedroid::~BluetoothServiceBluedroid()
 {
 }
 
 nsresult
-BluetoothServiceBluedroid::StartInternal()
+BluetoothServiceBluedroid::StartInternal(BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  // aRunnable will be a nullptr while startup
+  if(aRunnable) {
+    sChangeAdapterStateRunnableArray.AppendElement(aRunnable);
+  }
+
   nsresult ret = StartStopGonkBluetooth(true);
   if (NS_FAILED(ret)) {
     nsRefPtr<nsRunnable> runnable =
       new BluetoothService::ToggleBtAck(false);
     if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
       BT_WARNING("Failed to dispatch to main thread!");
     }
     BT_LOGR("Error");
   }
 
   return ret;
 }
 
 nsresult
-BluetoothServiceBluedroid::StopInternal()
+BluetoothServiceBluedroid::StopInternal(BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  // aRunnable will be a nullptr during starup and shutdown
+  if(aRunnable) {
+    sChangeAdapterStateRunnableArray.AppendElement(aRunnable);
+  }
+
   nsresult ret = StartStopGonkBluetooth(false);
   if (NS_FAILED(ret)) {
     nsRefPtr<nsRunnable> runnable =
       new BluetoothService::ToggleBtAck(true);
     if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
       BT_WARNING("Failed to dispatch to main thread!");
     }
     BT_LOGR("Error");
@@ -823,27 +955,32 @@ BluetoothServiceBluedroid::GetAdaptersIn
    *     |__ BluetoothNamedValue =
    *     |     {"Adapter", BluetoothValue = BluetoothNamedValue[]}
    *     ...
    */
   BluetoothValue adaptersProperties = InfallibleTArray<BluetoothNamedValue>();
   uint32_t numAdapters = 1; // Bluedroid supports single adapter only
 
   for (uint32_t i = 0; i < numAdapters; i++) {
+    // Since Atomic<*> is not acceptable for BT_APPEND_NAMED_VALUE(),
+    // create another variable to store data.
+    bool discoverable = sAdapterDiscoverable;
+    uint32_t discoverableTimeout = sAdapterDiscoverableTimeout;
+
     BluetoothValue properties = InfallibleTArray<BluetoothNamedValue>();
 
     // TODO: Revise here based on new BluetoothAdapter interface
     BT_APPEND_NAMED_VALUE(properties.get_ArrayOfBluetoothNamedValue(),
                           "Address", sAdapterBdAddress);
     BT_APPEND_NAMED_VALUE(properties.get_ArrayOfBluetoothNamedValue(),
                           "Name", sAdapterBdName);
     BT_APPEND_NAMED_VALUE(properties.get_ArrayOfBluetoothNamedValue(),
-                          "Discoverable", sAdapterDiscoverable);
+                          "Discoverable", discoverable);
     BT_APPEND_NAMED_VALUE(properties.get_ArrayOfBluetoothNamedValue(),
-                          "DiscoverableTimeout", sAdapterDiscoverableTimeout);
+                          "DiscoverableTimeout", discoverableTimeout);
     BT_APPEND_NAMED_VALUE(properties.get_ArrayOfBluetoothNamedValue(),
                           "Devices", sAdapterBondedAddressArray);
 
     BT_APPEND_NAMED_VALUE(adaptersProperties.get_ArrayOfBluetoothNamedValue(),
                           "Adapter", properties);
   }
 
   DispatchBluetoothReply(aRunnable, adaptersProperties, EmptyString());
@@ -851,21 +988,17 @@ BluetoothServiceBluedroid::GetAdaptersIn
 }
 
 nsresult
 BluetoothServiceBluedroid::GetConnectedDevicePropertiesInternal(
   uint16_t aServiceUuid, BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!IsReady()) {
-    NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
-    DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
-    return NS_OK;
-  }
+  ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
 
   BluetoothProfileManagerBase* profile =
     BluetoothUuidHelper::GetBluetoothProfileManager(aServiceUuid);
   if (!profile) {
     InfallibleTArray<BluetoothNamedValue> emptyArr;
     DispatchBluetoothReply(aRunnable, emptyArr,
                            NS_LITERAL_STRING(ERR_UNKNOWN_PROFILE));
     return NS_OK;
@@ -905,21 +1038,17 @@ BluetoothServiceBluedroid::GetConnectedD
 }
 
 nsresult
 BluetoothServiceBluedroid::GetPairedDevicePropertiesInternal(
   const nsTArray<nsString>& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!IsReady()) {
-    NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
-    DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
-    return NS_OK;
-  }
+  ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
 
   int requestedDeviceCount = aDeviceAddress.Length();
   if (requestedDeviceCount == 0) {
     InfallibleTArray<BluetoothNamedValue> emptyArr;
     DispatchBluetoothReply(aRunnable, BluetoothValue(emptyArr), EmptyString());
     return NS_OK;
   }
 
@@ -942,22 +1071,18 @@ BluetoothServiceBluedroid::GetPairedDevi
 }
 
 nsresult
 BluetoothServiceBluedroid::StartDiscoveryInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!IsReady()) {
-    NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
-    DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
+  ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
 
-    return NS_OK;
-  }
   int ret = sBtInterface->start_discovery();
   if (ret != BT_STATUS_SUCCESS) {
     ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("StartDiscovery"));
 
     return NS_OK;
   }
 
   sChangeDiscoveryRunnableArray.AppendElement(aRunnable);
@@ -965,21 +1090,17 @@ BluetoothServiceBluedroid::StartDiscover
 }
 
 nsresult
 BluetoothServiceBluedroid::StopDiscoveryInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!IsReady()) {
-    NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
-    DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
-    return NS_OK;
-  }
+  ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
 
   int ret = sBtInterface->cancel_discovery();
   if (ret != BT_STATUS_SUCCESS) {
     ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("StopDiscovery"));
     return NS_OK;
   }
 
   sChangeDiscoveryRunnableArray.AppendElement(aRunnable);
@@ -989,22 +1110,17 @@ BluetoothServiceBluedroid::StopDiscovery
 
 nsresult
 BluetoothServiceBluedroid::SetProperty(BluetoothObjectType aType,
                                        const BluetoothNamedValue& aValue,
                                        BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!IsReady()) {
-    NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
-    DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
-
-    return NS_OK;
-  }
+  ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
 
   const nsString propName = aValue.name();
   bt_property_t prop;
   bt_scan_mode_t scanMode;
   nsCString str;
 
   // For Bluedroid, it's necessary to check property name for SetProperty
   if (propName.EqualsLiteral("Name")) {
@@ -1067,21 +1183,17 @@ BluetoothServiceBluedroid::UpdateSdpReco
 
 nsresult
 BluetoothServiceBluedroid::CreatePairedDeviceInternal(
   const nsAString& aDeviceAddress, int aTimeout,
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!IsReady()) {
-    NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
-    DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
-    return NS_OK;
-  }
+  ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
 
   bt_bdaddr_t remoteAddress;
   StringToBdAddressType(aDeviceAddress, &remoteAddress);
 
   int ret = sBtInterface->create_bond(&remoteAddress);
   if (ret != BT_STATUS_SUCCESS) {
     ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("CreatedPairedDevice"));
   } else {
@@ -1092,21 +1204,17 @@ BluetoothServiceBluedroid::CreatePairedD
 }
 
 nsresult
 BluetoothServiceBluedroid::RemoveDeviceInternal(
   const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!IsReady()) {
-    NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
-    DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
-    return NS_OK;
-  }
+  ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
 
   bt_bdaddr_t remoteAddress;
   StringToBdAddressType(aDeviceAddress, &remoteAddress);
 
   int ret = sBtInterface->remove_bond(&remoteAddress);
   if (ret != BT_STATUS_SUCCESS) {
     ReplyStatusError(aRunnable, ret,
                      NS_LITERAL_STRING("RemoveDevice"));
@@ -1119,21 +1227,17 @@ BluetoothServiceBluedroid::RemoveDeviceI
 
 bool
 BluetoothServiceBluedroid::SetPinCodeInternal(
   const nsAString& aDeviceAddress, const nsAString& aPinCode,
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!IsReady()) {
-    NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
-    DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
-    return false;
-  }
+  ENSURE_BLUETOOTH_IS_READY(aRunnable, false);
 
   bt_bdaddr_t remoteAddress;
   StringToBdAddressType(aDeviceAddress, &remoteAddress);
 
   int ret = sBtInterface->pin_reply(
       &remoteAddress, true, aPinCode.Length(),
       (bt_pin_code_t*)NS_ConvertUTF16toUTF8(aPinCode).get());
 
@@ -1156,21 +1260,17 @@ BluetoothServiceBluedroid::SetPasskeyInt
 
 bool
 BluetoothServiceBluedroid::SetPairingConfirmationInternal(
   const nsAString& aDeviceAddress, bool aConfirm,
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!IsReady()) {
-    NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
-    DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
-    return false;
-  }
+  ENSURE_BLUETOOTH_IS_READY(aRunnable, false);
 
   bt_bdaddr_t remoteAddress;
   StringToBdAddressType(aDeviceAddress, &remoteAddress);
 
   int ret = sBtInterface->ssp_reply(&remoteAddress, (bt_ssp_variant_t)0,
                                     aConfirm, 0);
   if (ret != BT_STATUS_SUCCESS) {
     ReplyStatusError(aRunnable, ret,
--- a/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.h
+++ b/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.h
@@ -17,18 +17,18 @@ BEGIN_BLUETOOTH_NAMESPACE
 class BluetoothServiceBluedroid : public BluetoothService
 {
 public:
   static const bt_interface_t* GetBluetoothInterface();
 
   BluetoothServiceBluedroid();
   ~BluetoothServiceBluedroid();
 
-  virtual nsresult StartInternal();
-  virtual nsresult StopInternal();
+  virtual nsresult StartInternal(BluetoothReplyRunnable* aRunnable);
+  virtual nsresult StopInternal(BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   GetAdaptersInternal(BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   GetConnectedDevicePropertiesInternal(uint16_t aProfileId,
                                        BluetoothReplyRunnable* aRunnable);
 
--- a/dom/bluetooth2/bluez/BluetoothDBusService.cpp
+++ b/dom/bluetooth2/bluez/BluetoothDBusService.cpp
@@ -2092,18 +2092,20 @@ public:
     Task* task = new StartDBusConnectionTask(connection);
     DispatchToDBusThread(task);
 
     return NS_OK;
   }
 };
 
 nsresult
-BluetoothDBusService::StartInternal()
+BluetoothDBusService::StartInternal(BluetoothReplyRunnable* aRunnable)
 {
+  MOZ_ASSERT(!aRunnable);
+
   nsRefPtr<nsRunnable> runnable = new StartBluetoothRunnable();
   nsresult rv = DispatchToBtThread(runnable);
   if (NS_FAILED(rv)) {
     BT_WARNING("Failed to dispatch to BT thread!");
   }
   return rv;
 }
 
@@ -2220,18 +2222,20 @@ public:
 
     DispatchToDBusThread(new DeleteDBusConnectionTask());
 
     return NS_OK;
   }
 };
 
 nsresult
-BluetoothDBusService::StopInternal()
+BluetoothDBusService::StopInternal(BluetoothReplyRunnable* aRunnable)
 {
+  MOZ_ASSERT(!aRunnable);
+
   nsRefPtr<nsRunnable> runnable = new StopBluetoothRunnable();
   nsresult rv = DispatchToBtThread(runnable);
   if (NS_FAILED(rv)) {
     BT_WARNING("Failed to dispatch to BT thread!");
   }
   return rv;
 }
 
--- a/dom/bluetooth2/bluez/BluetoothDBusService.h
+++ b/dom/bluetooth2/bluez/BluetoothDBusService.h
@@ -42,19 +42,19 @@ public:
     EVENT_UNKNOWN
   };
 
   BluetoothDBusService();
   ~BluetoothDBusService();
 
   bool IsReady();
 
-  virtual nsresult StartInternal() MOZ_OVERRIDE;
+  virtual nsresult StartInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
-  virtual nsresult StopInternal() MOZ_OVERRIDE;
+  virtual nsresult StopInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual nsresult
   GetAdaptersInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual nsresult
   GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid,
                                        BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
--- a/dom/bluetooth2/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth2/ipc/BluetoothParent.cpp
@@ -187,16 +187,20 @@ BluetoothParent::RecvPBluetoothRequestCo
 
 #ifdef DEBUG
   actor->mRequestType = aRequest.type();
 #endif
 
   switch (aRequest.type()) {
     case Request::TGetAdaptersRequest:
       return actor->DoRequest(aRequest.get_GetAdaptersRequest());
+    case Request::TStartBluetoothRequest:
+      return actor->DoRequest(aRequest.get_StartBluetoothRequest());
+    case Request::TStopBluetoothRequest:
+      return actor->DoRequest(aRequest.get_StopBluetoothRequest());
     case Request::TSetPropertyRequest:
       return actor->DoRequest(aRequest.get_SetPropertyRequest());
     case Request::TStartDiscoveryRequest:
       return actor->DoRequest(aRequest.get_StartDiscoveryRequest());
     case Request::TStopDiscoveryRequest:
       return actor->DoRequest(aRequest.get_StopDiscoveryRequest());
     case Request::TPairRequest:
       return actor->DoRequest(aRequest.get_PairRequest());
@@ -317,16 +321,40 @@ BluetoothRequestParent::DoRequest(const 
 
   nsresult rv = mService->GetAdaptersInternal(mReplyRunnable.get());
   NS_ENSURE_SUCCESS(rv, false);
 
   return true;
 }
 
 bool
+BluetoothRequestParent::DoRequest(const StartBluetoothRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TStartBluetoothRequest);
+
+  nsresult rv = mService->StartInternal(mReplyRunnable.get());
+  NS_ENSURE_SUCCESS(rv, false);
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(const StopBluetoothRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TStopBluetoothRequest);
+
+  nsresult rv = mService->StopInternal(mReplyRunnable.get());
+  NS_ENSURE_SUCCESS(rv, false);
+
+  return true;
+}
+
+bool
 BluetoothRequestParent::DoRequest(const SetPropertyRequest& aRequest)
 {
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TSetPropertyRequest);
 
   nsresult rv =
     mService->SetProperty(aRequest.type(), aRequest.value(),
                           mReplyRunnable.get());
--- a/dom/bluetooth2/ipc/BluetoothParent.h
+++ b/dom/bluetooth2/ipc/BluetoothParent.h
@@ -124,16 +124,22 @@ protected:
 
   void
   RequestComplete();
 
   bool
   DoRequest(const GetAdaptersRequest& aRequest);
 
   bool
+  DoRequest(const StartBluetoothRequest& aRequest);
+
+  bool
+  DoRequest(const StopBluetoothRequest& aRequest);
+
+  bool
   DoRequest(const SetPropertyRequest& aRequest);
 
   bool
   DoRequest(const GetPropertyRequest& aRequest);
 
   bool
   DoRequest(const StartDiscoveryRequest& aRequest);
 
--- a/dom/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
@@ -99,16 +99,30 @@ nsresult
 BluetoothServiceChildProcess::GetAdaptersInternal(
                                               BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, GetAdaptersRequest());
   return NS_OK;
 }
 
 nsresult
+BluetoothServiceChildProcess::StartInternal(BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable, StartBluetoothRequest());
+  return NS_OK;
+}
+
+nsresult
+BluetoothServiceChildProcess::StopInternal(BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable, StopBluetoothRequest());
+  return NS_OK;
+}
+
+nsresult
 BluetoothServiceChildProcess::GetConnectedDevicePropertiesInternal(
                                               uint16_t aServiceUuid,
                                               BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, ConnectedDevicePropertiesRequest(aServiceUuid));
   return NS_OK;
 }
 nsresult
@@ -372,28 +386,16 @@ BluetoothServiceChildProcess::HandleShut
   // If this process is shutting down then we need to disconnect ourselves from
   // the parent.
   if (sBluetoothChild) {
     sBluetoothChild->BeginShutdown();
   }
   return NS_OK;
 }
 
-nsresult
-BluetoothServiceChildProcess::StartInternal()
-{
-  MOZ_CRASH("This should never be called!");
-}
-
-nsresult
-BluetoothServiceChildProcess::StopInternal()
-{
-  MOZ_CRASH("This should never be called!");
-}
-
 bool
 BluetoothServiceChildProcess::IsConnected(uint16_t aServiceUuid)
 {
   MOZ_CRASH("This should never be called!");
 }
 
 nsresult
 BluetoothServiceChildProcess::SendSinkMessage(const nsAString& aDeviceAddresses,
--- a/dom/bluetooth2/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth2/ipc/BluetoothServiceChildProcess.h
@@ -42,16 +42,22 @@ public:
   UnregisterBluetoothSignalHandler(const nsAString& aNodeName,
                                    BluetoothSignalObserver* aMsgHandler)
                                    MOZ_OVERRIDE;
 
   virtual nsresult
   GetAdaptersInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual nsresult
+  StartInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual nsresult
+  StopInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual nsresult
   GetPairedDevicePropertiesInternal(const nsTArray<nsString>& aDeviceAddresses,
                                     BluetoothReplyRunnable* aRunnable)
                                     MOZ_OVERRIDE;
 
   virtual nsresult
   GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid,
                                        BluetoothReplyRunnable* aRunnable)
                                        MOZ_OVERRIDE;
@@ -194,24 +200,16 @@ protected:
 
   virtual nsresult
   HandleStartup() MOZ_OVERRIDE;
 
   virtual nsresult
   HandleShutdown() MOZ_OVERRIDE;
 
 private:
-  // This method should never be called.
-  virtual nsresult
-  StartInternal() MOZ_OVERRIDE;
-
-  // This method should never be called.
-  virtual nsresult
-  StopInternal() MOZ_OVERRIDE;
-
   bool
   IsSignalRegistered(const nsAString& aNodeName) {
     return !!mBluetoothSignalObserverTable.Get(aNodeName);
   }
 };
 
 END_BLUETOOTH_NAMESPACE
 
--- a/dom/bluetooth2/ipc/PBluetooth.ipdl
+++ b/dom/bluetooth2/ipc/PBluetooth.ipdl
@@ -20,16 +20,24 @@ namespace bluetooth {
 
 /**
  * Bluetooth request types.
  */
 
 struct GetAdaptersRequest
 { };
 
+struct StartBluetoothRequest
+{
+};
+
+struct StopBluetoothRequest
+{
+};
+
 struct SetPropertyRequest
 {
   BluetoothObjectType type;
   BluetoothNamedValue value;
 };
 
 struct GetPropertyRequest
 {
@@ -161,16 +169,18 @@ struct SendPlayStatusRequest
   int64_t duration;
   int64_t position;
   nsString playStatus;
 };
 
 union Request
 {
   GetAdaptersRequest;
+  StartBluetoothRequest;
+  StopBluetoothRequest;
   SetPropertyRequest;
   GetPropertyRequest;
   StartDiscoveryRequest;
   StopDiscoveryRequest;
   PairRequest;
   UnpairRequest;
   SetPinCodeRequest;
   SetPasskeyRequest;
--- a/dom/cellbroadcast/interfaces/nsIDOMMozCellBroadcastMessage.idl
+++ b/dom/cellbroadcast/interfaces/nsIDOMMozCellBroadcastMessage.idl
@@ -1,21 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "domstubs.idl"
 #include "nsISupports.idl"
 
 interface nsIDOMMozCellBroadcastEtwsInfo;
 
 /**
  * MozCellBroadcastMessage encapsulates Cell Broadcast short message service
  * (CBS) messages.
  */
-[scriptable, uuid(6abe65de-6729-41f7-906a-3f3a2dbe30ae)]
+[scriptable, uuid(701e74a9-5fc4-4e2d-a324-9b7693395159)]
 interface nsIDOMMozCellBroadcastMessage : nsISupports
 {
   /**
    * Indication of the geographical area over which the Message Code is unique,
    * and the display mode.
    *
    * Possible values are: "cell-immediate", "plmn", "location-area" and "cell".
    */
@@ -48,17 +49,17 @@ interface nsIDOMMozCellBroadcastMessage 
    * Possible values are "normal", "class-0", "class-1", "class-2", "class-3",
    * "user-1", and "user-2".
    */
   readonly attribute DOMString messageClass;
 
   /**
    * System time stamp at receival.
    */
-  readonly attribute jsval timestamp; // jsval is for Date.
+  readonly attribute DOMTimeStamp timestamp;
 
   /**
    * Additional ETWS-specific info.
    */
   readonly attribute nsIDOMMozCellBroadcastEtwsInfo etws;
 
   /**
    * Service Category.
--- a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_etws.js
+++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_etws.js
@@ -191,17 +191,17 @@ function testReceiving_ETWS_MessageId() 
 function testReceiving_ETWS_Timestamp() {
   log("Test receiving ETWS Primary Notification - Timestamp");
 
   // Here we use a simple ETWS message for test.
   let pdu = buildHexStr(0, 12); // 6 octets
   doTestHelper(pdu, testReceiving_ETWS_WarningType, function(message) {
     // Cell Broadcast messages do not contain a timestamp field (however, ETWS
     // does). We only check the timestamp doesn't go too far (60 seconds) here.
-    let msMessage = message.timestamp.getTime();
+    let msMessage = message.timestamp;
     let msNow = Date.now();
     ok(Math.abs(msMessage - msNow) < (1000 * 60), "message.timestamp");
   });
 }
 
 function testReceiving_ETWS_WarningType() {
   log("Test receiving ETWS Primary Notification - Warning Type");
 
--- a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm.js
+++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm.js
@@ -356,17 +356,17 @@ function testReceiving_GSM_Language_and_
 
 function testReceiving_GSM_Timestamp() {
   log("Test receiving GSM Cell Broadcast - Timestamp");
 
   let pdu = buildHexStr(0, CB_MESSAGE_SIZE_GSM * 2);
   doTestHelper(pdu, testReceiving_GSM_WarningType, function(message) {
     // Cell Broadcast messages do not contain a timestamp field (however, ETWS
     // does). We only check the timestamp doesn't go too far (60 seconds) here.
-    let msMessage = message.timestamp.getTime();
+    let msMessage = message.timestamp;
     let msNow = Date.now();
     ok(Math.abs(msMessage - msNow) < (1000 * 60), "message.timestamp");
   });
 }
 
 function testReceiving_GSM_WarningType() {
   log("Test receiving GSM Cell Broadcast - Warning Type");
 
--- a/dom/mobilemessage/moz.build
+++ b/dom/mobilemessage/moz.build
@@ -2,10 +2,8 @@
 # vim: set filetype=python:
 # 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/.
 
 DIRS += ['interfaces', 'src']
 TEST_DIRS += ['tests']
 
-if CONFIG['ENABLE_TESTS']:
-    XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']
rename from dom/mobilemessage/tests/mochitest.ini
rename to dom/mobilemessage/tests/mochitest/mochitest.ini
rename from dom/mobilemessage/tests/test_sms_basics.html
rename to dom/mobilemessage/tests/mochitest/test_sms_basics.html
rename from dom/mobilemessage/tests/test_smsfilter.html
rename to dom/mobilemessage/tests/mochitest/test_smsfilter.html
--- a/dom/mobilemessage/tests/moz.build
+++ b/dom/mobilemessage/tests/moz.build
@@ -1,8 +1,11 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-MOCHITEST_MANIFESTS += ['mochitest.ini']
+MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini']
 
+if CONFIG['ENABLE_TESTS']:
+        XPCSHELL_TESTS_MANIFESTS += ['xpcshell/xpcshell.ini']
+
rename from dom/mobilemessage/tests/header_helpers.js
rename to dom/mobilemessage/tests/xpcshell/header_helpers.js
rename from dom/mobilemessage/tests/test_mms_pdu_helper.js
rename to dom/mobilemessage/tests/xpcshell/test_mms_pdu_helper.js
rename from dom/mobilemessage/tests/test_mms_service.js
rename to dom/mobilemessage/tests/xpcshell/test_mms_service.js
rename from dom/mobilemessage/tests/test_smsservice_createsmsmessage.js
rename to dom/mobilemessage/tests/xpcshell/test_smsservice_createsmsmessage.js
rename from dom/mobilemessage/tests/test_wsp_pdu_helper.js
rename to dom/mobilemessage/tests/xpcshell/test_wsp_pdu_helper.js
rename from dom/mobilemessage/tests/xpcshell.ini
rename to dom/mobilemessage/tests/xpcshell/xpcshell.ini
--- a/dom/mobilemessage/tests/xpcshell.ini
+++ b/dom/mobilemessage/tests/xpcshell/xpcshell.ini
@@ -1,14 +1,11 @@
 [DEFAULT]
 head = header_helpers.js
 tail =
-support-files =
-  test_sms_basics.html
-  test_smsfilter.html
 
 [test_smsservice_createsmsmessage.js]
 [test_wsp_pdu_helper.js]
 run-if = toolkit == "gonk"
 [test_mms_pdu_helper.js]
 run-if = toolkit == "gonk"
 [test_mms_service.js]
 run-if = toolkit == "gonk"
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -307,17 +307,17 @@ MobileCallForwardingInfo.prototype = {
 
 function CellBroadcastMessage(pdu) {
   this.gsmGeographicalScope = RIL.CB_GSM_GEOGRAPHICAL_SCOPE_NAMES[pdu.geographicalScope];
   this.messageCode = pdu.messageCode;
   this.messageId = pdu.messageId;
   this.language = pdu.language;
   this.body = pdu.fullBody;
   this.messageClass = pdu.messageClass;
-  this.timestamp = new Date(pdu.timestamp);
+  this.timestamp = pdu.timestamp;
 
   if (pdu.etws != null) {
     this.etws = new CellBroadcastEtwsInfo(pdu.etws);
   }
 
   this.cdmaServiceCategory = pdu.serviceCategory;
 }
 CellBroadcastMessage.prototype = {
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -12786,19 +12786,27 @@ SimRecordHelperObject.prototype = {
         for (let i = 0; i < ad.length; i++) {
           str += ad[i] + ", ";
         }
         this.context.debug("AD: " + str);
       }
 
       let ICCUtilsHelper = this.context.ICCUtilsHelper;
       let RIL = this.context.RIL;
+      // TS 31.102, clause 4.2.18 EFAD
+      let mncLength = 0;
+      if (ad && ad[3]) {
+        mncLength = ad[3] & 0x0f;
+        if (mncLength != 0x02 && mncLength != 0x03) {
+           mncLength = 0;
+        }
+      }
       // The 4th byte of the response is the length of MNC.
       let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi,
-                                                      ad && ad[3]);
+                                                      mncLength);
       if (mccMnc) {
         RIL.iccInfo.mcc = mccMnc.mcc;
         RIL.iccInfo.mnc = mccMnc.mnc;
         ICCUtilsHelper.handleICCInfoChange();
       }
     }
 
     this.context.ICCIOHelper.loadTransparentEF({
@@ -14125,16 +14133,17 @@ ICCUtilsHelperObject.prototype = {
   /**
    * Parse MCC/MNC from IMSI. If there is no available value for the length of
    * mnc, it will use the data in MCC table to parse.
    *
    * @param imsi
    *        The imsi of icc.
    * @param mncLength [optional]
    *        The length of mnc.
+   *        Zero indicates we haven't got a valid mnc length.
    *
    * @return An object contains the parsing result of mcc and mnc.
    *         Or null if any error occurred.
    */
   parseMccMncFromImsi: function(imsi, mncLength) {
     if (!imsi) {
       return null;
     }
--- a/dom/system/gonk/tests/test_ril_worker_icc.js
+++ b/dom/system/gonk/tests/test_ril_worker_icc.js
@@ -2499,17 +2499,17 @@ add_test(function test_reading_ad_and_pa
   let buf    = context.Buf;
   let io     = context.ICCIOHelper;
 
   function do_test(mncLengthInEf, imsi, expectedMcc, expectedMnc) {
     ril.iccInfoPrivate.imsi = imsi;
 
     io.loadTransparentEF = function fakeLoadTransparentEF(options) {
       let ad = [0x00, 0x00, 0x00];
-      if (mncLengthInEf) {
+      if (typeof mncLengthInEf === 'number') {
         ad.push(mncLengthInEf);
       }
 
       // Write data size
       buf.writeInt32(ad.length * 2);
 
       // Write data
       for (let i = 0; i < ad.length; i++) {
@@ -2526,19 +2526,30 @@ add_test(function test_reading_ad_and_pa
 
     record.readAD();
 
     do_check_eq(ril.iccInfo.mcc, expectedMcc);
     do_check_eq(ril.iccInfo.mnc, expectedMnc);
   }
 
   do_test(undefined, "466923202422409", "466", "92" );
+  do_test(0x00,      "466923202422409", "466", "92" );
+  do_test(0x01,      "466923202422409", "466", "92" );
+  do_test(0x02,      "466923202422409", "466", "92" );
   do_test(0x03,      "466923202422409", "466", "923");
+  do_test(0x04,      "466923202422409", "466", "92" );
+  do_test(0xff,      "466923202422409", "466", "92" );
+
   do_test(undefined, "310260542718417", "310", "260");
+  do_test(0x00,      "310260542718417", "310", "260");
+  do_test(0x01,      "310260542718417", "310", "260");
   do_test(0x02,      "310260542718417", "310", "26" );
+  do_test(0x03,      "310260542718417", "310", "260");
+  do_test(0x04,      "310260542718417", "310", "260");
+  do_test(0xff,      "310260542718417", "310", "260");
 
   run_next_test();
 });
 
 add_test(function test_reading_optional_efs() {
   let worker = newUint8Worker();
   let context = worker.ContextPool._contexts[0];
   let record = context.SimRecordHelper;
--- a/mobile/android/base/AppConstants.java.in
+++ b/mobile/android/base/AppConstants.java.in
@@ -116,16 +116,23 @@ public class AppConstants {
 
     public static final boolean MOZ_DATA_REPORTING =
 #ifdef MOZ_DATA_REPORTING
       true;
 #else
     false;
 #endif
 
+    public static final boolean MOZ_LOCALE_SWITCHER =
+#ifdef MOZ_LOCALE_SWITCHER
+    true;
+#else
+    false;
+#endif
+
     public static final boolean MOZ_UPDATER =
 #ifdef MOZ_UPDATER
     true;
 #else
     false;
 #endif
 
     public static final boolean MOZ_WEBSMS_BACKEND =
--- a/mobile/android/base/BrowserLocaleManager.java
+++ b/mobile/android/base/BrowserLocaleManager.java
@@ -70,16 +70,20 @@ public class BrowserLocaleManager implem
         localeManager = new BrowserLocaleManager();
         if (instance.compareAndSet(null, localeManager)) {
             return localeManager;
         } else {
             return instance.get();
         }
     }
 
+    public boolean isEnabled() {
+        return AppConstants.MOZ_LOCALE_SWITCHER;
+    }
+
     /**
      * Gecko uses locale codes like "es-ES", whereas a Java {@link Locale}
      * stringifies as "es_ES".
      *
      * This method approximates the Java 7 method <code>Locale#toLanguageTag()</code>.
      *
      * @return a locale string suitable for passing to Gecko.
      */
--- a/mobile/android/base/LocaleManager.java
+++ b/mobile/android/base/LocaleManager.java
@@ -13,16 +13,21 @@ import android.content.res.Resources;
 /**
  * Implement this interface to provide Fennec's locale switching functionality.
  *
  * The LocaleManager is responsible for persisting and applying selected locales,
  * and correcting configurations after Android has changed them.
  */
 public interface LocaleManager {
     void initialize(Context context);
+
+    /**
+     * @return true if locale switching is enabled.
+     */
+    boolean isEnabled();
     Locale getCurrentLocale(Context context);
     String getAndApplyPersistedLocale(Context context);
     void correctLocale(Context context, Resources resources, Configuration newConfig);
     void updateConfiguration(Context context, Locale locale);
     String setSelectedLocale(Context context, String localeCode);
     boolean systemLocaleDidChange();
     void resetToSystemLocale(Context context);
 
--- a/mobile/android/base/Tabs.java
+++ b/mobile/android/base/Tabs.java
@@ -183,23 +183,28 @@ public class Tabs implements GeckoEventL
                         tab.updateBookmark();
                     }
                 }
             };
             BrowserDB.registerBookmarkObserver(getContentResolver(), mContentObserver);
         }
     }
 
-    private Tab addTab(int id, String url, boolean external, int parentId, String title, boolean isPrivate) {
+    private Tab addTab(int id, String url, boolean external, int parentId, String title, boolean isPrivate, int tabIndex) {
         final Tab tab = isPrivate ? new PrivateTab(mAppContext, id, url, external, parentId, title) :
                                     new Tab(mAppContext, id, url, external, parentId, title);
         synchronized (this) {
             lazyRegisterBookmarkObserver();
             mTabs.put(id, tab);
-            mOrder.add(tab);
+
+            if (tabIndex > -1) {
+                mOrder.add(tabIndex, tab);
+            } else {
+                mOrder.add(tab);
+            }
         }
 
         // Suppress the ADDED event to prevent animation of tabs created via session restore.
         if (mInitialTabsAdded) {
             notifyListeners(tab, TabEvents.ADDED);
         }
 
         return tab;
@@ -422,17 +427,18 @@ public class Tabs implements GeckoEventL
                     if (tab == null) {
                         // Tab was already closed; abort
                         return;
                     }
                 } else {
                     tab = addTab(id, url, message.getBoolean("external"),
                                           message.getInt("parentId"),
                                           message.getString("title"),
-                                          message.getBoolean("isPrivate"));
+                                          message.getBoolean("isPrivate"),
+                                          message.getInt("tabIndex"));
 
                     // If we added the tab as a stub, we should have already
                     // selected it, so ignore this flag for stubbed tabs.
                     if (message.getBoolean("selected"))
                         selectTab(id);
                 }
 
                 if (message.getBoolean("delayLoad"))
@@ -794,17 +800,20 @@ public class Tabs implements GeckoEventL
                 int tabId = getNextTabId();
                 args.put("tabID", tabId);
 
                 // The URL is updated for the tab once Gecko responds with the
                 // Tab:Added message. We can preliminarily set the tab's URL as
                 // long as it's a valid URI.
                 String tabUrl = (url != null && Uri.parse(url).getScheme() != null) ? url : null;
 
-                added = addTab(tabId, tabUrl, external, parentId, url, isPrivate);
+                // Add the new tab to the end of the tab order.
+                final int tabIndex = -1;
+
+                added = addTab(tabId, tabUrl, external, parentId, url, isPrivate, tabIndex);
                 added.setDesktopMode(desktopMode);
             }
         } catch (Exception e) {
             Log.w(LOGTAG, "Error building JSON arguments for loadUrl.", e);
         }
 
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Load", args.toString()));
 
--- a/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java
@@ -1,23 +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/. */
 
 package org.mozilla.gecko.fxa.activities;
 
 import java.util.Locale;
 
-import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 import org.mozilla.gecko.sync.setup.activities.LocaleAware;
 
 import android.accounts.AccountAuthenticatorActivity;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.view.View;
--- a/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
+++ b/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
@@ -13,38 +13,44 @@ import org.mozilla.gecko.background.comm
 import org.mozilla.gecko.background.preferences.PreferenceFragment;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Married;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
 import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
+import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
 import org.mozilla.gecko.sync.SyncConfiguration;
 
 import android.accounts.Account;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.os.Handler;
 import android.preference.CheckBoxPreference;
+import android.preference.EditTextPreference;
 import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceCategory;
 import android.preference.PreferenceScreen;
+import android.text.TextUtils;
 
 /**
  * A fragment that displays the status of an AndroidFxAccount.
  * <p>
  * The owning activity is responsible for providing an AndroidFxAccount at
  * appropriate times.
  */
-public class FxAccountStatusFragment extends PreferenceFragment implements OnPreferenceClickListener {
+public class FxAccountStatusFragment
+    extends PreferenceFragment
+    implements OnPreferenceClickListener, OnPreferenceChangeListener {
   private static final String LOG_TAG = FxAccountStatusFragment.class.getSimpleName();
 
   // When a checkbox is toggled, wait 5 seconds (for other checkbox actions)
   // before trying to sync. Should we kill off the fragment before the sync
   // request happens, that's okay: the runnable will run if the UI thread is
   // still around to service it, and since we're not updating any UI, we'll just
   // schedule the sync as usual. See also comment below about garbage
   // collection.
@@ -60,17 +66,22 @@ public class FxAccountStatusFragment ext
 
   protected PreferenceCategory syncCategory;
 
   protected CheckBoxPreference bookmarksPreference;
   protected CheckBoxPreference historyPreference;
   protected CheckBoxPreference tabsPreference;
   protected CheckBoxPreference passwordsPreference;
 
+  protected EditTextPreference deviceNamePreference;
+
   protected volatile AndroidFxAccount fxAccount;
+  // The contract is: when fxAccount is non-null, then clientsDataDelegate is
+  // non-null.  If violated then an IllegalStateException is thrown.
+  protected volatile SharedPreferencesClientsDataDelegate clientsDataDelegate;
 
   // Used to post delayed sync requests.
   protected Handler handler;
 
   // Member variable so that re-posting pushes back the already posted instance.
   // This Runnable references the fxAccount above, but it is not specific to a
   // single account. (That is, it does not capture a single account instance.)
   protected Runnable requestSyncRunnable;
@@ -83,16 +94,20 @@ public class FxAccountStatusFragment ext
       throw new IllegalStateException("Could not find preference with key: " + key);
     }
     return preference;
   }
 
   @Override
   public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
+    addPreferences();
+  }
+
+  protected void addPreferences() {
     addPreferencesFromResource(R.xml.fxaccount_status_prefscreen);
 
     emailPreference = ensureFindPreference("email");
 
     needsPasswordPreference = ensureFindPreference("needs_credentials");
     needsUpgradePreference = ensureFindPreference("needs_upgrade");
     needsVerificationPreference = ensureFindPreference("needs_verification");
     needsMasterSyncAutomaticallyEnabledPreference = ensureFindPreference("needs_master_sync_automatically_enabled");
@@ -114,16 +129,19 @@ public class FxAccountStatusFragment ext
     needsPasswordPreference.setOnPreferenceClickListener(this);
     needsVerificationPreference.setOnPreferenceClickListener(this);
     needsAccountEnabledPreference.setOnPreferenceClickListener(this);
 
     bookmarksPreference.setOnPreferenceClickListener(this);
     historyPreference.setOnPreferenceClickListener(this);
     tabsPreference.setOnPreferenceClickListener(this);
     passwordsPreference.setOnPreferenceClickListener(this);
+
+    deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name");
+    deviceNamePreference.setOnPreferenceChangeListener(this);
   }
 
   /**
    * We intentionally don't refresh here. Our owning activity is responsible for
    * providing an AndroidFxAccount to our refresh method in its onResume method.
    */
   @Override
   public void onResume() {
@@ -172,16 +190,18 @@ public class FxAccountStatusFragment ext
     return false;
   }
 
   protected void setCheckboxesEnabled(boolean enabled) {
     bookmarksPreference.setEnabled(enabled);
     historyPreference.setEnabled(enabled);
     tabsPreference.setEnabled(enabled);
     passwordsPreference.setEnabled(enabled);
+    // Since we can't sync, we can't update our remote client record.
+    deviceNamePreference.setEnabled(enabled);
   }
 
   /**
    * Show at most one error preference, hiding all others.
    *
    * @param errorPreferenceToShow
    *          single error preference to show; if null, hide all error preferences
    */
@@ -289,16 +309,24 @@ public class FxAccountStatusFragment ext
    *
    * @param fxAccount new instance.
    */
   public void refresh(AndroidFxAccount fxAccount) {
     if (fxAccount == null) {
       throw new IllegalArgumentException("fxAccount must not be null");
     }
     this.fxAccount = fxAccount;
+    try {
+      this.clientsDataDelegate = new SharedPreferencesClientsDataDelegate(fxAccount.getSyncPrefs());
+    } catch (Exception e) {
+      Logger.error(LOG_TAG, "Got exception fetching Sync prefs associated to Firefox Account; aborting.", e);
+      // Something is terribly wrong; best to get a stack trace rather than
+      // continue with a null clients delegate.
+      throw new IllegalStateException(e);
+    }
 
     handler = new Handler(); // Attached to current (assumed to be UI) thread.
 
     // Runnable is not specific to one Firefox Account. This runnable will keep
     // a reference to this fragment alive, but we expect posted runnables to be
     // serviced very quickly, so this is not an issue.
     requestSyncRunnable = new RequestSyncRunnable();
 
@@ -314,16 +342,27 @@ public class FxAccountStatusFragment ext
   }
 
   @Override
   public void onPause() {
     super.onPause();
     FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate);
   }
 
+  protected void hardRefresh() {
+    // This is the only way to guarantee that the EditText dialogs created by
+    // EditTextPreferences are re-created. This works around the issue described
+    // at http://androiddev.orkitra.com/?p=112079.
+    final PreferenceScreen statusScreen = (PreferenceScreen) ensureFindPreference("status_screen");
+    statusScreen.removeAll();
+    addPreferences();
+
+    refresh();
+  }
+
   protected void refresh() {
     // refresh is called from our onResume, which can happen before the owning
     // Activity tells us about an account (via our public
     // refresh(AndroidFxAccount) method).
     if (fxAccount == null) {
       throw new IllegalArgumentException("fxAccount must not be null");
     }
 
@@ -367,16 +406,20 @@ public class FxAccountStatusFragment ext
       if (!masterSyncAutomatically) {
         showNeedsMasterSyncAutomaticallyEnabled();
         return;
       }
     } finally {
       // No matter our state, we should update the checkboxes.
       updateSelectedEngines();
     }
+
+    final String clientName = clientsDataDelegate.getClientName();
+    deviceNamePreference.setSummary(clientName);
+    deviceNamePreference.setText(clientName);
   }
 
   /**
    * Query shared prefs for the current engine state, and update the UI
    * accordingly.
    * <p>
    * In future, we might want this to be on a background thread, or implemented
    * as a Loader.
@@ -566,9 +609,27 @@ public class FxAccountStatusFragment ext
         "debug_require_password",
         "debug_require_upgrade" };
     for (String debugKey : debugKeys) {
       final Preference button = ensureFindPreference(debugKey);
       button.setTitle(debugKey); // Not very friendly, but this is for debugging only!
       button.setOnPreferenceClickListener(listener);
     }
   }
+
+  @Override
+  public boolean onPreferenceChange(Preference preference, Object newValue) {
+    if (preference == deviceNamePreference) {
+      String newClientName = (String) newValue;
+      if (TextUtils.isEmpty(newClientName)) {
+        newClientName = clientsDataDelegate.getDefaultClientName();
+      }
+      final long now = System.currentTimeMillis();
+      clientsDataDelegate.setClientName(newClientName, now);
+      requestDelayedSync(); // Try to update our remote client record.
+      hardRefresh(); // Updates the value displayed to the user, among other things.
+      return true;
+    }
+
+    // For everything else, accept the change.
+    return true;
+  }
 }
--- a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
@@ -352,17 +352,21 @@ public class FxAccountSyncAdapter extend
         }
 
         // Invalidate the previous backoff, because our storage host has changed,
         // or we never had one at all, or we're OK to sync.
         storageBackoffHandler.setEarliestNextRequest(0L);
 
         FxAccountGlobalSession globalSession = null;
         try {
-          ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs);
+          final ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs);
+          if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
+            FxAccountConstants.pii(LOG_TAG, "Client device name is: '" + clientsDataDelegate.getClientName() + "'.");
+            FxAccountConstants.pii(LOG_TAG, "Client device data last modified: " + clientsDataDelegate.getLastModifiedTimestamp());
+          }
 
           // We compute skew over time using SkewHandler. This yields an unchanging
           // skew adjustment that the HawkAuthHeaderProvider uses to adjust its
           // timestamps. Eventually we might want this to adapt within the scope of a
           // global session.
           final SkewHandler storageServerSkewHandler = SkewHandler.getSkewHandlerForHostname(storageHostname);
           final long storageServerSkew = storageServerSkewHandler.getSkewInSeconds();
           // We expect Sync to upload large sets of records. Calculating the
--- a/mobile/android/base/locales/en-US/sync_strings.dtd
+++ b/mobile/android/base/locales/en-US/sync_strings.dtd
@@ -170,16 +170,17 @@
 <!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_header2 'Firefox Account'>
 <!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
+<!ENTITY fxaccount_status_device_name 'Device name'>
 <!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_needs_master_sync_automatically_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Toggle “Auto-sync data” in Android Settings &gt; Data Usage.'>
 <!ENTITY fxaccount_status_needs_account_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Tap to start syncing.'>
 <!ENTITY fxaccount_status_bookmarks 'Bookmarks'>
--- a/mobile/android/base/preferences/GeckoPreferences.java
+++ b/mobile/android/base/preferences/GeckoPreferences.java
@@ -126,16 +126,17 @@ OnSharedPreferenceChangeListener
     // Callers can recognize this code to refresh themselves to
     // accommodate a locale change.
     public static final int RESULT_CODE_LOCALE_DID_CHANGE = 7;
 
     /**
      * Track the last locale so we know whether to redisplay.
      */
     private Locale lastLocale = Locale.getDefault();
+    private boolean localeSwitchingIsEnabled;
 
     private void updateActionBarTitle(int title) {
         if (Build.VERSION.SDK_INT >= 14) {
             final String newTitle = getString(title);
             if (newTitle != null) {
                 Log.v(LOGTAG, "Setting action bar title to " + newTitle);
 
                 final ActionBar actionBar = getActionBar();
@@ -263,35 +264,46 @@ OnSharedPreferenceChangeListener
         onLocaleChanged(currentLocale);
     }
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         // Apply the current user-selected locale, if necessary.
         checkLocale();
 
+        // Track this so we can decide whether to show locale options.
+        // See also the workaround below for Bug 1015209.
+        localeSwitchingIsEnabled = BrowserLocaleManager.getInstance().isEnabled();
+
         // For Android v11+ where we use Fragments (v11+ only due to bug 866352),
         // check that PreferenceActivity.EXTRA_SHOW_FRAGMENT has been set
         // (or set it) before super.onCreate() is called so Android can display
         // the correct Fragment resource.
 
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
             if (!getIntent().hasExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT)) {
                 // Set up the default fragment if there is no explicit fragment to show.
                 setupTopLevelFragmentIntent();
 
                 // This is the default header, because it's the first one.
                 // I know, this is an affront to all human decency. And yet.
                 updateTitle(getString(R.string.pref_header_customize));
             }
 
-            // So that Android doesn't put the fragment title (or nothing at
-            // all) in the action bar.
             if (onIsMultiPane()) {
+                // So that Android doesn't put the fragment title (or nothing at
+                // all) in the action bar.
                 updateActionBarTitle(R.string.settings_title);
+
+                if (Build.VERSION.SDK_INT < 13) {
+                    // Affected by Bug 1015209 -- no detach/attach.
+                    // If we try rejigging fragments, we'll crash, so don't
+                    // enable locale switching at all.
+                    localeSwitchingIsEnabled = false;
+                }
             }
         }
 
         super.onCreate(savedInstanceState);
 
         // Use setResourceToOpen to specify these extras.
         Bundle intentExtras = getIntent().getExtras();
 
@@ -392,16 +404,28 @@ OnSharedPreferenceChangeListener
     public boolean isValidFragment(String fragmentName) {
         return GeckoPreferenceFragment.class.getName().equals(fragmentName);
     }
 
     @Override
     public void onBuildHeaders(List<Header> target) {
         if (onIsMultiPane()) {
             loadHeadersFromResource(R.xml.preference_headers, target);
+
+            // If locale switching is disabled, remove the section
+            // entirely. This logic will need to be extended when
+            // content language selection (Bug 881510) is implemented.
+            if (!localeSwitchingIsEnabled) {
+                for (Header header : target) {
+                    if (header.id == R.id.pref_header_language) {
+                        target.remove(header);
+                        break;
+                    }
+                }
+            }
         }
     }
 
     @Override
     public void onWindowFocusChanged(boolean hasFocus) {
         if (!hasFocus || mInitialized)
             return;
 
@@ -559,19 +583,30 @@ OnSharedPreferenceChangeListener
       * @param prefs An ArrayList to fill with Gecko preferences that need to be
       *        initialized
       * @return The integer id for the PrefsHelper.PrefHandlerBase listener added
       *         to monitor changes to Gecko prefs.
       */
     private void setupPreferences(PreferenceGroup preferences, ArrayList<String> prefs) {
         for (int i = 0; i < preferences.getPreferenceCount(); i++) {
             Preference pref = preferences.getPreference(i);
+
+            // Eliminate locale switching if necessary.
+            // This logic will need to be extended when
+            // content language selection (Bug 881510) is implemented.
+            if (!localeSwitchingIsEnabled &&
+                "preferences_locale".equals(pref.getExtras().getString("resource", null))) {
+                preferences.removePreference(pref);
+                i--;
+                continue;
+            }
+
             String key = pref.getKey();
             if (pref instanceof PreferenceGroup) {
-                // If no datareporting is enabled, remove UI.
+                // If datareporting is disabled, remove UI.
                 if (PREFS_DATA_REPORTING_PREFERENCES.equals(key)) {
                     if (!AppConstants.MOZ_DATA_REPORTING) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
                 } else if (pref instanceof PanelsPreferenceCategory) {
                     mPanelsPreferenceCategory = (PanelsPreferenceCategory) pref;
--- a/mobile/android/base/resources/xml-v11/preference_headers.xml
+++ b/mobile/android/base/resources/xml-v11/preference_headers.xml
@@ -23,17 +23,18 @@
 
     <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
             android:title="@string/pref_header_privacy_short">
         <extra android:name="resource"
                android:value="preferences_privacy"/>
     </header>
 
     <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
-            android:title="@string/pref_header_language">
+            android:title="@string/pref_header_language"
+            android:id="@+id/pref_header_language">
         <extra android:name="resource"
                android:value="preferences_locale" />
     </header>
 
     <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
             android:title="@string/pref_header_vendor">
         <extra android:name="resource"
                android:value="preferences_vendor"/>
--- a/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
+++ b/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
@@ -61,16 +61,22 @@
         <CheckBoxPreference
             android:key="tabs"
             android:persistent="false"
             android:title="@string/fxaccount_status_tabs" />
         <CheckBoxPreference
             android:key="passwords"
             android:persistent="false"
             android:title="@string/fxaccount_status_passwords" />
+
+        <EditTextPreference
+            android:singleLine="true"
+            android:key="device_name"
+            android:persistent="false"
+            android:title="@string/fxaccount_status_device_name" />
     </PreferenceCategory>
     <PreferenceCategory
         android:key="legal_category"
         android:title="@string/fxaccount_status_legal" >
         <Preference android:title="@string/fxaccount_status_linktos" >
             <intent
                 android:action="android.intent.action.VIEW"
                 android:data="@string/fxaccount_link_tos"
--- a/mobile/android/base/sync/SharedPreferencesClientsDataDelegate.java
+++ b/mobile/android/base/sync/SharedPreferencesClientsDataDelegate.java
@@ -25,22 +25,43 @@ public class SharedPreferencesClientsDat
     String accountGUID = sharedPreferences.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null);
     if (accountGUID == null) {
       accountGUID = Utils.generateGuid();
       sharedPreferences.edit().putString(SyncConfiguration.PREF_ACCOUNT_GUID, accountGUID).commit();
     }
     return accountGUID;
   }
 
+  /**
+   * Set client name.
+   *
+   * @param clientName to change to.
+   */
+  @Override
+  public synchronized void setClientName(String clientName, long now) {
+    sharedPreferences
+      .edit()
+      .putString(SyncConfiguration.PREF_CLIENT_NAME, clientName)
+      .putLong(SyncConfiguration.PREF_CLIENT_DATA_TIMESTAMP, now)
+      .commit();
+  }
+
+  @Override
+  public String getDefaultClientName() {
+    // Bug 1019719: localize this string!
+    return GlobalConstants.MOZ_APP_DISPLAYNAME + " on " + android.os.Build.MODEL;
+  }
+
   @Override
   public synchronized String getClientName() {
     String clientName = sharedPreferences.getString(SyncConfiguration.PREF_CLIENT_NAME, null);
     if (clientName == null) {
-      clientName = GlobalConstants.MOZ_APP_DISPLAYNAME + " on " + android.os.Build.MODEL;
-      sharedPreferences.edit().putString(SyncConfiguration.PREF_CLIENT_NAME, clientName).commit();
+      clientName = getDefaultClientName();
+      long now = System.currentTimeMillis();
+      setClientName(clientName, now);
     }
     return clientName;
   }
 
   @Override
   public synchronized void setClientsCount(int clientsCount) {
     sharedPreferences.edit().putLong(SyncConfiguration.PREF_NUM_CLIENTS, (long) clientsCount).commit();
   }
@@ -49,9 +70,14 @@ public class SharedPreferencesClientsDat
   public boolean isLocalGUID(String guid) {
     return getAccountGUID().equals(guid);
   }
 
   @Override
   public synchronized int getClientsCount() {
     return (int) sharedPreferences.getLong(SyncConfiguration.PREF_NUM_CLIENTS, 0);
   }
+
+  @Override
+  public long getLastModifiedTimestamp() {
+    return sharedPreferences.getLong(SyncConfiguration.PREF_CLIENT_DATA_TIMESTAMP, 0);
+  }
 }
--- a/mobile/android/base/sync/SyncConfiguration.java
+++ b/mobile/android/base/sync/SyncConfiguration.java
@@ -253,16 +253,17 @@ public class SyncConfiguration {
   public static final String PREF_USER_SELECTED_ENGINES_TO_SYNC = "userSelectedEngines";
   public static final String PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP = "userSelectedEnginesTimestamp";
 
   public static final String PREF_CLUSTER_URL_IS_STALE = "clusterurlisstale";
 
   public static final String PREF_ACCOUNT_GUID = "account.guid";
   public static final String PREF_CLIENT_NAME = "account.clientName";
   public static final String PREF_NUM_CLIENTS = "account.numClients";
+  public static final String PREF_CLIENT_DATA_TIMESTAMP = "account.clientDataTimestamp";
 
   private static final String API_VERSION = "1.5";
 
   public SyncConfiguration(String username, AuthHeaderProvider authHeaderProvider, SharedPreferences prefs) {
     this.username = username;
     this.authHeaderProvider = authHeaderProvider;
     this.prefs = prefs;
     this.loadFromPrefs(prefs);
--- a/mobile/android/base/sync/delegates/ClientsDataDelegate.java
+++ b/mobile/android/base/sync/delegates/ClientsDataDelegate.java
@@ -1,13 +1,27 @@
 /* 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.sync.delegates;
 
 public interface ClientsDataDelegate {
   public String getAccountGUID();
+  public String getDefaultClientName();
+  public void setClientName(String clientName, long now);
   public String getClientName();
   public void setClientsCount(int clientsCount);
   public int getClientsCount();
   public boolean isLocalGUID(String guid);
+
+  /**
+   * The last time the client's data was modified in a way that should be
+   * reflected remotely.
+   * <p>
+   * Changing the client's name should be reflected remotely, while changing the
+   * clients count should not (since that data is only used to inform local
+   * policy.)
+   *
+   * @return timestamp in milliseconds.
+   */
+  public long getLastModifiedTimestamp();
 }
--- a/mobile/android/base/sync/repositories/android/FennecTabsRepository.java
+++ b/mobile/android/base/sync/repositories/android/FennecTabsRepository.java
@@ -4,16 +4,17 @@
 
 package org.mozilla.gecko.sync.repositories.android;
 
 import java.util.ArrayList;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.db.Tab;
 import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.NoContentProviderException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
@@ -25,22 +26,20 @@ import org.mozilla.gecko.sync.repositori
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.RemoteException;
 
 public class FennecTabsRepository extends Repository {
-  protected final String localClientName;
-  protected final String localClientGuid;
+  protected final ClientsDataDelegate clientsDataDelegate;
 
-  public FennecTabsRepository(final String localClientName, final String localClientGuid) {
-    this.localClientName = localClientName;
-    this.localClientGuid = localClientGuid;
+  public FennecTabsRepository(ClientsDataDelegate clientsDataDelegate) {
+    this.clientsDataDelegate = clientsDataDelegate;
   }
 
   /**
    * Note that — unlike most repositories — this will only fetch Fennec's tabs,
    * and only store tabs from other clients.
    *
    * It will never retrieve tabs from other clients, or store tabs for Fennec,
    * unless you use {@link #fetch(String[], RepositorySessionFetchRecordsDelegate)}
@@ -139,24 +138,27 @@ public class FennecTabsRepository extend
       final String localClientSelection = localClientSelection();
       final String[] localClientSelectionArgs = localClientSelectionArgs();
 
       final Runnable command = new Runnable() {
         @Override
         public void run() {
           // We fetch all local tabs (since the record must contain them all)
           // but only process the record if the timestamp is sufficiently
-          // recent.
+          // recent, or if the client data has been modified.
           try {
             final Cursor cursor = tabsHelper.safeQuery(tabsProvider, ".fetchSince()", null,
                 localClientSelection, localClientSelectionArgs, positionAscending);
             try {
+              final String localClientGuid = clientsDataDelegate.getAccountGUID();
+              final String localClientName = clientsDataDelegate.getClientName();
               final TabsRecord tabsRecord = FennecTabsRepository.tabsRecordFromCursor(cursor, localClientGuid, localClientName);
 
-              if (tabsRecord.lastModified >= timestamp) {
+              if (tabsRecord.lastModified >= timestamp ||
+                  clientsDataDelegate.getLastModifiedTimestamp() >= timestamp) {
                 delegate.onFetchedRecord(tabsRecord);
               }
             } finally {
               cursor.close();
             }
           } catch (Exception e) {
             delegate.onFetchFailed(e, null);
             return;
--- a/mobile/android/base/sync/setup/activities/SendTabActivity.java
+++ b/mobile/android/base/sync/setup/activities/SendTabActivity.java
@@ -20,22 +20,19 @@ import org.mozilla.gecko.fxa.authenticat
 import org.mozilla.gecko.fxa.login.State.Action;
 import org.mozilla.gecko.sync.CommandProcessor;
 import org.mozilla.gecko.sync.CommandRunner;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.SyncConstants;
 import org.mozilla.gecko.sync.repositories.NullCursorException;
 import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
-import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
-import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository.FennecTabsRepositorySession;
 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.sync.setup.activities.LocaleAware.LocaleAwareActivity;
-import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
 import org.mozilla.gecko.sync.syncadapter.SyncAdapter;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
--- a/mobile/android/base/sync/stage/FennecTabsServerSyncStage.java
+++ b/mobile/android/base/sync/stage/FennecTabsServerSyncStage.java
@@ -1,15 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.sync.stage;
 
-import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
 import org.mozilla.gecko.sync.repositories.RecordFactory;
 import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
 import org.mozilla.gecko.sync.repositories.domain.TabsRecordFactory;
 import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
 
 public class FennecTabsServerSyncStage extends ServerSyncStage {
   private static final String COLLECTION = "tabs";
@@ -26,17 +25,16 @@ public class FennecTabsServerSyncStage e
 
   @Override
   public Integer getStorageVersion() {
     return VersionConstants.TABS_ENGINE_VERSION;
   }
 
   @Override
   protected Repository getLocalRepository() {
-    final ClientsDataDelegate clientsDelegate = session.getClientsDelegate();
-    return new FennecTabsRepository(clientsDelegate.getClientName(), clientsDelegate.getAccountGUID());
+    return new FennecTabsRepository(session.getClientsDelegate());
   }
 
   @Override
   protected RecordFactory getRecordFactory() {
     return new TabsRecordFactory();
   }
 }
--- a/mobile/android/base/sync/stage/SyncClientsEngineStage.java
+++ b/mobile/android/base/sync/stage/SyncClientsEngineStage.java
@@ -392,16 +392,21 @@ public class SyncClientsEngineStage exte
       return true;
     }
 
     long lastUpload = session.config.getPersistedServerClientRecordTimestamp();   // Defaults to 0.
     if (lastUpload == 0) {
       return true;
     }
 
+    if (session.getClientsDelegate().getLastModifiedTimestamp() > lastUpload) {
+      // Something's changed locally since we last uploaded.
+      return true;
+    }
+
     // Note the opportunity for clock drift problems here.
     // TODO: if we track download times, we can use the timestamp of most
     // recent download response instead of the current time.
     long now = System.currentTimeMillis();
     long age = now - lastUpload;
     return age >= CLIENTS_TTL_REFRESH;
   }
 
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -916,17 +916,22 @@ var BrowserApp = {
       }
     }
   },
 
   addTab: function addTab(aURI, aParams) {
     aParams = aParams || {};
 
     let newTab = new Tab(aURI, aParams);
-    this._tabs.push(newTab);
+
+    if (typeof aParams.tabIndex == "number") {
+      this._tabs.splice(aParams.tabIndex, 0, newTab);
+    } else {
+      this._tabs.push(newTab);
+    }
 
     let selected = "selected" in aParams ? aParams.selected : true;
     if (selected)
       this.selectedTab = newTab;
 
     let pinned = "pinned" in aParams ? aParams.pinned : false;
     if (pinned) {
       let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
@@ -967,35 +972,40 @@ var BrowserApp = {
 #endif
 
   // Calling this will update the state in BrowserApp after a tab has been
   // closed in the Java UI.
   _handleTabClosed: function _handleTabClosed(aTab, aShowUndoToast) {
     if (aTab == this.selectedTab)
       this.selectedTab = null;
 
+    let tabIndex = this._tabs.indexOf(aTab);
+
     let evt = document.createEvent("UIEvents");
-    evt.initUIEvent("TabClose", true, false, window, null);
+    evt.initUIEvent("TabClose", true, false, window, tabIndex);
     aTab.browser.dispatchEvent(evt);
 
-    // Get a title for the undo close toast. Fall back to the URL if there is no title.
-    let title = aTab.browser.contentDocument.title || aTab.browser.contentDocument.URL;
-
     aTab.destroy();
-    this._tabs.splice(this._tabs.indexOf(aTab), 1);
+    this._tabs.splice(tabIndex, 1);
 
     if (aShowUndoToast) {
+      // Get a title for the undo close toast. Fall back to the URL if there is no title.
+      let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+      let closedTabData = ss.getClosedTabs(window)[0];
+
+      let historyEntry = closedTabData.entries[closedTabData.index - 1];
+      let title = historyEntry.title || historyEntry.url;
+
       let message = Strings.browser.formatStringFromName("undoCloseToast.message", [title], 1);
       NativeWindow.toast.show(message, "short", {
         button: {
           icon: "drawable://undo_button_icon",
           label: Strings.browser.GetStringFromName("undoCloseToast.action2"),
           callback: function() {
             UITelemetry.addEvent("undo.1", "toast", null, "closetab");
-            let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
             ss.undoCloseTab(window, 0);
           }
         }
       });
     }
   },
 
   // Use this method to select a tab from JS. This method sends a message
@@ -3045,16 +3055,17 @@ Tab.prototype = {
 
       this.desktopMode = ("desktopMode" in aParams) ? aParams.desktopMode : false;
 
       let message = {
         type: "Tab:Added",
         tabID: this.id,
         uri: uri,
         parentId: ("parentId" in aParams) ? aParams.parentId : -1,
+        tabIndex: ("tabIndex" in aParams) ? aParams.tabIndex : -1,
         external: ("external" in aParams) ? aParams.external : false,
         selected: ("selected" in aParams) ? aParams.selected : true,
         title: title,
         delayLoad: aParams.delayLoad || false,
         desktopMode: this.desktopMode,
         isPrivate: isPrivate,
         stub: stub
       };
--- a/mobile/android/components/SessionStore.idl
+++ b/mobile/android/components/SessionStore.idl
@@ -9,17 +9,17 @@ interface nsIDOMNode;
 
 /**
  * nsISessionStore keeps track of the current browsing state.
  *
  * The nsISessionStore API operates mostly on browser windows and the browser
  * tabs contained in them.
  */
 
-[scriptable, uuid(fe116b56-0226-4562-b52a-a623dad07ead)]
+[scriptable, uuid(91eca9cf-6741-4c8f-a3a0-2e957240894d)]
 interface nsISessionStore : nsISupports
 {
   /**
    * Get the current browsing state.
    * @returns a JSON string representing the session state.
    */
   AString getBrowserState();
 
@@ -27,19 +27,19 @@ interface nsISessionStore : nsISupports
    * Get the number of restore-able tabs for a browser window
    */
   unsigned long getClosedTabCount(in nsIDOMWindow aWindow);
 
   /**
    * Get closed tab data
    *
    * @param aWindow is the browser window for which to get closed tab data
-   * @returns a JSON string representing the list of closed tabs.
+   * @returns a JS array of closed tabs.
    */
-  AString getClosedTabData(in nsIDOMWindow aWindow);
+  jsval getClosedTabs(in nsIDOMWindow aWindow);
 
   /**
    * @param aWindow is the browser window to reopen a closed tab in.
    * @param aIndex  is the index of the tab to be restored (FIFO ordered).
    * @returns a reference to the reopened tab.
    */
   nsIDOMNode undoCloseTab(in nsIDOMWindow aWindow, in unsigned long aIndex);
 
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -41,16 +41,20 @@ SessionStore.prototype = {
                                          Ci.nsISupportsWeakReference]),
 
   _windows: {},
   _lastSaveTime: 0,
   _interval: 10000,
   _maxTabsUndo: 1,
   _pendingWrite: 0,
 
+  // The index where the most recently closed tab was in the tabs array
+  // when it was closed.
+  _lastClosedTabIndex: -1,
+
   init: function ss_init() {
     // Get file references
     this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
     this._sessionFileBackup = this._sessionFile.clone();
     this._sessionFile.append("sessionstore.js");
     this._sessionFileBackup.append("sessionstore.bak");
 
     this._loadState = STATE_STOPPED;
@@ -93,16 +97,18 @@ SessionStore.prototype = {
         break;
       case "browser:purge-session-history": // catch sanitization 
         this._clearDisk();
 
         // Clear all data about closed tabs
         for (let [ssid, win] in Iterator(this._windows))
           win.closedTabs = [];
 
+        this._lastClosedTabIndex = -1;
+
         if (this._loadState == STATE_RUNNING) {
           // Save the purged state immediately
           this.saveState();
         }
 
         Services.obs.notifyObservers(null, "sessionstore-state-purge-complete", "");
         break;
       case "timer-callback":
@@ -159,17 +165,17 @@ SessionStore.prototype = {
     switch (aEvent.type) {
       case "TabOpen": {
         let browser = aEvent.target;
         this.onTabAdd(window, browser);
         break;
       }
       case "TabClose": {
         let browser = aEvent.target;
-        this.onTabClose(window, browser);
+        this.onTabClose(window, browser, aEvent.detail);
         this.onTabRemove(window, browser);
         break;
       }
       case "TabSelect": {
         let browser = aEvent.target;
         this.onTabSelect(window, browser);
         break;
       }
@@ -264,30 +270,32 @@ SessionStore.prototype = {
       return;
 
     delete aBrowser.__SS_data;
 
     if (!aNoNotification)
       this.saveStateDelayed();
   },
 
-  onTabClose: function ss_onTabClose(aWindow, aBrowser) {
+  onTabClose: function ss_onTabClose(aWindow, aBrowser, aTabIndex) {
     if (this._maxTabsUndo == 0)
       return;
 
     if (aWindow.BrowserApp.tabs.length > 0) {
       // Bundle this browser's data and extra data and save in the closedTabs
       // window property
       let data = aBrowser.__SS_data;
       data.extData = aBrowser.__SS_extdata;
 
       this._windows[aWindow.__SSID].closedTabs.unshift(data);
       let length = this._windows[aWindow.__SSID].closedTabs.length;
       if (length > this._maxTabsUndo)
         this._windows[aWindow.__SSID].closedTabs.splice(this._maxTabsUndo, length - this._maxTabsUndo);
+
+      this._lastClosedTabIndex = aTabIndex;
     }
   },
 
   onTabLoad: function ss_onTabLoad(aWindow, aBrowser) {
     // If this browser is being restored, skip any session save activity
     if (aBrowser.__SS_restore)
       return;
 
@@ -813,21 +821,21 @@ SessionStore.prototype = {
 
   getClosedTabCount: function ss_getClosedTabCount(aWindow) {
     if (!aWindow || !aWindow.__SSID || !this._windows[aWindow.__SSID])
       return 0; // not a browser window, or not otherwise tracked by SS.
 
     return this._windows[aWindow.__SSID].closedTabs.length;
   },
 
-  getClosedTabData: function ss_getClosedTabData(aWindow) {
+  getClosedTabs: function ss_getClosedTabs(aWindow) {
     if (!aWindow.__SSID)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
-    return JSON.stringify(this._windows[aWindow.__SSID].closedTabs);
+    return this._windows[aWindow.__SSID].closedTabs;
   },
 
   undoCloseTab: function ss_undoCloseTab(aWindow, aIndex) {
     if (!aWindow.__SSID)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
     let closedTabs = this._windows[aWindow.__SSID].closedTabs;
     if (!closedTabs)
@@ -840,21 +848,24 @@ SessionStore.prototype = {
 
     // fetch the data of closed tab, while removing it from the array
     let closedTab = closedTabs.splice(aIndex, 1).shift();
 
     // create a new tab and bring to front
     let params = {
       selected: true,
       isPrivate: closedTab.isPrivate,
-      desktopMode: closedTab.desktopMode
+      desktopMode: closedTab.desktopMode,
+      tabIndex: this._lastClosedTabIndex
     };
     let tab = aWindow.BrowserApp.addTab(closedTab.entries[closedTab.index - 1].url, params);
     this._restoreHistory(closedTab, tab.browser.sessionHistory);
 
+    this._lastClosedTabIndex = -1;
+
     // Put back the extra data
     tab.browser.__SS_extdata = closedTab.extData;
 
     return tab.browser;
   },
 
   forgetClosedTab: function ss_forgetClosedTab(aWindow, aIndex) {
     if (!aWindow.__SSID)
@@ -864,16 +875,21 @@ SessionStore.prototype = {
 
     // default to the most-recently closed tab
     aIndex = aIndex || 0;
     if (!(aIndex in closedTabs))
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
     // remove closed tab from the array
     closedTabs.splice(aIndex, 1);
+
+    // Forget the last closed tab index if we're forgetting the last closed tab.
+    if (aIndex == 0) {
+      this._lastClosedTabIndex = -1;
+    }
   },
 
   getTabValue: function ss_getTabValue(aTab, aKey) {
     let browser = aTab.browser;
     let data = browser.__SS_extdata || {};
     return data[aKey] || "";
   },
 
--- a/mobile/android/confvars.sh
+++ b/mobile/android/confvars.sh
@@ -59,13 +59,16 @@ MOZ_PAY=1
 MOZ_SERVICES_HEALTHREPORT=1
 
 # Enable FirefoxAccounts
 MOZ_SERVICES_FXACCOUNTS=1
 
 # Enable Wifi-AP/cell tower data reporting
 MOZ_DATA_REPORTING=1
 
+# Enable runtime locale switching.
+MOZ_LOCALE_SWITCHER=1
+
 # Enable the "synthetic APKs" implementation of Open Web Apps.
 MOZ_ANDROID_SYNTHAPKS=1
 
 # Enable second screen and casting support for external devices.
 MOZ_DEVICES=1
--- a/mobile/android/services/strings.xml.in
+++ b/mobile/android/services/strings.xml.in
@@ -164,16 +164,17 @@
 
 <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_header2;</string>
 <string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
+<string name="fxaccount_status_device_name">&fxaccount_status_device_name;</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_needs_master_sync_automatically_enabled">&fxaccount_status_needs_master_sync_automatically_enabled;</string>
 <string name="fxaccount_status_needs_account_enabled">&fxaccount_status_needs_account_enabled;</string>
 <string name="fxaccount_status_bookmarks">&fxaccount_status_bookmarks;</string>
--- a/mobile/android/tests/background/junit3/src/db/TestFennecTabsRepositorySession.java
+++ b/mobile/android/tests/background/junit3/src/db/TestFennecTabsRepositorySession.java
@@ -2,16 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.db;
 
 import org.json.simple.JSONArray;
 import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
 import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
 import org.mozilla.gecko.background.sync.helpers.SessionTestHelper;
+import org.mozilla.gecko.background.testhelpers.MockClientsDataDelegate;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.sync.repositories.NoContentProviderException;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
 import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository.FennecTabsRepositorySession;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.domain.Record;
@@ -19,18 +20,19 @@ import org.mozilla.gecko.sync.repositori
 
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
 import android.os.RemoteException;
 
 public class TestFennecTabsRepositorySession extends AndroidSyncTestCase {
-  public static final String TEST_CLIENT_GUID = "test guid"; // Real GUIDs never contain spaces.
-  public static final String TEST_CLIENT_NAME = "test client name";
+  public static final MockClientsDataDelegate clientsDataDelegate = new MockClientsDataDelegate();
+  public static final String TEST_CLIENT_GUID = clientsDataDelegate.getAccountGUID();
+  public static final String TEST_CLIENT_NAME = clientsDataDelegate.getClientName();
 
   // Override these to test against data that is not live.
   public static final String TEST_TABS_CLIENT_GUID_IS_LOCAL_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS ?";
   public static final String[] TEST_TABS_CLIENT_GUID_IS_LOCAL_SELECTION_ARGS = new String[] { TEST_CLIENT_GUID };
 
   protected ContentProviderClient tabsClient = null;
 
   protected ContentProviderClient getTabsClient() {
@@ -67,17 +69,17 @@ public class TestFennecTabsRepositorySes
     }
   }
 
   protected FennecTabsRepository getRepository() {
     /**
      * Override this chain in order to avoid our test code having to create two
      * sessions all the time.
      */
-    return new FennecTabsRepository(TEST_CLIENT_NAME, TEST_CLIENT_GUID) {
+    return new FennecTabsRepository(clientsDataDelegate) {
       @Override
       public void createSession(RepositorySessionCreationDelegate delegate,
                                 Context context) {
         try {
           final FennecTabsRepositorySession session = new FennecTabsRepositorySession(this, context) {
             @Override
             protected synchronized void trackGUID(String guid) {
             }
@@ -192,14 +194,25 @@ public class TestFennecTabsRepositorySes
   public void testFetchSince() throws NoContentProviderException, RemoteException {
     final TabsRecord tabsRecord = insertTestTabsAndExtractTabsRecord();
 
     final FennecTabsRepositorySession session = createAndBeginSession();
 
     // Not all tabs are modified after this, but the record should contain them all.
     performWait(fetchSinceRunnable(session, 1000, new Record[] { tabsRecord }));
 
-    // No tabs are modified after this, so we shouldn't get a record at all.
-    performWait(fetchSinceRunnable(session, 4000, new Record[] { }));
+    // No tabs are modified after this, but our client name has changed in the interim.
+    performWait(fetchSinceRunnable(session, 4000, new Record[] { tabsRecord }));
+
+    // No tabs are modified after this, and our client name hasn't changed, so
+    // we shouldn't get a record at all. Note: this runs after our static
+    // initializer that sets the client data timestamp.
+    final long now = System.currentTimeMillis();
+    performWait(fetchSinceRunnable(session, now, new Record[] { }));
+
+    // No tabs are modified after this, but our client name has changed, so
+    // again we get a record.
+    clientsDataDelegate.setClientName("new client name", System.currentTimeMillis());
+    performWait(fetchSinceRunnable(session, now, new Record[] { tabsRecord }));
 
     session.abort();
   }
 }
--- a/mobile/android/tests/background/junit3/src/testhelpers/MockClientsDataDelegate.java
+++ b/mobile/android/tests/background/junit3/src/testhelpers/MockClientsDataDelegate.java
@@ -5,40 +5,57 @@ package org.mozilla.gecko.background.tes
 
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
 
 public class MockClientsDataDelegate implements ClientsDataDelegate {
   private String accountGUID;
   private String clientName;
   private int clientsCount;
+  private long clientDataTimestamp = 0;
 
   @Override
   public synchronized String getAccountGUID() {
     if (accountGUID == null) {
       accountGUID = Utils.generateGuid();
     }
     return accountGUID;
   }
 
   @Override
+  public synchronized String getDefaultClientName() {
+    return "Default client";
+  }
+
+  @Override
+  public synchronized void setClientName(String clientName, long now) {
+    this.clientName = clientName;
+    this.clientDataTimestamp = now;
+  }
+
+  @Override
   public synchronized String getClientName() {
     if (clientName == null) {
-      clientName = "Default Name";
+      setClientName(getDefaultClientName(), System.currentTimeMillis());
     }
     return clientName;
   }
 
   @Override
   public synchronized void setClientsCount(int clientsCount) {
     this.clientsCount = clientsCount;
   }
 
   @Override
   public synchronized int getClientsCount() {
     return clientsCount;
   }
 
   @Override
-  public boolean isLocalGUID(String guid) {
+  public synchronized boolean isLocalGUID(String guid) {
     return getAccountGUID().equals(guid);
   }
-}
\ No newline at end of file
+
+  @Override
+  public synchronized long getLastModifiedTimestamp() {
+    return clientDataTimestamp;
+  }
+}
--- a/testing/xpcshell/xpcshell_android.ini
+++ b/testing/xpcshell/xpcshell_android.ini
@@ -7,17 +7,17 @@
 [include:intl/locale/tests/unit/xpcshell.ini]
 [include:netwerk/cookie/test/unit/xpcshell.ini]
 [include:modules/libjar/zipwriter/test/unit/xpcshell.ini]
 [include:parser/xml/test/unit/xpcshell.ini]
 [include:image/test/unit/xpcshell.ini]
 [include:dom/activities/tests/unit/xpcshell.ini]
 [include:dom/apps/tests/unit/xpcshell.ini]
 [include:dom/encoding/test/unit/xpcshell.ini]
-[include:dom/mobilemessage/tests/xpcshell.ini]
+[include:dom/mobilemessage/tests/xpcshell/xpcshell.ini]
 [include:dom/network/tests/unit/xpcshell.ini]
 [include:dom/payment/tests/unit/xpcshell.ini]
 [include:dom/permission/tests/unit/xpcshell.ini]
 [include:dom/src/json/test/unit/xpcshell.ini]
 [include:dom/tests/unit/xpcshell.ini]
 [include:dom/indexedDB/test/unit/xpcshell.ini]
 [include:embedding/tests/unit/xpcshell.ini]
 [include:toolkit/components/downloads/test/schema_migration/xpcshell.ini]
--- a/testing/xpcshell/xpcshell_b2g.ini
+++ b/testing/xpcshell/xpcshell_b2g.ini
@@ -1,14 +1,14 @@
 ; This Source Code Form is subject to the terms of the Mozilla Public
 ; License, v. 2.0. If a copy of the MPL was not distributed with this
 ; file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 [include:dom/apps/tests/unit/xpcshell.ini]
-[include:dom/mobilemessage/tests/xpcshell.ini]
+[include:dom/mobilemessage/tests/xpcshell/xpcshell.ini]
 [include:dom/network/tests/unit_stats/xpcshell.ini]
 [include:dom/system/gonk/tests/xpcshell.ini]
 [include:dom/wappush/tests/xpcshell.ini]
 [include:toolkit/components/osfile/tests/xpcshell/xpcshell.ini]
 [include:toolkit/components/captivedetect/test/unit/xpcshell.ini]
 [include:toolkit/devtools/apps/tests/unit/xpcshell.ini]
 [include:toolkit/devtools/debugger/tests/unit/xpcshell.ini]
 [include:toolkit/devtools/qrcode/tests/unit/xpcshell.ini]
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4138,16 +4138,70 @@
     "description": "Number of times about:newtab was shown from opening a new tab or window."
   },
   "NEWTAB_PAGE_SITE_CLICKED": {
     "expires_in_version": "35",
     "kind": "enumerated",
     "n_values": 10,
     "description": "Track click count on about:newtab tiles per index (0-8). For non-default row or column configurations all clicks into the '9' bucket."
   },
+  "NEWTAB_PAGE_DIRECTORY_LINK0_SHOWN": {
+    "expires_in_version": "35",
+    "kind": "enumerated",
+    "n_values": 10,
+    "description": "Track impression count of directory link #0 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
+  },
+  "NEWTAB_PAGE_DIRECTORY_LINK1_SHOWN": {
+    "expires_in_version": "35",
+    "kind": "enumerated",
+    "n_values": 10,
+    "description": "Track impression count of directory link #1 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
+  },
+  "NEWTAB_PAGE_DIRECTORY_LINK2_SHOWN": {
+    "expires_in_version": "35",
+    "kind": "enumerated",
+    "n_values": 10,
+    "description": "Track impression count of directory link #2 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
+  },
+  "NEWTAB_PAGE_DIRECTORY_LINK3_SHOWN": {
+    "expires_in_version": "35",
+    "kind": "enumerated",
+    "n_values": 10,
+    "description": "Track impression count of directory link #3 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
+  },
+  "NEWTAB_PAGE_DIRECTORY_LINK4_SHOWN": {
+    "expires_in_version": "35",
+    "kind": "enumerated",
+    "n_values": 10,
+    "description": "Track impression count of directory link #4 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
+  },
+  "NEWTAB_PAGE_DIRECTORY_LINK5_SHOWN": {
+    "expires_in_version": "35",
+    "kind": "enumerated",
+    "n_values": 10,
+    "description": "Track impression count of directory link #5 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
+  },
+  "NEWTAB_PAGE_DIRECTORY_LINK6_SHOWN": {
+    "expires_in_version": "35",
+    "kind": "enumerated",
+    "n_values": 10,
+    "description": "Track impression count of directory link #6 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
+  },
+  "NEWTAB_PAGE_DIRECTORY_LINK7_SHOWN": {
+    "expires_in_version": "35",
+    "kind": "enumerated",
+    "n_values": 10,
+    "description": "Track impression count of directory link #7 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
+  },
+  "NEWTAB_PAGE_DIRECTORY_LINK8_SHOWN": {
+    "expires_in_version": "35",
+    "kind": "enumerated",
+    "n_values": 10,
+    "description": "Track impression count of directory link #8 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
+  },
   "NEWTAB_PAGE_DIRECTORY_AFFILIATE_SHOWN": {
     "expires_in_version": "35",
     "kind": "enumerated",
     "n_values": 11,
     "description": "Number of affiliate directory links shown on about:newtab. For non-default row or column configurations, extra links fall into the '10' bucket."
   },
   "NEWTAB_PAGE_DIRECTORY_ORGANIC_SHOWN": {
     "expires_in_version": "35",
--- a/toolkit/devtools/LayoutHelpers.jsm
+++ b/toolkit/devtools/LayoutHelpers.jsm
@@ -407,103 +407,9 @@ LayoutHelpers.prototype = {
       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
@@ -393,45 +393,50 @@ BoxModelHighlighter.prototype = {
     idLabel.className = "highlighter-nodeinfobar-id";
 
     let classesBox = this.chromeDoc.createElementNS(XHTML_NS, "span");
     classesBox.className = "highlighter-nodeinfobar-classes";
 
     let pseudoClassesBox = this.chromeDoc.createElementNS(XHTML_NS, "span");
     pseudoClassesBox.className = "highlighter-nodeinfobar-pseudo-classes";
 
+    let dimensionBox = this.chromeDoc.createElementNS(XHTML_NS, "span");
+    dimensionBox.className = "highlighter-nodeinfobar-dimensions";
+
     // Add some content to force a better boundingClientRect
     pseudoClassesBox.textContent = "&nbsp;";
 
     // <hbox class="highlighter-nodeinfobar-text"/>
     let texthbox = this.chromeDoc.createElement("hbox");
     texthbox.className = "highlighter-nodeinfobar-text";
     texthbox.setAttribute("align", "center");
     texthbox.setAttribute("flex", "1");
 
     texthbox.appendChild(tagNameLabel);
     texthbox.appendChild(idLabel);
     texthbox.appendChild(classesBox);
     texthbox.appendChild(pseudoClassesBox);
+    texthbox.appendChild(dimensionBox);
 
     nodeInfobar.appendChild(texthbox);
 
     infobarPositioner.appendChild(arrowBoxTop);
     infobarPositioner.appendChild(nodeInfobar);
     infobarPositioner.appendChild(arrowBoxBottom);
 
     infobarContainer.appendChild(infobarPositioner);
 
     let barHeight = infobarPositioner.getBoundingClientRect().height;
 
     this.nodeInfo = {
       tagNameLabel: tagNameLabel,
       idLabel: idLabel,
       classesBox: classesBox,
       pseudoClassesBox: pseudoClassesBox,
+      dimensionBox: dimensionBox,
       positioner: infobarPositioner,
       barHeight: barHeight,
     };
   },
 
   _createSVGNode: function(classPostfix, nodeType, parent) {
     let node = this.chromeDoc.createElementNS(SVG_NS, nodeType);
     node.setAttribute("class", "box-model-" + classPostfix);
@@ -577,29 +582,26 @@ BoxModelHighlighter.prototype = {
    * @return {boolean}
    *         True if the rectangle was highlighted, false otherwise.
    */
   _highlightBoxModel: function(options) {
     let isShown = false;
 
     options.region = options.region || "content";
 
-    // TODO: Remove this polyfill
-    this.rect =
-      this.layoutHelpers.getAdjustedQuadsPolyfill(this.currentNode, "margin");
+    this.rect = this.layoutHelpers.getAdjustedQuads(this.currentNode, "margin");
 
     if (!this.rect) {
       return null;
     }
 
     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);
+          this.layoutHelpers.getAdjustedQuads(this.currentNode, boxType);
 
         let boxNode = this._boxModelNodes[boxType];
         boxNode.setAttribute("points",
                              p1.x + "," + p1.y + " " +
                              p2.x + "," + p2.y + " " +
                              p3.x + "," + p3.y + " " +
                              p4.x + "," + p4.y);
 
@@ -700,39 +702,46 @@ BoxModelHighlighter.prototype = {
       return false;
     }
   },
 
   /**
    * Update node information (tagName#id.class)
    */
   _updateInfobar: function() {
-    if (this.currentNode) {
-      // Tag name
-      this.nodeInfo.tagNameLabel.textContent = this.currentNode.tagName;
+    if (!this.currentNode) {
+      return;
+    }
 
-      // ID
-      this.nodeInfo.idLabel.textContent = this.currentNode.id ? "#" + this.currentNode.id : "";
+    // Tag name
+    this.nodeInfo.tagNameLabel.textContent = this.currentNode.tagName;
 
-      // Classes
-      let classes = this.nodeInfo.classesBox;
+    // ID
+    this.nodeInfo.idLabel.textContent = this.currentNode.id ? "#" + this.currentNode.id : "";
+
+    // Classes
+    let classes = this.nodeInfo.classesBox;
 
-      classes.textContent = this.currentNode.classList.length ?
-                              "." + Array.join(this.currentNode.classList, ".") : "";
+    classes.textContent = this.currentNode.classList.length ?
+                            "." + Array.join(this.currentNode.classList, ".") : "";
+
+    // Pseudo-classes
+    let pseudos = PSEUDO_CLASSES.filter(pseudo => {
+      return DOMUtils.hasPseudoClassLock(this.currentNode, pseudo);
+    }, this);
 
-      // Pseudo-classes
-      let pseudos = PSEUDO_CLASSES.filter(pseudo => {
-        return DOMUtils.hasPseudoClassLock(this.currentNode, pseudo);
-      }, this);
+    let pseudoBox = this.nodeInfo.pseudoClassesBox;
+    pseudoBox.textContent = pseudos.join("");
 
-      let pseudoBox = this.nodeInfo.pseudoClassesBox;
-      pseudoBox.textContent = pseudos.join("");
-
-      this._moveInfobar();
-    }
+    // Dimensions
+    let dimensionBox = this.nodeInfo.dimensionBox;
+    let rect = this.currentNode.getBoundingClientRect();
+    dimensionBox.textContent = Math.ceil(rect.width) + " x " +
+                               Math.ceil(rect.height);
+    this._moveInfobar();
   },
 
   /**
    * Move the Infobar to the right place in the highlighter.
    */
   _moveInfobar: function() {
     if (this.rect) {
       let bounds = this.rect.bounds;
--- a/toolkit/modules/DirectoryLinksProvider.jsm
+++ b/toolkit/modules/DirectoryLinksProvider.jsm
@@ -263,16 +263,17 @@ let DirectoryLinksProvider = {
   /**
    * Gets the current set of directory links.
    * @param aCallback The function that the array of links is passed to.
    */
   getLinks: function DirectoryLinksProvider_getLinks(aCallback) {
     this._readDirectoryLinksFile().then(rawLinks => {
       // all directory links have a frecency of DIRECTORY_FRECENCY
       aCallback(rawLinks.map((link, position) => {
+        link.directoryIndex = position;
         link.frecency = DIRECTORY_FRECENCY;
         link.lastVisitDate = rawLinks.length - position;
         return link;
       }));
     });
   },
 
   init: function DirectoryLinksProvider_init() {
--- a/toolkit/modules/tests/xpcshell/test_DirectoryLinksProvider.js
+++ b/toolkit/modules/tests/xpcshell/test_DirectoryLinksProvider.js
@@ -254,38 +254,38 @@ add_task(function test_linksURL_locale()
 
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   let links;
   let expected_data;
 
   links = yield fetchData();
   do_check_eq(links.length, 1);
-  expected_data = [{url: "http://example.com", title: "US", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
+  expected_data = [{url: "http://example.com", title: "US", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1, directoryIndex: 0}];
   isIdentical(links, expected_data);
 
   yield promiseDirectoryDownloadOnPrefChange("general.useragent.locale", "zh-CN");
 
   links = yield fetchData();
   do_check_eq(links.length, 2)
   expected_data = [
-    {url: "http://example.net", title: "CN", frecency: DIRECTORY_FRECENCY, lastVisitDate: 2},
-    {url: "http://example.net/2", title: "CN2", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}
+    {url: "http://example.net", title: "CN", frecency: DIRECTORY_FRECENCY, lastVisitDate: 2, directoryIndex: 0},
+    {url: "http://example.net/2", title: "CN2", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1, directoryIndex: 1}
   ];
   isIdentical(links, expected_data);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
 add_task(function test_DirectoryLinksProvider__prefObserver_url() {
   yield promiseSetupDirectoryLinksProvider({linksURL: kTestURL});
 
   let links = yield fetchData();
   do_check_eq(links.length, 1);
-  let expectedData =  [{url: "http://example.com", title: "LocalSource", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
+  let expectedData =  [{url: "http://example.com", title: "LocalSource", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1, directoryIndex: 0}];
   isIdentical(links, expectedData);
 
   // tests these 2 things:
   // 1. _linksURL is properly set after the pref change
   // 2. invalid source url is correctly handled
   let exampleUrl = 'http://nosuchhost/bad';
   yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, exampleUrl);
   do_check_eq(DirectoryLinksProvider._linksURL, exampleUrl);