Merge m-c to inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 04 May 2015 16:11:38 -0400
changeset 273679 b491b0ab6c2ebc5a16f76e81f34543762cd92cad
parent 273678 82a9bf38481bcf9492e14abc7c1dec2c614ec776 (current diff)
parent 273628 102d0e9aa9e1629e2b448b45ac5224c2aef87ec7 (diff)
child 273680 f4f4e2be37652cb1dc23f066725a0f8868bb411b
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone40.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 m-c to inbound. a=merge
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="2eda36a4795012a5d1275b77ebb20ac377c8cd26">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="ba860ac479a07bb0265a23d210f62447e872b4fe"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="70077825aab2c7a79611befb40a5fe7e610d5443"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="ba860ac479a07bb0265a23d210f62447e872b4fe"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="70077825aab2c7a79611befb40a5fe7e610d5443"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9a9797062c6001d6346504161c51187a2968466b"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="8c2d32bccc7061e9ca0165135457c3fd53e7107e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="ba860ac479a07bb0265a23d210f62447e872b4fe"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="70077825aab2c7a79611befb40a5fe7e610d5443"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
   <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="2eda36a4795012a5d1275b77ebb20ac377c8cd26">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="ba860ac479a07bb0265a23d210f62447e872b4fe"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="70077825aab2c7a79611befb40a5fe7e610d5443"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="ba860ac479a07bb0265a23d210f62447e872b4fe"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="70077825aab2c7a79611befb40a5fe7e610d5443"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="ba860ac479a07bb0265a23d210f62447e872b4fe"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="70077825aab2c7a79611befb40a5fe7e610d5443"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9a9797062c6001d6346504161c51187a2968466b"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="8c2d32bccc7061e9ca0165135457c3fd53e7107e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="2eda36a4795012a5d1275b77ebb20ac377c8cd26">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="ba860ac479a07bb0265a23d210f62447e872b4fe"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="70077825aab2c7a79611befb40a5fe7e610d5443"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="ba860ac479a07bb0265a23d210f62447e872b4fe"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="70077825aab2c7a79611befb40a5fe7e610d5443"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
   <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": "ba860ac479a07bb0265a23d210f62447e872b4fe", 
+        "git_revision": "70077825aab2c7a79611befb40a5fe7e610d5443", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "c9b2cd8132076014f4f72ebb7b315def582f1575", 
+    "revision": "536698903e2c4c12a3ff6c6d83f70278fab7311d", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="ba860ac479a07bb0265a23d210f62447e872b4fe"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="70077825aab2c7a79611befb40a5fe7e610d5443"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="ba860ac479a07bb0265a23d210f62447e872b4fe"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="70077825aab2c7a79611befb40a5fe7e610d5443"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -25,17 +25,17 @@ var permissionObserver = {
           setPluginsRadioState();
       }
     }
   }
 };
 
 function onLoadPermission()
 {
-  var uri = gDocument.documentURIObject;
+  var uri = BrowserUtils.makeURIFromCPOW(gDocument.documentURIObject);
   var permTab = document.getElementById("permTab");
   if (SitePermissions.isSupportedURI(uri)) {
     gPermURI = uri;
     var hostText = document.getElementById("hostText");
     hostText.value = gPermURI.host;
 
     for (var i of gPermissions)
       initRow(i);
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -1,13 +1,15 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
+
 var security = {
   // Display the server certificate (static)
   viewCert : function () {
     var cert = security._cert;
     viewCertHelper(window, cert);
   },
 
   _getSecurityInfo : function() {
@@ -130,17 +132,17 @@ var security = {
   {
     var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                        .getService(Components.interfaces.nsIWindowMediator);
     var win = wm.getMostRecentWindow("Browser:Cookies");
     var eTLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"].
                       getService(Components.interfaces.nsIEffectiveTLDService);
 
     var eTLD;
-    var uri = gDocument.documentURIObject;
+    var uri = BrowserUtils.makeURIFromCPOW(gDocument.documentURIObject);
     try {
       eTLD = eTLDService.getBaseDomain(uri);
     }
     catch (e) {
       // getBaseDomain will fail if the host is an IP address or is empty
       eTLD = uri.asciiHost;
     }
 
@@ -227,17 +229,17 @@ function securityOnLoad() {
   }
   else
     viewCert.collapsed = true;
 
   /* Set Privacy & History section text */
   var yesStr = pageInfoBundle.getString("yes");
   var noStr = pageInfoBundle.getString("no");
 
-  var uri = gDocument.documentURIObject;
+  var uri = BrowserUtils.makeURIFromCPOW(gDocument.documentURIObject);
   setText("security-privacy-cookies-value",
           hostHasCookies(uri) ? yesStr : noStr);
   setText("security-privacy-passwords-value",
           realmHasPasswords(uri) ? yesStr : noStr);
   
   var visitCount = previousVisitCount(info.hostName);
   if(visitCount > 1) {
     setText("security-privacy-history-value",
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -169,30 +169,28 @@ skip-if = true # bug 428712
 [browser_bug432599.js]
 [browser_bug435035.js]
 [browser_bug435325.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1099156 - test directly manipulates content
 [browser_bug441778.js]
 skip-if = buildapp == 'mulet'
 [browser_bug455852.js]
 [browser_bug460146.js]
-skip-if = e10s # Bug 866413 - PageInfo doesn't work in e10s
 [browser_bug462289.js]
 skip-if = toolkit == "cocoa" || e10s # Bug 1102017 - middle-button mousedown on selected tab2 does not activate tab - Didn't expect [object XULElement], but got it
 [browser_bug462673.js]
 [browser_bug477014.js]
 [browser_bug479408.js]
 skip-if = buildapp == 'mulet'
 [browser_bug481560.js]
 [browser_bug484315.js]
 [browser_bug491431.js]
 skip-if = buildapp == 'mulet'
 [browser_bug495058.js]
 [browser_bug517902.js]
-skip-if = e10s # Bug 866413 - PageInfo doesn't work in e10s
 [browser_bug519216.js]
 [browser_bug520538.js]
 [browser_bug521216.js]
 [browser_bug533232.js]
 [browser_bug537013.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1134458 - Find bar doesn't work correctly in a detached tab
 [browser_bug537474.js]
 skip-if = e10s # Bug 1102020 - test tries to use browserDOMWindow.openURI to open a link, and gets a null rv where it expects a window
@@ -330,17 +328,17 @@ skip-if = os != "win" # The Fitts Law me
 skip-if = e10s # Bug 1100664 - test directly access content docShells (TypeError: gBrowser.docShell is null)
 [browser_mixedcontent_securityflags.js]
 [browser_notification_tab_switching.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1100662 - content access causing uncaught exception - Error: cannot ipc non-cpow object at chrome://mochitests/content/browser/browser/base/content/test/general/browser_notification_tab_switching.js:32 (or in RemoteAddonsChild.jsm)
 [browser_offlineQuotaNotification.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1093603 - test breaks with PopupNotifications.panel.firstElementChild is null
 [browser_overflowScroll.js]
 [browser_pageInfo.js]
-skip-if = buildapp == 'mulet' || e10s # Bug 866413 - PageInfo doesn't work in e10s
+skip-if = buildapp == 'mulet'
 [browser_page_style_menu.js]
 
 [browser_parsable_css.js]
 skip-if = e10s
 [browser_parsable_script.js]
 skip-if = asan # Disabled because it takes a long time (see test for more information)
 
 [browser_pinnedTabs.js]
--- a/browser/base/content/test/general/browser_bug427559.js
+++ b/browser/base/content/test/general/browser_bug427559.js
@@ -1,49 +1,53 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
 
 /*
  * Test bug 427559 to make sure focused elements that are no longer on the page
  * will have focus transferred to the window when changing tabs back to that
  * tab with the now-gone element.
  */
 
-// Default focus on a button and have it kill itself on blur
-let testPage = 'data:text/html,<body><button onblur="this.parentNode.removeChild(this);"><script>document.body.firstChild.focus();</script></body>';
-
-function test() {
-  waitForExplicitFinish();
-
-  gBrowser.selectedTab = gBrowser.addTab();
-  var browser = gBrowser.selectedBrowser;
+// Default focus on a button and have it kill itself on blur.
+const URL = 'data:text/html;charset=utf-8,' +
+            '<body><button onblur="this.remove()">' +
+            '<script>document.body.firstChild.focus()</script></body>';
 
-  browser.addEventListener("load", function () {
-    browser.removeEventListener("load", arguments.callee, true);
-    executeSoon(function () {
-      var testPageWin = content;
+function getFocusedLocalName(browser) {
+  return ContentTask.spawn(browser, null, function* () {
+    return content.document.activeElement.localName;
+  });
+}
 
-      is(browser.contentDocumentAsCPOW.activeElement.localName, "button", "button is focused");
+add_task(function* () {
+  gBrowser.selectedTab = gBrowser.addTab(URL);
+  let browser = gBrowser.selectedBrowser;
+  yield BrowserTestUtils.browserLoaded(browser);
 
-      addEventListener("focus", function focusedWindow(event) {
-        if (!String(event.target.location).startsWith("data:"))
-          return;
+  is((yield getFocusedLocalName(browser)), "button", "button is focused");
 
-        removeEventListener("focus", focusedWindow, true);
-
-        // Make sure focus is given to the window because the element is now gone
-        is(browser.contentDocumentAsCPOW.activeElement.localName, "body", "body is focused");
-
-        gBrowser.removeCurrentTab();
-        finish();
-      }, true);
+  let promiseFocused = ContentTask.spawn(browser, null, function* () {
+    return new Promise(resolve => {
+      content.addEventListener("focus", function onFocus({target}) {
+        if (String(target.location).startsWith("data:")) {
+          content.removeEventListener("focus", onFocus);
+          resolve();
+        }
+      });
+    });
+  });
 
-      // The test page loaded, so open an empty tab, select it, then restore
-      // the test tab. This causes the test page's focused element to be removed
-      // from its document.
-      gBrowser.selectedTab = gBrowser.addTab();
-      gBrowser.removeCurrentTab();
-    });
-  }, true);
+  // The test page loaded, so open an empty tab, select it, then restore
+  // the test tab. This causes the test page's focused element to be removed
+  // from its document.
+  gBrowser.selectedTab = gBrowser.addTab("about:blank");
+  yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+  gBrowser.removeCurrentTab();
 
-  content.location = testPage;
-}
+  // Wait until the original tab is focused again.
+  yield promiseFocused;
+
+  // Make sure focus is given to the window because the element is now gone.
+  is((yield getFocusedLocalName(browser)), "body", "body is focused");
+
+  // Cleanup.
+  gBrowser.removeCurrentTab();
+});
--- a/browser/base/content/test/plugins/browser.ini
+++ b/browser/base/content/test/plugins/browser.ini
@@ -74,17 +74,16 @@ skip-if = e10s # bug 1160166
 [browser_plugin_reloading.js]
 [browser_blocklist_content.js]
 skip-if = !e10s
 [browser_globalplugin_crashinfobar.js]
 skip-if = !crashreporter
 [browser_pluginCrashCommentAndURL.js]
 skip-if = !crashreporter
 [browser_pageInfo_plugins.js]
-skip-if = e10s # Bug 866413
 [browser_pluginplaypreview.js]
 skip-if = e10s # bug 1148827
 [browser_pluginplaypreview2.js]
 skip-if = e10s # bug 1148827
 [browser_pluginplaypreview3.js]
 skip-if = e10s # bug 1148827
 [browser_pluginCrashReportNonDeterminism.js]
 skip-if = !crashreporter || os == 'linux' # Bug 1152811
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -463,20 +463,24 @@ let gEditItemOverlay = {
                  this._paneInfo.uris : [this._paneInfo.uri];
     let currentTags = this._paneInfo.bulkTagging ?
                         yield this._getCommonTags() :
                         PlacesUtils.tagging.getTagsForURI(uris[0]);
     let anyChanges = yield this._setTagsFromTagsInputField(currentTags, uris);
     if (!anyChanges)
       return false;
 
+    // The panel could have been closed in the meanwhile.
+    if (!this._paneInfo)
+      return false;
+
     // Ensure the tagsField is in sync, clean it up from empty tags
     currentTags = this._paneInfo.bulkTagging ?
                     this._getCommonTags() :
-                    PlacesUtils.tagging.getTagsForURI(this._uri);
+                    PlacesUtils.tagging.getTagsForURI(this._paneInfo.uri);
     this._initTextField(this._tagsField, currentTags.join(", "), false);
     return true;
   }),
 
   /**
    * Stores the first-edit field for this dialog, if the passed-in field
    * is indeed the first edited field
    * @param aNewField
@@ -500,17 +504,17 @@ let gEditItemOverlay = {
 
   onNamePickerChange() {
     if (this.readOnly || !this._paneInfo.isItem)
       return;
 
     // Here we update either the item title or its cached static title
     let newTitle = this._namePicker.value;
     if (!newTitle &&
-        PlacesUtils.bookmarks.getFolderIdForItem(itemId) == PlacesUtils.tagsFolderId) {
+        PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) == PlacesUtils.tagsFolderId) {
       // We don't allow setting an empty title for a tag, restore the old one.
       this._initNamePicker();
     }
     else {
       this._mayUpdateFirstEditField("namePicker");
       if (!PlacesUIUtils.useAsyncTransactions) {
         let txn = new PlacesEditItemTitleTransaction(this._paneInfo.itemId,
                                                      newTitle);
@@ -651,27 +655,27 @@ let gEditItemOverlay = {
    * Get the corresponding menu-item in the folder-menu-list for a bookmarks
    * folder if such an item exists. Otherwise, this creates a menu-item for the
    * folder. If the items-count limit (see MAX_FOLDERS_IN_MENU_LIST) is reached,
    * the new item replaces the last menu-item.
    * @param aFolderId
    *        The identifier of the bookmarks folder.
    */
   _getFolderMenuItem(aFolderId) {
-    let menuPopup = this._folderMenuList.menupopup;
+    let menupopup = this._folderMenuList.menupopup;
     let menuItem = Array.prototype.find.call(
-      menuPopup.childNodes, menuItem => menuItem.folderId === aFolderId);
+      menupopup.childNodes, menuItem => menuItem.folderId === aFolderId);
     if (menuItem !== undefined)
       return menuItem;
 
     // 3 special folders + separator + folder-items-count limit
     if (menupopup.childNodes.length == 4 + MAX_FOLDER_ITEM_IN_MENU_LIST)
-      menupopup.removeChild(menuPopup.lastChild);
+      menupopup.removeChild(menupopup.lastChild);
 
-    return this._appendFolderItemToMenupopup(menuPopup, aFolderId);
+    return this._appendFolderItemToMenupopup(menupopup, aFolderId);
   },
 
   onFolderMenuListCommand(aEvent) {
     // Set a selectedIndex attribute to show special icons
     this._folderMenuList.setAttribute("selectedIndex",
                                       this._folderMenuList.selectedIndex);
 
     if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") {
@@ -683,17 +687,18 @@ let gEditItemOverlay = {
       // XXXmano HACK: setTimeout 100, otherwise focus goes back to the
       // menulist right away
       setTimeout(function(self) self.toggleFolderTreeVisibility(), 100, this);
       return;
     }
 
     // Move the item
     let containerId = this._getFolderIdFromMenuList();
-    if (PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) != containerId) {
+    if (PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) != containerId &&
+        this._paneInfo.itemId != containerId) {
       if (PlacesUIUtils.useAsyncTransactions) {
         Task.spawn(function* () {
           let newParentGuid = yield PlacesUtils.promiseItemGuid(containerId);
           let guid = this._paneInfo.itemGuid;
           yield PlacesTransactions.Move({ guid, newParentGuid }).transact();
         }.bind(this));
       }
       else {
@@ -703,28 +708,28 @@ let gEditItemOverlay = {
         PlacesUtils.transactionManager.doTransaction(txn);
       }
 
       // Mark the containing folder as recently-used if it isn't in the
       // static list
       if (containerId != PlacesUtils.unfiledBookmarksFolderId &&
           containerId != PlacesUtils.toolbarFolderId &&
           containerId != PlacesUtils.bookmarksMenuFolderId) {
-        this._markFolderAsRecentlyUsed(container)
+        this._markFolderAsRecentlyUsed(containerId)
             .catch(Components.utils.reportError);
       }
     }
 
     // Update folder-tree selection
     var folderTreeRow = this._element("folderTreeRow");
     if (!folderTreeRow.collapsed) {
       var selectedNode = this._folderTree.selectedNode;
       if (!selectedNode ||
-          PlacesUtils.getConcreteItemId(selectedNode) != container)
-        this._folderTree.selectItems([container]);
+          PlacesUtils.getConcreteItemId(selectedNode) != containerId)
+        this._folderTree.selectItems([containerId]);
     }
   },
 
   onFolderTreeSelect() {
     var selectedNode = this._folderTree.selectedNode;
 
     // Disable the "New Folder" button if we cannot create a new folder
     this._element("newFolderButton")
@@ -745,23 +750,25 @@ let gEditItemOverlay = {
   _markFolderAsRecentlyUsed: Task.async(function* (aFolderId) {
     if (!PlacesUIUtils.useAsyncTransactions) {
       let txns = [];
 
       // Expire old unused recent folders.
       let annotation = this._getLastUsedAnnotationObject(false);
       while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) {
         let folderId = this._recentFolders.pop().folderId;
-        let annoTxn = new PlacesSetItemAnnotationTransaction(folderId, anno);
+        let annoTxn = new PlacesSetItemAnnotationTransaction(folderId,
+                                                             annotation);
         txns.push(annoTxn);
       }
 
       // Mark folder as recently used
       annotation = this._getLastUsedAnnotationObject(true);
-      let annoTxn = new PlacesSetItemAnnotationTransaction(aFolderId, anno);
+      let annoTxn = new PlacesSetItemAnnotationTransaction(aFolderId,
+                                                           annotation);
       txns.push(annoTxn);
 
       let aggregate =
         new PlacesAggregatedTransaction("Update last used folders", txns);
       PlacesUtils.transactionManager.doTransaction(aggregate);
       return;
     }
 
@@ -978,45 +985,47 @@ let gEditItemOverlay = {
       this._rebuildTagsSelectorList().catch(Components.utils.reportError);
   },
 
   _onItemTitleChange(aItemId, aNewTitle) {
     if (!this._paneInfo.isBookmark)
       return;
     if (aItemId == this._paneInfo.itemId) {
       this._paneInfo.title = aNewTitle;
-      this._initTextField(this._namePicker);
+      this._initTextField(this._namePicker, aNewTitle);
     }
     else if (this._paneInfo.visibleRows.has("folderRow")) {
       // If the title of a folder which is listed within the folders
       // menulist has been changed, we need to update the label of its
       // representing element.
       let menupopup = this._folderMenuList.menupopup;
       for (menuitem of menupopup.childNodes) {
-        if ("folderId" in menuItem && menuItem.folderId == aItemId) {
+        if ("folderId" in menuitem && menuitem.folderId == aItemId) {
           menuitem.label = aNewTitle;
           break;
         }
       }
     }
   },
 
   // nsINavBookmarkObserver
   onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aValue,
                 aLastModified, aItemType) {
-    if (aProperty == "tags" && this._paneInfo.visibleRows.has("tagsRow"))
+    if (aProperty == "tags" && this._paneInfo.visibleRows.has("tagsRow")) {
       this._onTagsChange(aItemId);
-    else if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId)
+    }
+    else if (aProperty == "title" && this._paneInfo.isItem) {
+      // This also updates titles of folders in the folder menu list.
+      this._onItemTitleChange(aItemId, aValue);
+    }
+    else if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId) {
       return;
+    }
 
     switch (aProperty) {
-    case "title":
-      if (this._paneInfo.isItem)
-        this._onItemTitleChange(aItemId, aValue);
-      break;
     case "uri":
       let newURI = NetUtil.newURI(aValue);
       if (!newURI.equals(this._paneInfo.uri)) {
         this._paneInfo.uri = newURI;
         if (this._paneInfo.visibleRows.has("locationRow"))
           this._initLocationField();
 
         if (this._paneInfo.visibleRows.has("tagsRow")) {
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -1220,17 +1220,16 @@ var ViewMenu = {
     //   dir:  Default sort direction to use if none has been specified
     //   anno: The annotation to sort by, if key is "ANNOTATION"
     var colLookupTable = {
       title:        { key: "TITLE",        dir: "ascending"  },
       tags:         { key: "TAGS",         dir: "ascending"  },
       url:          { key: "URI",          dir: "ascending"  },
       date:         { key: "DATE",         dir: "descending" },
       visitCount:   { key: "VISITCOUNT",   dir: "descending" },
-      keyword:      { key: "KEYWORD",      dir: "ascending"  },
       dateAdded:    { key: "DATEADDED",    dir: "descending" },
       lastModified: { key: "LASTMODIFIED", dir: "descending" },
       description:  { key: "ANNOTATION",
                       dir: "ascending",
                       anno: PlacesUIUtils.DESCRIPTION_ANNO }
     };
 
     // Make sure we have a valid column.
--- a/browser/components/places/content/places.xul
+++ b/browser/components/places/content/places.xul
@@ -385,19 +385,16 @@
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
             <treecol label="&col.mostrecentvisit.label;" id="placesContentDate" anonid="date" flex="1" hidden="true"
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
             <treecol label="&col.visitcount.label;" id="placesContentVisitCount" anonid="visitCount" flex="1" hidden="true"
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
-            <treecol label="&col.keyword.label;" id="placesContentKeyword" anonid="keyword" flex="1" hidden="true"
-                      persist="width hidden ordinal sortActive sortDirection"/>
-            <splitter class="tree-splitter"/>
             <treecol label="&col.description.label;" id="placesContentDescription" anonid="description" flex="1" hidden="true"
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
             <treecol label="&col.dateadded.label;" id="placesContentDateAdded" anonid="dateAdded" flex="1" hidden="true"
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
             <treecol label="&col.lastmodified.label;" id="placesContentLastModified" anonid="lastModified" flex="1" hidden="true"
                       persist="width hidden ordinal sortActive sortDirection"/>
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -513,36 +513,33 @@ PlacesTreeView.prototype = {
       timeObj.getMinutes(), timeObj.getSeconds()));
   },
 
   COLUMN_TYPE_UNKNOWN: 0,
   COLUMN_TYPE_TITLE: 1,
   COLUMN_TYPE_URI: 2,
   COLUMN_TYPE_DATE: 3,
   COLUMN_TYPE_VISITCOUNT: 4,
-  COLUMN_TYPE_KEYWORD: 5,
-  COLUMN_TYPE_DESCRIPTION: 6,
-  COLUMN_TYPE_DATEADDED: 7,
-  COLUMN_TYPE_LASTMODIFIED: 8,
-  COLUMN_TYPE_TAGS: 9,
+  COLUMN_TYPE_DESCRIPTION: 5,
+  COLUMN_TYPE_DATEADDED: 6,
+  COLUMN_TYPE_LASTMODIFIED: 7,
+  COLUMN_TYPE_TAGS: 8,
 
   _getColumnType: function PTV__getColumnType(aColumn) {
     let columnType = aColumn.element.getAttribute("anonid") || aColumn.id;
 
     switch (columnType) {
       case "title":
         return this.COLUMN_TYPE_TITLE;
       case "url":
         return this.COLUMN_TYPE_URI;
       case "date":
         return this.COLUMN_TYPE_DATE;
       case "visitCount":
         return this.COLUMN_TYPE_VISITCOUNT;
-      case "keyword":
-        return this.COLUMN_TYPE_KEYWORD;
       case "description":
         return this.COLUMN_TYPE_DESCRIPTION;
       case "dateAdded":
         return this.COLUMN_TYPE_DATEADDED;
       case "lastModified":
         return this.COLUMN_TYPE_LASTMODIFIED;
       case "tags":
         return this.COLUMN_TYPE_TAGS;
@@ -563,20 +560,16 @@ PlacesTreeView.prototype = {
       case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_ASCENDING:
         return [this.COLUMN_TYPE_URI, false];
       case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_DESCENDING:
         return [this.COLUMN_TYPE_URI, true];
       case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_ASCENDING:
         return [this.COLUMN_TYPE_VISITCOUNT, false];
       case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING:
         return [this.COLUMN_TYPE_VISITCOUNT, true];
-      case Ci.nsINavHistoryQueryOptions.SORT_BY_KEYWORD_ASCENDING:
-        return [this.COLUMN_TYPE_KEYWORD, false];
-      case Ci.nsINavHistoryQueryOptions.SORT_BY_KEYWORD_DESCENDING:
-        return [this.COLUMN_TYPE_KEYWORD, true];
       case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_ASCENDING:
         if (this._result.sortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO)
           return [this.COLUMN_TYPE_DESCRIPTION, false];
         break;
       case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_DESCENDING:
         if (this._result.sortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO)
           return [this.COLUMN_TYPE_DESCRIPTION, true];
       case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_ASCENDING:
@@ -844,19 +837,17 @@ PlacesTreeView.prototype = {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATE);
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_VISITCOUNT);
   },
 
   nodeTagsChanged: function PTV_nodeTagsChanged(aNode) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_TAGS);
   },
 
-  nodeKeywordChanged: function PTV_nodeKeywordChanged(aNode, aNewKeyword) {
-    this._invalidateCellValue(aNode, this.COLUMN_TYPE_KEYWORD);
-  },
+  nodeKeywordChanged(aNode, aNewKeyword) {},
 
   nodeAnnotationChanged: function PTV_nodeAnnotationChanged(aNode, aAnno) {
     if (aAnno == PlacesUIUtils.DESCRIPTION_ANNO) {
       this._invalidateCellValue(aNode, this.COLUMN_TYPE_DESCRIPTION);
     }
     else if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
       PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
         .then(aLivemark => {
@@ -1439,20 +1430,16 @@ PlacesTreeView.prototype = {
           // I expect, and gives me no information I know how to use.
           // Only show this for URI-based items.
           return "";
         }
 
         return this._convertPRTimeToString(nodeTime);
       case this.COLUMN_TYPE_VISITCOUNT:
         return node.accessCount;
-      case this.COLUMN_TYPE_KEYWORD:
-        if (PlacesUtils.nodeIsBookmark(node))
-          return PlacesUtils.bookmarks.getKeywordForBookmark(node.itemId);
-        return "";
       case this.COLUMN_TYPE_DESCRIPTION:
         if (node.itemId != -1) {
           try {
             return PlacesUtils.annotations.
                                getItemAnnotation(node.itemId, PlacesUIUtils.DESCRIPTION_ANNO);
           }
           catch (ex) { /* has no description */ }
         }
@@ -1578,25 +1565,16 @@ PlacesTreeView.prototype = {
         if (oldSort == NHQO.SORT_BY_VISITCOUNT_DESCENDING)
           newSort = NHQO.SORT_BY_VISITCOUNT_ASCENDING;
         else if (allowTriState && oldSort == NHQO.SORT_BY_VISITCOUNT_ASCENDING)
           newSort = NHQO.SORT_BY_NONE;
         else
           newSort = NHQO.SORT_BY_VISITCOUNT_DESCENDING;
 
         break;
-      case this.COLUMN_TYPE_KEYWORD:
-        if (oldSort == NHQO.SORT_BY_KEYWORD_ASCENDING)
-          newSort = NHQO.SORT_BY_KEYWORD_DESCENDING;
-        else if (allowTriState && oldSort == NHQO.SORT_BY_KEYWORD_DESCENDING)
-          newSort = NHQO.SORT_BY_NONE;
-        else
-          newSort = NHQO.SORT_BY_KEYWORD_ASCENDING;
-
-        break;
       case this.COLUMN_TYPE_DESCRIPTION:
         if (oldSort == NHQO.SORT_BY_ANNOTATION_ASCENDING &&
             oldSortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO) {
           newSort = NHQO.SORT_BY_ANNOTATION_DESCENDING;
           newSortingAnnotation = PlacesUIUtils.DESCRIPTION_ANNO;
         }
         else if (allowTriState &&
                  oldSort == NHQO.SORT_BY_ANNOTATION_DESCENDING &&
--- a/browser/components/places/tests/browser/browser_sort_in_library.js
+++ b/browser/components/places/tests/browser/browser_sort_in_library.js
@@ -30,17 +30,16 @@
 // SORT_BY_<key>_<dir>) and sortingAnnotation is checked against anno.  anno
 // may be undefined if key is not "ANNOTATION".
 const SORT_LOOKUP_TABLE = {
   title:        { key: "TITLE",        dir: "ASCENDING"  },
   tags:         { key: "TAGS",         dir: "ASCENDING"  },
   url:          { key: "URI",          dir: "ASCENDING"  },
   date:         { key: "DATE",         dir: "DESCENDING" },
   visitCount:   { key: "VISITCOUNT",   dir: "DESCENDING" },
-  keyword:      { key: "KEYWORD",      dir: "ASCENDING"  },
   dateAdded:    { key: "DATEADDED",    dir: "DESCENDING" },
   lastModified: { key: "LASTMODIFIED", dir: "DESCENDING" },
   description:  { key:  "ANNOTATION",
                   dir:  "ASCENDING",
                   anno: "bookmarkProperties/description" }
 };
 
 // This is the column that's sorted if one is not specified and the tree is
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -567,17 +567,21 @@ let gDevToolsBrowser = {
    * This function is for the benefit of Tools:DevToolbox in
    * browser/base/content/browser-sets.inc and should not be used outside
    * of there
    */
   toggleToolboxCommand: function(gBrowser) {
     let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
     let toolbox = gDevTools.getToolbox(target);
 
-    toolbox ? toolbox.destroy() : gDevTools.showToolbox(target);
+    // If a toolbox exists, using toggle from the Main window :
+    // - should close a docked toolbox
+    // - should focus a windowed toolbox
+    let isDocked = toolbox && toolbox.hostType != devtools.Toolbox.HostType.WINDOW;
+    isDocked ? toolbox.destroy() : gDevTools.showToolbox(target);
   },
 
   toggleBrowserToolboxCommand: function(gBrowser) {
     let target = devtools.TargetFactory.forWindow(gBrowser.ownerDocument.defaultView);
     let toolbox = gDevTools.getToolbox(target);
 
     toolbox ? toolbox.destroy()
      : gDevTools.showToolbox(target, "inspector", Toolbox.HostType.WINDOW);
--- a/browser/devtools/framework/test/browser.ini
+++ b/browser/devtools/framework/test/browser.ini
@@ -42,16 +42,17 @@ skip-if = e10s # Bug 1030318
 [browser_toolbox_select_event.js]
 skip-if = e10s # Bug 1069044 - destroyInspector may hang during shutdown
 [browser_toolbox_sidebar.js]
 [browser_toolbox_sidebar_events.js]
 [browser_toolbox_sidebar_existing_tabs.js]
 [browser_toolbox_sidebar_overflow_menu.js]
 [browser_toolbox_tabsswitch_shortcuts.js]
 [browser_toolbox_textbox_context_menu.js]
+[browser_toolbox_toggle.js]
 [browser_toolbox_tool_ready.js]
 [browser_toolbox_tool_remote_reopen.js]
 [browser_toolbox_transport_events.js]
 [browser_toolbox_view_source_01.js]
 [browser_toolbox_view_source_02.js]
 [browser_toolbox_view_source_03.js]
 [browser_toolbox_view_source_04.js]
 [browser_toolbox_window_reload_target.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/browser_toolbox_toggle.js
@@ -0,0 +1,86 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+const URL = "data:text/html;charset=utf-8,Test toggling devtools using keyboard shortcuts";
+
+add_task(function*() {
+  // Test with ACCEL+SHIFT+I / ACCEL+ALT+I (MacOSX) ; modifiers should match :
+  // - toolbox-key-toggle in browser/devtools/framework/toolbox-window.xul
+  // - key_devToolboxMenuItem in browser/base/content/browser.xul
+  info('Test toggle using CTRL+SHIFT+I/CMD+ALT+I');
+  yield testToggle('I', {
+    accelKey : true,
+    shiftKey : !navigator.userAgent.match(/Mac/),
+    altKey : navigator.userAgent.match(/Mac/),
+  });
+  // Test with F12 ; no modifiers
+  info('Test toggle using F12');
+  yield testToggle('VK_F12', {});
+});
+
+function* testToggle(key, modifiers) {
+  let tab = yield addTab(URL + " ; key : '" + key + "'");
+  yield gDevTools.showToolbox(TargetFactory.forTab(tab));
+
+  yield testToggleDockedToolbox(tab, key, modifiers);
+
+  yield testToggleDetachedToolbox(tab, key, modifiers);
+
+  yield cleanup();
+}
+
+function* testToggleDockedToolbox (tab, key, modifiers) {
+  let toolbox = getToolboxForTab(tab);
+
+  isnot(toolbox.hostType, devtools.Toolbox.HostType.WINDOW, "Toolbox is docked in the main window");
+
+  info('verify docked toolbox is destroyed when using toggle key');
+  let onToolboxDestroyed = once(gDevTools, "toolbox-destroyed");
+  EventUtils.synthesizeKey(key, modifiers);
+  yield onToolboxDestroyed;
+  ok(true, "Docked toolbox is destroyed when using a toggle key");
+
+  info('verify new toolbox is created when using toggle key');
+  let onToolboxReady = once(gDevTools, "toolbox-ready");
+  EventUtils.synthesizeKey(key, modifiers);
+  yield onToolboxReady;
+  ok(true, "Toolbox is created by using when toggle key");
+}
+
+function* testToggleDetachedToolbox (tab, key, modifiers) {
+  let toolbox = getToolboxForTab(tab);
+
+  info('change the toolbox hostType to WINDOW');
+  yield toolbox.switchHost(devtools.Toolbox.HostType.WINDOW);
+  is(toolbox.hostType, devtools.Toolbox.HostType.WINDOW, "Toolbox opened on separate window");
+
+  let toolboxWindow = toolbox._host._window;
+  info('Wait for focus on the toolbox window')
+  yield new Promise(resolve => waitForFocus(resolve, toolboxWindow));
+
+  info('Focus main window')
+  let onMainWindowFocus = once(window, "focus");
+  window.focus();
+  yield onMainWindowFocus;
+  ok(true, "Main window focused");
+
+  info('verify windowed toolbox is focused when using toggle key from the main window')
+  let onToolboxWindowFocus = once(toolboxWindow, "focus");
+  EventUtils.synthesizeKey(key, modifiers);
+  yield onToolboxWindowFocus;
+  ok(true, "Toolbox focused and not destroyed");
+
+  info('verify windowed toolbox is destroyed when using toggle key from its own window')
+  let onToolboxDestroyed = once(gDevTools, "toolbox-destroyed");
+  EventUtils.synthesizeKey(key, modifiers, toolboxWindow);
+  yield onToolboxDestroyed;
+  ok(true, "Toolbox destroyed");
+}
+
+function getToolboxForTab(tab) {
+  return gDevTools.getToolbox(TargetFactory.forTab(tab));
+}
+
+function* cleanup(toolbox) {
+  Services.prefs.setCharPref("devtools.toolbox.host", devtools.Toolbox.HostType.BOTTOM);
+  gBrowser.removeCurrentTab();
+}
--- a/browser/devtools/framework/toolbox-window.xul
+++ b/browser/devtools/framework/toolbox-window.xul
@@ -21,12 +21,25 @@
     <command id="toolbox-cmd-close" oncommand="window.close();"/>
   </commandset>
 
   <keyset id="toolbox-keyset">
     <key id="toolbox-key-close"
          key="&closeCmd.key;"
          command="toolbox-cmd-close"
          modifiers="accel"/>
+    <key id="toolbox-key-toggle"
+         key="&toggleToolbox.key;"
+         command="toolbox-cmd-close"
+#ifdef XP_MACOSX
+         modifiers="accel,alt"
+#else
+         modifiers="accel,shift"
+#endif
+        />
+    <key id="toolbox-key-toggle-F12"
+         keycode="&toggleToolboxF12.keycode;"
+         keytext="&toggleToolboxF12.keytext;"
+         command="toolbox-cmd-close"/>
   </keyset>
 
   <iframe id="toolbox-iframe" flex="1" forceOwnRefreshDriver=""></iframe>
 </window>
--- a/browser/devtools/inspector/inspector-panel.js
+++ b/browser/devtools/inspector/inspector-panel.js
@@ -769,33 +769,40 @@ InspectorPanel.prototype = {
 
     // Get information about the right-clicked node.
     let popupNode = this.panelDoc.popupNode;
     if (!popupNode || !popupNode.classList.contains("link")) {
       return;
     }
 
     let type = popupNode.dataset.type;
-    // Bug 1158822 will make "resource" type URLs open in devtools, but for now
-    // they're considered like "uri".
-    if (type === "uri" || type === "resource") {
+    if (type === "uri" || type === "cssresource" || type === "jsresource") {
       // First make sure the target can resolve relative URLs.
       this.target.actorHasMethod("inspector", "resolveRelativeURL").then(canResolve => {
         if (!canResolve) {
           return;
         }
 
         linkSeparator.removeAttribute("hidden");
 
         // Links can't be opened in new tabs in the browser toolbox.
-        if (!this.target.chrome) {
+        if (type === "uri" && !this.target.chrome) {
           linkFollow.removeAttribute("hidden");
           linkFollow.setAttribute("label", this.strings.GetStringFromName(
             "inspector.menu.openUrlInNewTab.label"));
+        } else if (type === "cssresource") {
+          linkFollow.removeAttribute("hidden");
+          linkFollow.setAttribute("label", this.toolboxStrings.GetStringFromName(
+            "toolbox.viewCssSourceInStyleEditor.label"));
+        } else if (type === "jsresource") {
+          linkFollow.removeAttribute("hidden");
+          linkFollow.setAttribute("label", this.toolboxStrings.GetStringFromName(
+            "toolbox.viewJsSourceInDebugger.label"));
         }
+
         linkCopy.removeAttribute("hidden");
         linkCopy.setAttribute("label", this.strings.GetStringFromName(
           "inspector.menu.copyUrlToClipboard.label"));
       }, console.error);
     } else if (type === "idref") {
       linkSeparator.removeAttribute("hidden");
       linkFollow.removeAttribute("hidden");
       linkFollow.setAttribute("label", this.strings.formatStringFromName(
@@ -1094,36 +1101,41 @@ InspectorPanel.prototype = {
    * This method is here for the benefit of the node-menu-link-follow menu item
    * in the inspector contextual-menu. It's behavior depends on which node was
    * right-clicked when the menu was opened.
    */
   followAttributeLink: function InspectorPanel_followLink(e) {
     let type = this.panelDoc.popupNode.dataset.type;
     let link = this.panelDoc.popupNode.dataset.link;
 
-    // "resource" type links should open appropriate tool instead (bug 1158822).
-    if (type === "uri" || type === "resource") {
+    if (type === "uri" || type === "cssresource" || type === "jsresource") {
       // Open link in a new tab.
       // When the inspector menu was setup on click (see _setupNodeLinkMenu), we
       // already checked that resolveRelativeURL existed.
       this.inspector.resolveRelativeURL(link, this.selection.nodeFront).then(url => {
-        let browserWin = this.target.tab.ownerDocument.defaultView;
-        browserWin.openUILinkIn(url, "tab");
-      }, console.error);
+        if (type === "uri") {
+          let browserWin = this.target.tab.ownerDocument.defaultView;
+          browserWin.openUILinkIn(url, "tab");
+        } else if (type === "cssresource") {
+          return this.toolbox.viewSourceInStyleEditor(url);
+        } else if (type === "jsresource") {
+          return this.toolbox.viewSourceInDebugger(url);
+        }
+      }).catch(e => console.error(e));
     } else if (type == "idref") {
       // Select the node in the same document.
       this.walker.document(this.selection.nodeFront).then(doc => {
-        this.walker.querySelector(doc, "#" + CSS.escape(link)).then(node => {
+        return this.walker.querySelector(doc, "#" + CSS.escape(link)).then(node => {
           if (!node) {
             this.emit("idref-attribute-link-failed");
             return;
           }
           this.selection.setNodeFront(node);
-        }, console.error);
-      }, console.error);
+        });
+      }).catch(e => console.error(e));
     }
   },
 
   /**
    * This method is here for the benefit of the node-menu-link-copy menu item
    * in the inspector contextual-menu. It's behavior depends on which node was
    * right-clicked when the menu was opened.
    */
@@ -1174,23 +1186,26 @@ InspectorPanel.prototype = {
       delete this._timer;
     }
   }
 };
 
 /////////////////////////////////////////////////////////////////////////
 //// Initializers
 
-loader.lazyGetter(InspectorPanel.prototype, "strings",
-  function () {
-    return Services.strings.createBundle(
-            "chrome://browser/locale/devtools/inspector.properties");
-  });
+loader.lazyGetter(InspectorPanel.prototype, "strings", function () {
+  return Services.strings.createBundle(
+    "chrome://browser/locale/devtools/inspector.properties");
+});
+
+loader.lazyGetter(InspectorPanel.prototype, "toolboxStrings", function () {
+  return Services.strings.createBundle(
+    "chrome://browser/locale/devtools/toolbox.properties");
+});
 
 loader.lazyGetter(this, "clipboardHelper", function() {
   return Cc["@mozilla.org/widget/clipboardhelper;1"].
     getService(Ci.nsIClipboardHelper);
 });
 
-
 loader.lazyGetter(this, "DOMUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -113,17 +113,17 @@ browser.jar:
     content/browser/devtools/performance/views/details-memory-flamegraph.js (performance/views/details-memory-flamegraph.js)
     content/browser/devtools/performance/views/recordings.js           (performance/views/recordings.js)
     content/browser/devtools/performance/views/jit-optimizations.js    (performance/views/jit-optimizations.js)
     content/browser/devtools/responsivedesign/resize-commands.js       (responsivedesign/resize-commands.js)
     content/browser/devtools/commandline.css                           (commandline/commandline.css)
     content/browser/devtools/commandlineoutput.xhtml                   (commandline/commandlineoutput.xhtml)
     content/browser/devtools/commandlinetooltip.xhtml                  (commandline/commandlinetooltip.xhtml)
     content/browser/devtools/commandline/commands-index.js             (commandline/commands-index.js)
-    content/browser/devtools/framework/toolbox-window.xul              (framework/toolbox-window.xul)
+*   content/browser/devtools/framework/toolbox-window.xul              (framework/toolbox-window.xul)
     content/browser/devtools/framework/toolbox-options.xul             (framework/toolbox-options.xul)
     content/browser/devtools/framework/toolbox-options.js              (framework/toolbox-options.js)
     content/browser/devtools/framework/toolbox.xul                     (framework/toolbox.xul)
     content/browser/devtools/framework/options-panel.css               (framework/options-panel.css)
     content/browser/devtools/framework/toolbox-process-window.xul      (framework/toolbox-process-window.xul)
 *   content/browser/devtools/framework/toolbox-process-window.js       (framework/toolbox-process-window.js)
     content/browser/devtools/framework/dev-edition-promo.xul           (framework/dev-edition-promo/dev-edition-promo.xul)
 *   content/browser/devtools/framework/dev-edition-promo.css           (framework/dev-edition-promo/dev-edition-promo.css)
--- a/browser/devtools/markupview/markup-view.xhtml
+++ b/browser/devtools/markupview/markup-view.xhtml
@@ -80,17 +80,17 @@
      --><span save="${name}" class="attr-name theme-fg-color2"></span><!--
      -->=&quot;<!--
      --><span save="${val}" class="attr-value theme-fg-color6"></span><!--
      -->&quot;<!--
    --></span><!--
  --></span>
 
     <span id="template-text" save="${elt}" class="editor text">
-      <pre save="${value}" style="display:inline-block;" tabindex="0"></pre>
+      <pre save="${value}" style="display:inline-block; white-space: normal;" tabindex="0"></pre>
     </span>
 
     <span id="template-comment"
           save="${elt}"
           class="editor comment theme-comment"><!--
    --><span>&lt;!--</span><!--
    --><pre save="${value}" style="display:inline-block;" tabindex="0"></pre><!--
    --><span>--&gt;</span><!--
--- a/browser/devtools/markupview/test/browser.ini
+++ b/browser/devtools/markupview/test/browser.ini
@@ -69,16 +69,17 @@ skip-if = e10s # Bug 1040751 - CodeMirro
 skip-if = e10s # Bug 1040751 - CodeMirror editor.destroy() isn't e10s compatible
 [browser_markupview_events_jquery_2.1.1.js]
 skip-if = e10s # Bug 1040751 - CodeMirror editor.destroy() isn't e10s compatible
 [browser_markupview_links_01.js]
 [browser_markupview_links_02.js]
 [browser_markupview_links_03.js]
 [browser_markupview_links_04.js]
 [browser_markupview_links_05.js]
+[browser_markupview_links_06.js]
 [browser_markupview_load_01.js]
 [browser_markupview_html_edit_01.js]
 [browser_markupview_html_edit_02.js]
 [browser_markupview_html_edit_03.js]
 [browser_markupview_image_tooltip.js]
 [browser_markupview_keybindings_01.js]
 [browser_markupview_keybindings_02.js]
 [browser_markupview_keybindings_03.js]
--- a/browser/devtools/markupview/test/browser_markupview_links_01.js
+++ b/browser/devtools/markupview/test/browser_markupview_links_01.js
@@ -8,17 +8,17 @@
 // values) are URIs or pointers to IDs.
 
 const TEST_URL = TEST_URL_ROOT + "doc_markup_links.html";
 
 const TEST_DATA = [{
   selector: "link",
   attributes: [{
     attributeName: "href",
-    links: [{type: "resource", value: "style.css"}]
+    links: [{type: "cssresource", value: "style.css"}]
   }]
 }, {
   selector: "link[rel=icon]",
   attributes: [{
     attributeName: "href",
     links: [{type: "uri", value: "/media/img/firefox/favicon-196.223e1bcaf067.png"}]
   }]
 }, {
@@ -90,17 +90,17 @@ const TEST_DATA = [{
   }, {
     attributeName: "src",
     links: [{type: "uri", value: "code-rush.mp4"}]
   }]
 }, {
   selector: "script",
   attributes: [{
     attributeName: "src",
-    links: [{type: "resource", value: "lib_jquery_1.0.js"}]
+    links: [{type: "jsresource", value: "lib_jquery_1.0.js"}]
   }]
 }];
 
 add_task(function*() {
   let {inspector} = yield addTab(TEST_URL).then(openInspector);
 
   for (let {selector, attributes} of TEST_DATA) {
     info("Testing attributes on node " + selector);
--- a/browser/devtools/markupview/test/browser_markupview_links_04.js
+++ b/browser/devtools/markupview/test/browser_markupview_links_04.js
@@ -5,33 +5,35 @@
 "use strict";
 
 // Tests that the contextual menu shows the right items when clicking on a link
 // in an attribute.
 
 const TEST_URL = TEST_URL_ROOT + "doc_markup_links.html";
 const STRINGS = Services.strings
   .createBundle("chrome://browser/locale/devtools/inspector.properties");
+const TOOLBOX_STRINGS = Services.strings
+  .createBundle("chrome://browser/locale/devtools/toolbox.properties");
 
 // The test case array contains objects with the following properties:
 // - selector: css selector for the node to select in the inspector
 // - attributeName: name of the attribute to test
 // - popupNodeSelector: css selector for the element inside the attribute
 //   element to use as the contextual menu anchor
 // - isLinkFollowItemVisible: is the follow-link item expected to be displayed
 // - isLinkCopyItemVisible: is the copy-link item expected to be displayed
 // - linkFollowItemLabel: the expected label of the follow-link item
 // - linkCopyItemLabel: the expected label of the copy-link item
 const TEST_DATA = [{
   selector: "link",
   attributeName: "href",
   popupNodeSelector: ".link",
   isLinkFollowItemVisible: true,
   isLinkCopyItemVisible: true,
-  linkFollowItemLabel: STRINGS.GetStringFromName("inspector.menu.openUrlInNewTab.label"),
+  linkFollowItemLabel: TOOLBOX_STRINGS.GetStringFromName("toolbox.viewCssSourceInStyleEditor.label"),
   linkCopyItemLabel: STRINGS.GetStringFromName("inspector.menu.copyUrlToClipboard.label")
 }, {
   selector: "link[rel=icon]",
   attributeName: "href",
   popupNodeSelector: ".link",
   isLinkFollowItemVisible: true,
   isLinkCopyItemVisible: true,
   linkFollowItemLabel: STRINGS.GetStringFromName("inspector.menu.openUrlInNewTab.label"),
@@ -51,17 +53,17 @@ const TEST_DATA = [{
   linkFollowItemLabel: STRINGS.formatStringFromName(
     "inspector.menu.selectElement.label", ["name"], 1)
 }, {
   selector: "script",
   attributeName: "src",
   popupNodeSelector: ".link",
   isLinkFollowItemVisible: true,
   isLinkCopyItemVisible: true,
-  linkFollowItemLabel: STRINGS.GetStringFromName("inspector.menu.openUrlInNewTab.label"),
+  linkFollowItemLabel: TOOLBOX_STRINGS.GetStringFromName("toolbox.viewJsSourceInDebugger.label"),
   linkCopyItemLabel: STRINGS.GetStringFromName("inspector.menu.copyUrlToClipboard.label")
 }, {
   selector: "p[for]",
   attributeName: "for",
   popupNodeSelector: ".attr-value",
   isLinkFollowItemVisible: false,
   isLinkCopyItemVisible: false
 }];
--- a/browser/devtools/markupview/test/browser_markupview_links_05.js
+++ b/browser/devtools/markupview/test/browser_markupview_links_05.js
@@ -7,19 +7,16 @@
 // Tests that the contextual menu items shown when clicking on links in
 // attributes actually do the right things.
 
 const TEST_URL = TEST_URL_ROOT + "doc_markup_links.html";
 
 add_task(function*() {
   let {inspector} = yield addTab(TEST_URL).then(openInspector);
 
-  let linkFollow = inspector.panelDoc.getElementById("node-menu-link-follow");
-  let linkCopy = inspector.panelDoc.getElementById("node-menu-link-copy");
-
   info("Select a node with a URI attribute");
   yield selectNode("video", inspector);
 
   info("Set the popupNode to the node that contains the uri");
   let {editor} = yield getContainerForSelector("video", inspector);
   let popupNode = editor.attrElements.get("poster").querySelector(".link");
   inspector.panelDoc.popupNode = popupNode;
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_markupview_links_06.js
@@ -0,0 +1,51 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the contextual menu items shown when clicking on linked attributes
+// for <script> and <link> tags actually open the right tools.
+
+const TEST_URL = TEST_URL_ROOT + "doc_markup_links.html";
+
+add_task(function*() {
+  let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
+
+  info("Select a node with a cssresource attribute");
+  yield selectNode("link", inspector);
+
+  info("Set the popupNode to the node that contains the uri");
+  let {editor} = yield getContainerForSelector("link", inspector);
+  let popupNode = editor.attrElements.get("href").querySelector(".link");
+  inspector.panelDoc.popupNode = popupNode;
+
+  info("Follow the link and wait for the style-editor to open");
+  let onStyleEditorReady = toolbox.once("styleeditor-ready");
+  inspector.followAttributeLink();
+  yield onStyleEditorReady;
+
+  // No real need to test that the editor opened on the right file here as this
+  // is already tested in /framework/test/browser_toolbox_view_source_*
+  ok(true, "The style-editor was open");
+
+  info("Switch back to the inspector");
+  yield toolbox.selectTool("inspector");
+
+  info("Select a node with a jsresource attribute");
+  yield selectNode("script", inspector);
+
+  info("Set the popupNode to the node that contains the uri");
+  ({editor}) = yield getContainerForSelector("script", inspector);
+  popupNode = editor.attrElements.get("src").querySelector(".link");
+  inspector.panelDoc.popupNode = popupNode;
+
+  info("Follow the link and wait for the debugger to open");
+  let onDebuggerReady = toolbox.once("jsdebugger-ready");
+  inspector.followAttributeLink();
+  yield onDebuggerReady;
+
+  // No real need to test that the debugger opened on the right file here as
+  // this is already tested in /framework/test/browser_toolbox_view_source_*
+  ok(true, "The debugger was open");
+});
--- a/browser/devtools/shared/inplace-editor.js
+++ b/browser/devtools/shared/inplace-editor.js
@@ -242,16 +242,18 @@ function InplaceEditor(aOptions, aEvent)
     (e) => { e.stopPropagation(); }, false);
 
   this.validate = aOptions.validate;
 
   if (this.validate) {
     this.input.addEventListener("keyup", this._onKeyup, false);
   }
 
+  this._updateSize();
+
   if (aOptions.start) {
     aOptions.start(this, aEvent);
   }
 
   EventEmitter.decorate(this);
 }
 
 exports.InplaceEditor = InplaceEditor;
@@ -359,17 +361,16 @@ InplaceEditor.prototype = {
     // we get a chance to resize.  Yuck.
     let width = this._measurement.offsetWidth + 10;
 
     if (this.multiline) {
       // Make sure there's some content in the current line.  This is a hack to
       // account for the fact that after adding a newline the <pre> doesn't grow
       // unless there's text content on the line.
       width += 15;
-      this._measurement.textContent += "M";
       this.input.style.height = this._measurement.offsetHeight + "px";
     }
 
     this.input.style.width = width + "px";
   },
 
   /**
    * Get the width of a single character in the input to properly position the
--- a/browser/devtools/shared/node-attribute-parser.js
+++ b/browser/devtools/shared/node-attribute-parser.js
@@ -13,28 +13,31 @@
  *
  * There are several types of linkable attribute values:
  * - TYPE_URI: a URI (e.g. <a href="uri">).
  * - TYPE_URI_LIST: a space separated list of URIs (e.g. <a ping="uri1 uri2">).
  * - TYPE_IDREF: a reference to an other element in the same document via its id
  *   (e.g. <label for="input-id"> or <key command="command-id">).
  * - TYPE_IDREF_LIST: a space separated list of IDREFs (e.g.
  *   <output for="id1 id2">).
- * - TYPE_RESOURCE_URI: a URI to a javascript or css resource that can be opened
- *   in the devtools (e.g. <script src="uri">).
+ * - TYPE_JS_RESOURCE_URI: a URI to a javascript resource that can be opened in
+ *   the devtools (e.g. <script src="uri">).
+ * - TYPE_CSS_RESOURCE_URI: a URI to a css resource that can be opened in the
+ *   devtools (e.g. <link href="uri">).
  *
  * parseAttribute is the parser entry function, exported on this module.
  */
 
 const TYPE_STRING = "string";
 const TYPE_URI = "uri";
 const TYPE_URI_LIST = "uriList";
 const TYPE_IDREF = "idref";
 const TYPE_IDREF_LIST = "idrefList";
-const TYPE_RESOURCE_URI = "resource";
+const TYPE_JS_RESOURCE_URI = "jsresource";
+const TYPE_CSS_RESOURCE_URI = "cssresource";
 
 const SVG_NS = "http://www.w3.org/2000/svg";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 const ATTRIBUTE_TYPES = [
   {namespaceURI: HTML_NS, attributeName: "action", tagName: "form", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "background", tagName: "body", type: TYPE_URI},
@@ -60,34 +63,34 @@ const ATTRIBUTE_TYPES = [
   {namespaceURI: HTML_NS, attributeName: "form", tagName: "select", type: TYPE_IDREF},
   {namespaceURI: HTML_NS, attributeName: "form", tagName: "textarea", type: TYPE_IDREF},
   {namespaceURI: HTML_NS, attributeName: "formaction", tagName: "button", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "formaction", tagName: "input", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "headers", tagName: "td", type: TYPE_IDREF_LIST},
   {namespaceURI: HTML_NS, attributeName: "headers", tagName: "th", type: TYPE_IDREF_LIST},
   {namespaceURI: HTML_NS, attributeName: "href", tagName: "a", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "href", tagName: "area", type: TYPE_URI},
-  {namespaceURI: "*", attributeName: "href", tagName: "link", type: TYPE_RESOURCE_URI,
+  {namespaceURI: "*", attributeName: "href", tagName: "link", type: TYPE_CSS_RESOURCE_URI,
    isValid: (namespaceURI, tagName, attributes) => {
     return getAttribute(attributes, "rel") === "stylesheet";
    }},
   {namespaceURI: "*", attributeName: "href", tagName: "link", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "href", tagName: "base", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "icon", tagName: "menuitem", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "list", tagName: "input", type: TYPE_IDREF},
   {namespaceURI: HTML_NS, attributeName: "longdesc", tagName: "img", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "longdesc", tagName: "frame", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "longdesc", tagName: "iframe", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "manifest", tagName: "html", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "menu", tagName: "button", type: TYPE_IDREF},
   {namespaceURI: HTML_NS, attributeName: "ping", tagName: "a", type: TYPE_URI_LIST},
   {namespaceURI: HTML_NS, attributeName: "ping", tagName: "area", type: TYPE_URI_LIST},
   {namespaceURI: HTML_NS, attributeName: "poster", tagName: "video", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "profile", tagName: "head", type: TYPE_URI},
-  {namespaceURI: "*", attributeName: "src", tagName: "script", type: TYPE_RESOURCE_URI},
+  {namespaceURI: "*", attributeName: "src", tagName: "script", type: TYPE_JS_RESOURCE_URI},
   {namespaceURI: HTML_NS, attributeName: "src", tagName: "input", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "src", tagName: "frame", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "src", tagName: "iframe", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "src", tagName: "img", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "src", tagName: "audio", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "src", tagName: "embed", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "src", tagName: "source", type: TYPE_URI},
   {namespaceURI: HTML_NS, attributeName: "src", tagName: "track", type: TYPE_URI},
@@ -132,19 +135,25 @@ let parsers = {
     let data = splitBy(attributeValue, " ");
     for (let token of data) {
       if (!token.type) {
         token.type = TYPE_URI;
       }
     }
     return data;
   },
-  [TYPE_RESOURCE_URI]: function(attributeValue) {
+  [TYPE_JS_RESOURCE_URI]: function(attributeValue) {
     return [{
-      type: TYPE_RESOURCE_URI,
+      type: TYPE_JS_RESOURCE_URI,
+      value: attributeValue
+    }];
+  },
+  [TYPE_CSS_RESOURCE_URI]: function(attributeValue) {
+    return [{
+      type: TYPE_CSS_RESOURCE_URI,
       value: attributeValue
     }];
   },
   [TYPE_IDREF]: function(attributeValue) {
     return [{
       type: TYPE_IDREF,
       value: attributeValue
     }];
@@ -164,17 +173,17 @@ let parsers = {
  * Parse an attribute value.
  * @param {String} namespaceURI The namespaceURI of the node that has the
  * attribute.
  * @param {String} tagName The tagName of the node that has the attribute.
  * @param {Array} attributes The list of all attributes of the node. This should
  * be an array of {name, value} objects.
  * @param {String} attributeName The name of the attribute to parse.
  * @return {Array} An array of tokens that represents the value. Each token is
- * an object {type: [string|uri|resource|idref], value}.
+ * an object {type: [string|uri|jsresource|cssresource|idref], value}.
  * For instance parsing the ping attribute in <a ping="uri1 uri2"> returns:
  * [
  *   {type: "uri", value: "uri2"},
  *   {type: "string", value: " "},
  *   {type: "uri", value: "uri1"}
  * ]
  */
 function parseAttribute(namespaceURI, tagName, attributes, attributeName) {
--- a/browser/devtools/shared/test/unit/test_attribute-parsing-02.js
+++ b/browser/devtools/shared/test/unit/test_attribute-parsing-02.js
@@ -46,17 +46,17 @@ const TEST_DATA = [{
   ]
 }, {
   tagName: "link",
   namespaceURI: "http://www.w3.org/1999/xhtml",
   attributeName: "href",
   attributeValue: "styles.css",
   otherAttributes: [{name: "rel", value: "stylesheet"}],
   expected: [
-    {value: "styles.css", type: "resource"}
+    {value: "styles.css", type: "cssresource"}
   ]
 }, {
   tagName: "link",
   namespaceURI: "http://www.w3.org/1999/xhtml",
   attributeName: "href",
   attributeValue: "styles.css",
   expected: [
     {value: "styles.css", type: "uri"}
@@ -98,17 +98,17 @@ const TEST_DATA = [{
     {value: "some_command_id", type: "idref"}
   ]
 }, {
   tagName: "script",
   namespaceURI: "whatever",
   attributeName: "src",
   attributeValue: "script.js",
   expected: [
-    {value: "script.js", type: "resource"}
+    {value: "script.js", type: "jsresource"}
   ]
 }];
 
 function run_test() {
   for (let {tagName, namespaceURI, attributeName,
             otherAttributes, attributeValue, expected} of TEST_DATA) {
     do_print("Testing <" + tagName + " " + attributeName + "='" + attributeValue + "'>");
 
--- a/browser/devtools/webconsole/console-output.js
+++ b/browser/devtools/webconsole/console-output.js
@@ -88,16 +88,17 @@ const CONSOLE_API_LEVELS_TO_SEVERITIES =
   assert: "error",
   warn: "warning",
   info: "info",
   log: "log",
   trace: "log",
   table: "log",
   debug: "log",
   dir: "log",
+  dirxml: "log",
   group: "log",
   groupCollapsed: "log",
   groupEnd: "log",
   time: "log",
   timeEnd: "log",
   count: "log"
 };
 
@@ -2977,17 +2978,18 @@ Widgets.ObjectRenderers.add({
         break;
       default:
         throw new Error("Unsupported nodeType: " + preview.nodeType);
     }
   },
 
   _renderDocumentNode: function()
   {
-    let fn = Widgets.ObjectRenderers.byKind.ObjectWithURL.prototype._renderElement;
+    let fn =
+      Widgets.ObjectRenderers.byKind.ObjectWithURL.prototype._renderElement;
     this.element = fn.call(this, this.objectActor,
                            this.objectActor.preview.location);
     this.element.classList.add("documentNode");
   },
 
   _renderAttributeNode: function(nodeName, nodeValue, addLink)
   {
     let value = VariablesView.getString(nodeValue, { noStringQuotes: true });
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -379,8 +379,9 @@ skip-if = e10s # Bug 1042253 - webconsol
 [browser_webconsole_start_netmon_first.js]
 [browser_webconsole_console_trace_duplicates.js]
 [browser_webconsole_cd_iframe.js]
 [browser_webconsole_autocomplete_crossdomain_iframe.js]
 [browser_webconsole_console_custom_styles.js]
 [browser_webconsole_console_api_stackframe.js]
 [browser_webconsole_column_numbers.js]
 [browser_console_open_or_focus.js]
+[browser_webconsole_bug_922212_console_dirxml.js]
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_659907_console_dir.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_659907_console_dir.js
@@ -4,17 +4,17 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Tests that console.dir works as intended.
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 659907: " +
-  "Expand console object with a dir method"
+  "Expand console object with a dir method";
 
 let test = asyncTest(function*() {
   yield loadTab(TEST_URI);
   let hud = yield openConsole();
   hud.jsterm.clearOutput();
 
   hud.jsterm.execute("console.dir(document)");
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_922212_console_dirxml.js
@@ -0,0 +1,49 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that console.dirxml works as intended.
+
+"use strict";
+
+const TEST_URI = `data:text/html;charset=utf-8,Web Console test for bug 922212:
+  Add console.dirxml`;
+
+let test = asyncTest(function*() {
+  yield loadTab(TEST_URI);
+  let hud = yield openConsole();
+  hud.jsterm.clearOutput();
+
+  // Should work like console.log(window)
+  hud.jsterm.execute("console.dirxml(window)");
+
+  let [result] = yield waitForMessages({
+    webconsole: hud,
+    messages: [{
+      name: "console.dirxml(window) output:",
+      text: /Window \u2192/,
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  });
+
+  hud.jsterm.clearOutput();
+
+  hud.jsterm.execute("console.dirxml(document.body)");
+
+  // Should work like console.log(document.body);
+  [result] = yield waitForMessages({
+    webconsole: hud,
+    messages: [{
+      name: "console.dirxml(document.body) output:",
+      text: "<body>",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  });
+  let msg = [...result.matched][0];
+  yield checkLinkToInspector(true, msg);
+});
+
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -1469,17 +1469,18 @@ function checkOutputForInputs(hud, input
         text: entry.output,
         category: CATEGORY_WEBDEV,
         severity: SEVERITY_LOG,
       }],
     });
 
     if (typeof entry.inspectorIcon == "boolean") {
       let msg = [...result.matched][0];
-      yield checkLinkToInspector(entry, msg);
+      info("Checking Inspector Link: " + entry.input);
+      yield checkLinkToInspector(entry.inspectorIcon, msg);
     }
   }
 
   function checkPrintOutput(entry)
   {
     info("Printing: " + entry.input);
     hud.jsterm.clearOutput();
     hud.jsterm.execute("print(" + entry.input + ")");
@@ -1511,17 +1512,18 @@ function checkOutputForInputs(hud, input
       }],
     });
 
     let msg = [...result.matched][0];
     if (!entry.noClick) {
       yield checkObjectClick(entry, msg);
     }
     if (typeof entry.inspectorIcon == "boolean") {
-      yield checkLinkToInspector(entry, msg);
+      info("Checking Inspector Link: " + entry.input);
+      yield checkLinkToInspector(entry.inspectorIcon, msg);
     }
   }
 
   function* checkObjectClick(entry, msg)
   {
     info("Clicking: " + entry.input);
     let body = msg.querySelector(".message-body a") ||
                msg.querySelector(".message-body");
@@ -1551,40 +1553,16 @@ function checkOutputForInputs(hud, input
     } else {
       container.removeEventListener("TabOpen", entry._onTabOpen, true);
       entry._onTabOpen = null;
     }
 
     yield promise.resolve(null);
   }
 
-  function checkLinkToInspector(entry, msg)
-  {
-    info("Checking Inspector Link: " + entry.input);
-    let elementNodeWidget = [...msg._messageObject.widgets][0];
-    if (!elementNodeWidget) {
-      ok(!entry.inspectorIcon, "The message has no ElementNode widget");
-      return;
-    }
-
-    return elementNodeWidget.linkToInspector().then(() => {
-      // linkToInspector resolved, check for the .open-inspector element
-      if (entry.inspectorIcon) {
-        ok(msg.querySelectorAll(".open-inspector").length,
-          "The ElementNode widget is linked to the inspector");
-      } else {
-        ok(!msg.querySelectorAll(".open-inspector").length,
-          "The ElementNode widget isn't linked to the inspector");
-      }
-    }, () => {
-      // linkToInspector promise rejected, node not linked to inspector
-      ok(!entry.inspectorIcon, "The ElementNode widget isn't linked to the inspector");
-    });
-  }
-
   function onVariablesViewOpen(entry, {resolve, reject}, event, view, options)
   {
     info("Variables view opened: " + entry.input);
     let label = entry.variablesViewLabel || entry.output;
     if (typeof label == "string" && options.label != label) {
       return;
     }
     if (label instanceof RegExp && !label.test(options.label)) {
@@ -1641,16 +1619,46 @@ function once(target, eventName, useCapt
       }, useCapture);
       break;
     }
   }
 
   return deferred.promise;
 }
 
+/**
+ * Checks a link to the inspector
+ *
+ * @param {boolean} hasLinkToInspector Set to true if the message should
+ *  link to the inspector panel.
+ * @param {element} msg The message to test.
+ */
+function checkLinkToInspector(hasLinkToInspector, msg)
+{
+  let elementNodeWidget = [...msg._messageObject.widgets][0];
+  if (!elementNodeWidget) {
+    ok(!hasLinkToInspector, "The message has no ElementNode widget");
+    return;
+  }
+
+  return elementNodeWidget.linkToInspector().then(() => {
+    // linkToInspector resolved, check for the .open-inspector element
+    if (hasLinkToInspector) {
+      ok(msg.querySelectorAll(".open-inspector").length,
+        "The ElementNode widget is linked to the inspector");
+    } else {
+      ok(!msg.querySelectorAll(".open-inspector").length,
+        "The ElementNode widget isn't linked to the inspector");
+    }
+  }, () => {
+    // linkToInspector promise rejected, node not linked to inspector
+    ok(!hasLinkToInspector, "The ElementNode widget isn't linked to the inspector");
+  });
+}
+
 function getSourceActor(aSources, aURL) {
   let item = aSources.getItemForAttachment(a => a.source.url === aURL);
   return item && item.value;
 }
 
 /**
  * Verify that clicking on a link from a popup notification message tries to
  * open the expected URL.
--- a/browser/devtools/webconsole/test/test-console-extras.html
+++ b/browser/devtools/webconsole/test/test-console-extras.html
@@ -1,17 +1,17 @@
 <!DOCTYPE HTML>
 <html dir="ltr" xml:lang="en-US" lang="en-US"><head>
     <meta charset="utf-8">
     <title>Console extended API test</title>
     <script type="text/javascript">
       function test() {
         console.log("start");
         console.clear()
-        console.dirxml()
+        console.timeStamp()
         console.log("end");
       }
     </script>
   </head>
   <body>
     <h1 id="header">Heads Up Display Demo</h1>
     <button onclick="test();">Test Extended API</button>
     <div id="myDiv"></div>
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -125,16 +125,17 @@ const LEVELS = {
   assert: SEVERITY_ERROR,
   warn: SEVERITY_WARNING,
   info: SEVERITY_INFO,
   log: SEVERITY_LOG,
   trace: SEVERITY_LOG,
   table: SEVERITY_LOG,
   debug: SEVERITY_LOG,
   dir: SEVERITY_LOG,
+  dirxml: SEVERITY_LOG,
   group: SEVERITY_LOG,
   groupCollapsed: SEVERITY_LOG,
   groupEnd: SEVERITY_LOG,
   time: SEVERITY_LOG,
   timeEnd: SEVERITY_LOG,
   count: SEVERITY_LOG
 };
 
@@ -1280,17 +1281,21 @@ WebConsoleFrame.prototype = {
         body = { arguments: args };
         let clipboardArray = [];
         args.forEach((aValue) => {
           clipboardArray.push(VariablesView.getString(aValue));
         });
         clipboardText = clipboardArray.join(" ");
         break;
       }
-
+      case "dirxml": {
+        // We just alias console.dirxml() with console.log().
+        aMessage.level = "log";
+        return WCF_logConsoleAPIMessage.call(this, aMessage);
+      }
       case "group":
       case "groupCollapsed":
         clipboardText = body = aMessage.groupName;
         this.groupDepth++;
         break;
 
       case "groupEnd":
         if (this.groupDepth > 0) {
--- a/browser/locales/en-US/chrome/browser/devtools/inspector.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/inspector.properties
@@ -80,28 +80,14 @@ inspector.expandPane=Expand pane
 inspector.menu.openUrlInNewTab.label=Open Link in New Tab
 
 # LOCALIZATION NOTE (inspector.menu.copyUrlToClipboard.label): This is the label
 # of a menu item in the inspector contextual-menu that appears when the user
 # right-clicks on the attribute of a node in the inspector that is a URL, and
 # that allows to copy that URL in the clipboard.
 inspector.menu.copyUrlToClipboard.label=Copy Link Address
 
-# LOCALIZATION NOTE (inspector.menu.openFileInDebugger.label): This is the label
-# of a menu item in the inspector contextual-menu that appears when the user
-# right-clicks on the attribute of a node in the inspector that is a URL to a
-# javascript filename, and that allows to open the corresponding file in the
-# debugger.
-inspector.menu.openFileInDebugger.label=Open File in Debugger
-
-# LOCALIZATION NOTE (inspector.menu.openFileInStyleEditor.label): This is the
-# label of a menu item in the inspector contextual-menu that appears when the
-# user right-clicks on the attribute of a node in the inspector that is a URL to
-# a css filename, and that allows to open the corresponding file in the style
-# editor.
-inspector.menu.openFileInStyleEditor.label=Open File in Style-Editor
-
 # LOCALIZATION NOTE (inspector.menu.selectElement.label): This is the label of a
 # menu item in the inspector contextual-menu that appears when the user right-
 # clicks on the attribute of a node in the inspector that is the ID of another
 # element in the DOM (like with <label for="input-id">), and that allows to
 # select that element in the inspector.
 inspector.menu.selectElement.label=Select Element #%S
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
@@ -1,16 +1,19 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!-- LOCALIZATION NOTE : FILE This file contains the Toolbox strings -->
 <!-- LOCALIZATION NOTE : FILE Do not translate key -->
 
 <!ENTITY closeCmd.key  "W">
+<!ENTITY toggleToolbox.key  "I">
+<!ENTITY toggleToolboxF12.keycode          "VK_F12">
+<!ENTITY toggleToolboxF12.keytext          "F12">
 
 <!ENTITY toolboxCloseButton.tooltip    "Close Developer Tools">
 <!ENTITY toolboxOptionsButton.key      "O">
 <!ENTITY toolboxNextTool.key           "]">
 <!ENTITY toolboxPreviousTool.key       "[">
 
 <!ENTITY toolboxZoomIn.key             "+">
 <!ENTITY toolboxZoomIn.key2            "="> <!-- + is above this key on many keyboards -->
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.properties
@@ -83,8 +83,20 @@ options.darkTheme.label=Dark theme
 # LOCALIZATION NOTE (options.lightTheme.label)
 # Used as a label for light theme
 options.lightTheme.label=Light theme
 
 # LOCALIZATION NOTE (toolbox.noContentProcess.message)
 # Used as a message in the alert displayed when trying to open a browser
 # content toolbox and there is no content process running
 toolbox.noContentProcess.message=No content process running.
+
+# LOCALIZATION NOTE (toolbox.viewCssSourceInStyleEditor.label)
+# Used as a message in either tooltips or contextual menu items to open the
+# corresponding URL as a css file in the Style-Editor tool.
+# DEV NOTE: Mostly used wherever toolbox.viewSourceInStyleEditor is used.
+toolbox.viewCssSourceInStyleEditor.label=Open File in Style-Editor
+
+# LOCALIZATION NOTE (toolbox.viewJsSourceInDebugger.label)
+# Used as a message in either tooltips or contextual menu items to open the
+# corresponding URL as a js file in the Debugger tool.
+# DEV NOTE: Mostly used wherever toolbox.viewSourceInDebugger is used.
+toolbox.viewJsSourceInDebugger.label=Open File in Debugger
--- a/browser/locales/en-US/chrome/browser/places/places.dtd
+++ b/browser/locales/en-US/chrome/browser/places/places.dtd
@@ -80,17 +80,16 @@
 <!ENTITY cmd.moveBookmarks.label                  "Move…">
 <!ENTITY cmd.moveBookmarks.accesskey              "M">
 
 <!ENTITY col.name.label          "Name">
 <!ENTITY col.tags.label          "Tags">
 <!ENTITY col.url.label           "Location">
 <!ENTITY col.mostrecentvisit.label "Most Recent Visit">
 <!ENTITY col.visitcount.label    "Visit Count">
-<!ENTITY col.keyword.label       "Keyword">
 <!ENTITY col.description.label   "Description">
 <!ENTITY col.dateadded.label     "Added">
 <!ENTITY col.lastmodified.label  "Last Modified">
 
 <!ENTITY search.label                              "Search:">
 <!ENTITY search.accesskey                          "S">
 
 <!ENTITY cmd.find.key  "f">
--- a/browser/locales/en-US/chrome/browser/places/places.properties
+++ b/browser/locales/en-US/chrome/browser/places/places.properties
@@ -31,18 +31,16 @@ sortByNameGeneric=Sort by Name
 view.sortBy.1.name.label=Sort by Name
 view.sortBy.1.name.accesskey=N
 view.sortBy.1.url.label=Sort by Location
 view.sortBy.1.url.accesskey=L
 view.sortBy.1.date.label=Sort by Most Recent Visit
 view.sortBy.1.date.accesskey=V
 view.sortBy.1.visitCount.label=Sort by Visit Count
 view.sortBy.1.visitCount.accesskey=C
-view.sortBy.1.keyword.label=Sort by Keyword
-view.sortBy.1.keyword.accesskey=K
 view.sortBy.1.description.label=Sort by Description
 view.sortBy.1.description.accesskey=D
 view.sortBy.1.dateAdded.label=Sort by Added
 view.sortBy.1.dateAdded.accesskey=e
 view.sortBy.1.lastModified.label=Sort by Last Modified
 view.sortBy.1.lastModified.accesskey=M
 view.sortBy.1.tags.label=Sort by Tags
 view.sortBy.1.tags.accesskey=T
--- a/browser/modules/Feeds.jsm
+++ b/browser/modules/Feeds.jsm
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [ "Feeds" ];
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                   "resource://gre/modules/BrowserUtils.jsm");
 
 const Ci = Components.interfaces;
 
 this.Feeds = {
 
@@ -32,18 +33,21 @@ this.Feeds = {
 
     var type = aLink.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
     if (!aIsFeed) {
       aIsFeed = (type == "application/rss+xml" ||
                  type == "application/atom+xml");
     }
 
     if (aIsFeed) {
+      // re-create the principal as it may be a CPOW.
+      let principalURI = BrowserUtils.makeURIFromCPOW(aPrincipal.URI);
+      let principalToCheck = Services.scriptSecurityManager.getNoAppCodebasePrincipal(principalURI);
       try {
-        BrowserUtils.urlSecurityCheck(aLink.href, aPrincipal,
+        BrowserUtils.urlSecurityCheck(aLink.href, principalToCheck,
                                       Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
         return type || "application/rss+xml";
       }
       catch(ex) {
       }
     }
 
     return null;
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -809,16 +809,17 @@ void
 Console::Trace(JSContext* aCx)
 {
   const Sequence<JS::Value> data;
   Method(aCx, MethodTrace, NS_LITERAL_STRING("trace"), data);
 }
 
 // Displays an interactive listing of all the properties of an object.
 METHOD(Dir, "dir");
+METHOD(Dirxml, "dirxml");
 
 METHOD(Group, "group")
 METHOD(GroupCollapsed, "groupCollapsed")
 METHOD(GroupEnd, "groupEnd")
 
 void
 Console::Time(JSContext* aCx, const JS::Handle<JS::Value> aTime)
 {
--- a/dom/base/Console.h
+++ b/dom/base/Console.h
@@ -70,16 +70,19 @@ public:
 
   void
   Trace(JSContext* aCx);
 
   void
   Dir(JSContext* aCx, const Sequence<JS::Value>& aData);
 
   void
+  Dirxml(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
   Group(JSContext* aCx, const Sequence<JS::Value>& aData);
 
   void
   GroupCollapsed(JSContext* aCx, const Sequence<JS::Value>& aData);
 
   void
   GroupEnd(JSContext* aCx, const Sequence<JS::Value>& aData);
 
@@ -111,16 +114,17 @@ private:
     MethodInfo,
     MethodWarn,
     MethodError,
     MethodException,
     MethodDebug,
     MethodTable,
     MethodTrace,
     MethodDir,
+    MethodDirxml,
     MethodGroup,
     MethodGroupCollapsed,
     MethodGroupEnd,
     MethodTime,
     MethodTimeEnd,
     MethodAssert,
     MethodCount
   };
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -174,16 +174,20 @@ DOMInterfaces = {
 'BluetoothGattDescriptor': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothGattDescriptor',
 },
 
 'BluetoothGattService': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothGattService',
 },
 
+'BluetoothLeDeviceEvent': {
+    'nativeType': 'mozilla::dom::bluetooth::BluetoothLeDeviceEvent',
+},
+
 'BluetoothManager': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothManager',
 },
 
 'BluetoothPairingHandle': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothPairingHandle',
 },
 
--- a/dom/bluetooth/BluetoothCommon.h
+++ b/dom/bluetooth/BluetoothCommon.h
@@ -746,11 +746,27 @@ struct BluetoothGattNotifyParam {
   uint8_t mValue[BLUETOOTH_GATT_MAX_ATTR_LEN];
   nsString mBdAddr;
   BluetoothGattServiceId mServiceId;
   BluetoothGattId mCharId;
   uint16_t mLength;
   bool mIsNotify;
 };
 
+/**
+ * EIR Data Type, Advertising Data Type (AD Type) and OOB Data Type Definitions
+ * Please refer to https://www.bluetooth.org/en-us/specification/\
+ * assigned-numbers/generic-access-profile
+ */
+enum BluetoothGapDataType {
+  GAP_INCOMPLETE_UUID16  = 0X02, // Incomplete List of 16-bit Service Class UUIDs
+  GAP_COMPLETE_UUID16    = 0X03, // Complete List of 16-bit Service Class UUIDs
+  GAP_INCOMPLETE_UUID32  = 0X04, // Incomplete List of 32-bit Service Class UUIDs
+  GAP_COMPLETE_UUID32    = 0X05, // Complete List of 32-bit Service Class UUIDs»
+  GAP_INCOMPLETE_UUID128 = 0X06, // Incomplete List of 128-bit Service Class UUIDs
+  GAP_COMPLETE_UUID128   = 0X07, // Complete List of 128-bit Service Class UUIDs
+  GAP_SHORTENED_NAME     = 0X08, // Shortened Local Name
+  GAP_COMPLETE_NAME      = 0X09, // Complete Local Name
+};
+
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_bluetoothcommon_h__
--- a/dom/bluetooth/BluetoothUtils.cpp
+++ b/dom/bluetooth/BluetoothUtils.cpp
@@ -7,16 +7,17 @@
 #include "BluetoothUtils.h"
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "jsapi.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "nsContentUtils.h"
 #include "nsISystemMessagesInternal.h"
+#include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 #include "nsXULAppAPI.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 void
 UuidToString(const BluetoothUuid& aUuid, nsAString& aString)
 {
@@ -72,16 +73,37 @@ StringToUuid(const char* aString, Blueto
   memcpy(&aUuid.mUuid[4], &uuid1, sizeof(uint16_t));
   memcpy(&aUuid.mUuid[6], &uuid2, sizeof(uint16_t));
   memcpy(&aUuid.mUuid[8], &uuid3, sizeof(uint16_t));
   memcpy(&aUuid.mUuid[10], &uuid4, sizeof(uint32_t));
   memcpy(&aUuid.mUuid[14], &uuid5, sizeof(uint16_t));
 }
 
 void
+GenerateUuid(nsAString &aUuidString)
+{
+  nsresult rv;
+  nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
+    do_GetService("@mozilla.org/uuid-generator;1", &rv);
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  nsID uuid;
+  rv = uuidGenerator->GenerateUUIDInPlace(&uuid);
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format
+  char uuidBuffer[NSID_LENGTH];
+  uuid.ToProvidedString(uuidBuffer);
+  NS_ConvertASCIItoUTF16 uuidString(uuidBuffer);
+
+  // Remove {} and the null terminator
+  aUuidString.Assign(Substring(uuidString, 1, NSID_LENGTH - 3));
+}
+
+void
 GeneratePathFromGattId(const BluetoothGattId& aId,
                        nsAString& aPath,
                        nsAString& aUuidStr)
 {
   ReversedUuidToString(aId.mUuid, aUuidStr);
 
   aPath.Assign(aUuidStr);
   aPath.AppendLiteral("_");
--- a/dom/bluetooth/BluetoothUtils.h
+++ b/dom/bluetooth/BluetoothUtils.h
@@ -46,16 +46,24 @@ ReversedUuidToString(const BluetoothUuid
  * Convert xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx uuid string to BluetoothUuid object.
  *
  * Note: This utility function is used by gecko internal only to convert uuid
  * string created by gecko back to BluetoothUuid representation.
  */
 void
 StringToUuid(const char* aString, BluetoothUuid& aUuid);
 
+/**
+ * Generate a random uuid.
+ *
+ * @param aUuidString [out] String to store the generated uuid.
+ */
+void
+GenerateUuid(nsAString &aUuidString);
+
 //
 // Generate bluetooth signal path from GattId
 //
 
 /**
  * Generate bluetooth signal path and UUID string from a GattId.
  *
  * @param aId      [in] GattId value to convert.
--- a/dom/bluetooth/bluedroid/BluetoothGattManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothGattManager.cpp
@@ -168,16 +168,17 @@ public:
     mDescriptors.Clear();
     mDiscoverRunnable = nullptr;
   }
 
   nsString mAppUuid;
   nsString mDeviceAddr;
   int mClientIf;
   int mConnId;
+  nsRefPtr<BluetoothReplyRunnable> mStartLeScanRunnable;
   nsRefPtr<BluetoothReplyRunnable> mConnectRunnable;
   nsRefPtr<BluetoothReplyRunnable> mDisconnectRunnable;
   nsRefPtr<BluetoothReplyRunnable> mDiscoverRunnable;
   nsRefPtr<BluetoothReplyRunnable> mReadRemoteRssiRunnable;
   nsRefPtr<BluetoothReplyRunnable> mRegisterNotificationsRunnable;
   nsRefPtr<BluetoothReplyRunnable> mDeregisterNotificationsRunnable;
   nsRefPtr<BluetoothReplyRunnable> mUnregisterClientRunnable;
 
@@ -483,16 +484,150 @@ BluetoothGattManager::UnregisterClient(i
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
   client->mUnregisterClientRunnable = aRunnable;
 
   sBluetoothGattClientInterface->UnregisterClient(
     aClientIf,
     new UnregisterClientResultHandler(client));
 }
 
+class BluetoothGattManager::StartLeScanResultHandler final
+  : public BluetoothGattClientResultHandler
+{
+public:
+  StartLeScanResultHandler(BluetoothGattClient* aClient)
+    : mClient(aClient)
+  { }
+
+  void Scan() override
+  {
+    MOZ_ASSERT(mClient > 0);
+
+    DispatchReplySuccess(mClient->mStartLeScanRunnable,
+                         BluetoothValue(mClient->mAppUuid));
+    mClient->mStartLeScanRunnable = nullptr;
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattClientInterface::StartLeScan failed: %d",
+               (int)aStatus);
+    MOZ_ASSERT(mClient->mStartLeScanRunnable);
+
+    // Unregister client if startLeScan failed
+    if (mClient->mClientIf > 0) {
+      BluetoothGattManager* gattManager = BluetoothGattManager::Get();
+      NS_ENSURE_TRUE_VOID(gattManager);
+
+      nsRefPtr<BluetoothVoidReplyRunnable> result =
+        new BluetoothVoidReplyRunnable(nullptr);
+      gattManager->UnregisterClient(mClient->mClientIf, result);
+    }
+
+    DispatchReplyError(mClient->mStartLeScanRunnable,
+                       BluetoothValue(mClient->mAppUuid));
+    mClient->mStartLeScanRunnable = nullptr;
+  }
+
+private:
+  nsRefPtr<BluetoothGattClient> mClient;
+};
+
+class BluetoothGattManager::StopLeScanResultHandler final
+  : public BluetoothGattClientResultHandler
+{
+public:
+   StopLeScanResultHandler(BluetoothReplyRunnable* aRunnable, int aClientIf)
+     : mRunnable(aRunnable), mClientIf(aClientIf)
+  { }
+
+  void Scan() override
+  {
+    DispatchReplySuccess(mRunnable);
+
+    // Unregister client when stopLeScan succeeded
+    if (mClientIf > 0) {
+      BluetoothGattManager* gattManager = BluetoothGattManager::Get();
+      NS_ENSURE_TRUE_VOID(gattManager);
+
+      nsRefPtr<BluetoothVoidReplyRunnable> result =
+        new BluetoothVoidReplyRunnable(nullptr);
+      gattManager->UnregisterClient(mClientIf, result);
+    }
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattClientInterface::StopLeScan failed: %d",
+                (int)aStatus);
+    DispatchReplyError(mRunnable, aStatus);
+  }
+
+private:
+  nsRefPtr<BluetoothReplyRunnable> mRunnable;
+  int mClientIf;
+};
+
+void
+BluetoothGattManager::StartLeScan(const nsTArray<nsString>& aServiceUuids,
+                                  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+  nsString appUuidStr;
+  GenerateUuid(appUuidStr);
+
+  size_t index = sClients->IndexOf(appUuidStr, 0 /* Start */, UuidComparator());
+
+  // Reject the startLeScan request if the clientIf is being used.
+  if (index != sClients->NoIndex) {
+    DispatchReplyError(aRunnable,
+                       NS_LITERAL_STRING("start LE scan failed"));
+    return;
+  }
+
+  index = sClients->Length();
+  sClients->AppendElement(new BluetoothGattClient(appUuidStr, EmptyString()));
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+  client->mStartLeScanRunnable = aRunnable;
+
+  BluetoothUuid appUuid;
+  StringToUuid(NS_ConvertUTF16toUTF8(appUuidStr).get(), appUuid);
+
+  // 'startLeScan' will be proceeded after client registered
+  sBluetoothGattClientInterface->RegisterClient(
+    appUuid, new RegisterClientResultHandler(client));
+}
+
+void
+BluetoothGattManager::StopLeScan(const nsAString& aScanUuid,
+                                 BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sClients->IndexOf(aScanUuid, 0 /* Start */, UuidComparator());
+  if (NS_WARN_IF(index == sClients->NoIndex)) {
+    // Reject the stop LE scan request
+    DispatchReplyError(aRunnable, NS_LITERAL_STRING("StopLeScan failed"));
+    return;
+  }
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+  sBluetoothGattClientInterface->Scan(
+    client->mClientIf,
+    false /* Stop */,
+    new StopLeScanResultHandler(aRunnable, client->mClientIf));
+}
+
 class BluetoothGattManager::ConnectResultHandler final
   : public BluetoothGattClientResultHandler
 {
 public:
   ConnectResultHandler(BluetoothGattClient* aClient)
   : mClient(aClient)
   {
     MOZ_ASSERT(mClient);
@@ -1229,18 +1364,24 @@ BluetoothGattManager::RegisterClientNoti
       "RegisterClient failed, clientIf = %d, status = %d, appUuid = %s",
       aClientIf, aStatus, NS_ConvertUTF16toUTF8(uuid).get());
 
     // Notify BluetoothGatt for client disconnected
     bs->DistributeSignal(
       NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
       uuid, BluetoothValue(false)); // Disconnected
 
-    // Reject the connect request
-    if (client->mConnectRunnable) {
+    if (client->mStartLeScanRunnable) {
+      // Reject the LE scan request
+      DispatchReplyError(client->mStartLeScanRunnable,
+                         NS_LITERAL_STRING(
+                           "StartLeScan failed due to registration failed"));
+      client->mStartLeScanRunnable = nullptr;
+    } else if (client->mConnectRunnable) {
+      // Reject the connect request
       DispatchReplyError(client->mConnectRunnable,
                          NS_LITERAL_STRING(
                            "Connect failed due to registration failed"));
       client->mConnectRunnable = nullptr;
     }
 
     sClients->RemoveElement(client);
     return;
@@ -1248,30 +1389,53 @@ BluetoothGattManager::RegisterClientNoti
 
   client->mClientIf = aClientIf;
 
   // Notify BluetoothGatt to update the clientIf
   bs->DistributeSignal(
     NS_LITERAL_STRING("ClientRegistered"),
     uuid, BluetoothValue(uint32_t(aClientIf)));
 
-  // Client just registered, proceed remaining connect request.
-  if (client->mConnectRunnable) {
+  if (client->mStartLeScanRunnable) {
+    // Client just registered, proceed remaining startLeScan request.
+    sBluetoothGattClientInterface->Scan(
+      aClientIf, true /* start */,
+      new StartLeScanResultHandler(client));
+  } else if (client->mConnectRunnable) {
+    // Client just registered, proceed remaining connect request.
     sBluetoothGattClientInterface->Connect(
       aClientIf, client->mDeviceAddr, true /* direct connect */,
       TRANSPORT_AUTO,
       new ConnectResultHandler(client));
   }
 }
 
 void
 BluetoothGattManager::ScanResultNotification(
   const nsAString& aBdAddr, int aRssi,
   const BluetoothGattAdvData& aAdvData)
-{ }
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  InfallibleTArray<BluetoothNamedValue> properties;
+
+  nsTArray<uint8_t> advData;
+  advData.AppendElements(aAdvData.mAdvData, sizeof(aAdvData.mAdvData));
+
+  BT_APPEND_NAMED_VALUE(properties, "Address", nsString(aBdAddr));
+  BT_APPEND_NAMED_VALUE(properties, "Rssi", static_cast<int32_t>(aRssi));
+  BT_APPEND_NAMED_VALUE(properties, "GattAdv", advData);
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  bs->DistributeSignal(NS_LITERAL_STRING("LeDeviceFound"),
+                       NS_LITERAL_STRING(KEY_ADAPTER),
+                       BluetoothValue(properties));
+}
 
 void
 BluetoothGattManager::ConnectNotification(int aConnId,
                                           BluetoothGattStatus aStatus,
                                           int aClientIf,
                                           const nsAString& aDeviceAddr)
 {
   BT_API2_LOGR();
@@ -1868,17 +2032,17 @@ BluetoothGattManager::ReadRemoteRssiNoti
     }
 
     return;
   }
 
   // Resolve the read remote rssi request
   if (client->mReadRemoteRssiRunnable) {
     DispatchReplySuccess(client->mReadRemoteRssiRunnable,
-                         BluetoothValue(static_cast<uint32_t>(aRssi)));
+                         BluetoothValue(static_cast<int32_t>(aRssi)));
     client->mReadRemoteRssiRunnable = nullptr;
   }
 }
 
 void
 BluetoothGattManager::ListenNotification(BluetoothGattStatus aStatus,
                                          int aServerIf)
 { }
@@ -1912,16 +2076,17 @@ BluetoothGattManager::Observe(nsISupport
 }
 
 void
 BluetoothGattManager::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mInShutdown = true;
   sBluetoothGattManager = nullptr;
+  sClients = nullptr;
 }
 
 void
 BluetoothGattManager::ProceedDiscoverProcess(
   BluetoothGattClient* aClient,
   const BluetoothGattServiceId& aServiceId)
 {
   /**
--- a/dom/bluetooth/bluedroid/BluetoothGattManager.h
+++ b/dom/bluetooth/bluedroid/BluetoothGattManager.h
@@ -22,16 +22,22 @@ class BluetoothGattManager final : publi
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   static BluetoothGattManager* Get();
   static void InitGattInterface(BluetoothProfileResultHandler* aRes);
   static void DeinitGattInterface(BluetoothProfileResultHandler* aRes);
 
+  void StartLeScan(const nsTArray<nsString>& aServiceUuids,
+                   BluetoothReplyRunnable* aRunnable);
+
+  void StopLeScan(const nsAString& aScanUuid,
+                  BluetoothReplyRunnable* aRunnable);
+
   void Connect(const nsAString& aAppUuid,
                const nsAString& aDeviceAddr,
                BluetoothReplyRunnable* aRunnable);
 
   void Disconnect(const nsAString& aAppUuid,
                   const nsAString& aDeviceAddr,
                   BluetoothReplyRunnable* aRunnable);
 
@@ -87,16 +93,18 @@ public:
 private:
   ~BluetoothGattManager();
 
   class CleanupResultHandler;
   class CleanupResultHandlerRunnable;
   class InitGattResultHandler;
   class RegisterClientResultHandler;
   class UnregisterClientResultHandler;
+  class StartLeScanResultHandler;
+  class StopLeScanResultHandler;
   class ConnectResultHandler;
   class DisconnectResultHandler;
   class DiscoverResultHandler;
   class ReadRemoteRssiResultHandler;
   class RegisterNotificationsResultHandler;
   class DeregisterNotificationsResultHandler;
   class ReadCharacteristicValueResultHandler;
   class WriteCharacteristicValueResultHandler;
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
@@ -385,16 +385,44 @@ BluetoothServiceBluedroid::StopInternal(
   return ret;
 }
 
 //
 // GATT Client
 //
 
 void
+BluetoothServiceBluedroid::StartLeScanInternal(
+  const nsTArray<nsString>& aServiceUuids,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->StartLeScan(aServiceUuids, aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::StopLeScanInternal(
+  const nsAString& aScanUuid,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->StopLeScan(aScanUuid, aRunnable);
+}
+
+void
 BluetoothServiceBluedroid::ConnectGattClientInternal(
   const nsAString& aAppUuid, const nsAString& aDeviceAddress,
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
 
@@ -574,16 +602,25 @@ BluetoothServiceBluedroid::GattClientWri
   do {                                                                 \
     if (!sBtInterface || !IsEnabled()) {                               \
       NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth is not ready");     \
       DispatchBluetoothReply(runnable, BluetoothValue(), errorStr);    \
       return result;                                                   \
     }                                                                  \
   } while(0)
 
+#define ENSURE_BLUETOOTH_IS_READY_VOID(runnable)                       \
+  do {                                                                 \
+    if (!sBtInterface || !IsEnabled()) {                               \
+      NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth is not ready");     \
+      DispatchBluetoothReply(runnable, BluetoothValue(), errorStr);    \
+      return;                                                          \
+    }                                                                  \
+  } while(0)
+
 // Audio: Major service class = 0x100 (Bit 21 is set)
 #define SET_AUDIO_BIT(cod)               (cod |= 0x200000)
 // Rendering: Major service class = 0x20 (Bit 18 is set)
 #define SET_RENDERING_BIT(cod)           (cod |= 0x40000)
 
 using namespace mozilla;
 using namespace mozilla::ipc;
 USING_BLUETOOTH_NAMESPACE
@@ -1208,33 +1245,30 @@ public:
     ReplyStatusError(mRunnable, aStatus, NS_LITERAL_STRING("StartDiscovery"));
   }
 #endif
 
 private:
   BluetoothReplyRunnable* mRunnable;
 };
 
-nsresult
+void
 BluetoothServiceBluedroid::StartDiscoveryInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
-
-  ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
 
 #ifdef MOZ_B2G_BT_API_V2
   sChangeDiscoveryRunnableArray.AppendElement(aRunnable);
 #else
   // Missing in bluetooth1
 #endif
 
   sBtInterface->StartDiscovery(new StartDiscoveryResultHandler(aRunnable));
-
-  return NS_OK;
 }
 
 class BluetoothServiceBluedroid::CancelDiscoveryResultHandler final
   : public BluetoothResultHandler
 {
 public:
   CancelDiscoveryResultHandler(BluetoothReplyRunnable* aRunnable)
   : mRunnable(aRunnable)
@@ -1307,33 +1341,30 @@ BluetoothServiceBluedroid::FetchUuidsInt
     new GetRemoteServicesResultHandler(aRunnable));
 
   return NS_OK;
 }
 #else
 // Missing in bluetooth1
 #endif
 
-nsresult
+void
 BluetoothServiceBluedroid::StopDiscoveryInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
-
-  ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
 
 #ifdef MOZ_B2G_BT_API_V2
   sChangeDiscoveryRunnableArray.AppendElement(aRunnable);
 #else
   // Missing in bluetooth1
 #endif
 
   sBtInterface->CancelDiscovery(new CancelDiscoveryResultHandler(aRunnable));
-
-  return NS_OK;
 }
 
 class BluetoothServiceBluedroid::SetAdapterPropertyResultHandler final
   : public BluetoothResultHandler
 {
 public:
   SetAdapterPropertyResultHandler(BluetoothReplyRunnable* aRunnable)
   : mRunnable(aRunnable)
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
@@ -51,18 +51,18 @@ public:
   virtual nsresult
   GetPairedDevicePropertiesInternal(const nsTArray<nsString>& aDeviceAddress,
                                     BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   FetchUuidsInternal(const nsAString& aDeviceAddress,
                      BluetoothReplyRunnable* aRunnable) override;
 
-  virtual nsresult StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
-  virtual nsresult StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
+  virtual void StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
+  virtual void StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
               BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   GetServiceChannel(const nsAString& aDeviceAddress,
@@ -186,16 +186,22 @@ public:
   virtual nsresult
   SendInputMessage(const nsAString& aDeviceAddresses,
                    const nsAString& aMessage) override;
 
   //
   // GATT Client
   //
 
+  virtual void StartLeScanInternal(const nsTArray<nsString>& aServiceUuids,
+                                   BluetoothReplyRunnable* aRunnable);
+
+  virtual void StopLeScanInternal(const nsAString& aScanUuid,
+                                  BluetoothReplyRunnable* aRunnable);
+
   virtual void
   ConnectGattClientInternal(const nsAString& aAppUuid,
                             const nsAString& aDeviceAddress,
                             BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   DisconnectGattClientInternal(const nsAString& aAppUuid,
                                const nsAString& aDeviceAddress,
@@ -357,18 +363,18 @@ public:
 
   virtual nsresult GetConnectedDevicePropertiesInternal(uint16_t aProfileId,
                                              BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult GetPairedDevicePropertiesInternal(
                                      const nsTArray<nsString>& aDeviceAddress,
                                      BluetoothReplyRunnable* aRunnable);
 
-  virtual nsresult StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
-  virtual nsresult StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
+  virtual void StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
+  virtual void StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
               BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   GetServiceChannel(const nsAString& aDeviceAddress,
--- a/dom/bluetooth/bluetooth1/BluetoothAdapter.cpp
+++ b/dom/bluetooth/bluetooth1/BluetoothAdapter.cpp
@@ -392,26 +392,21 @@ BluetoothAdapter::StartStopDiscovery(boo
   nsRefPtr<BluetoothVoidReplyRunnable> results =
     new BluetoothVoidReplyRunnable(request);
 
   BluetoothService* bs = BluetoothService::Get();
   if (!bs) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
-  nsresult rv;
+
   if (aStart) {
-    rv = bs->StartDiscoveryInternal(results);
+    bs->StartDiscoveryInternal(results);
   } else {
-    rv = bs->StopDiscoveryInternal(results);
-  }
-  if (NS_FAILED(rv)) {
-    BT_WARNING("Start/Stop Discovery failed!");
-    aRv.Throw(rv);
-    return nullptr;
+    bs->StopDiscoveryInternal(results);
   }
 
   // mDiscovering is not set here, we'll get a Property update from our external
   // protocol to tell us that it's been set.
 
   return request.forget();
 }
 
--- a/dom/bluetooth/bluetooth1/BluetoothService.h
+++ b/dom/bluetooth/bluetooth1/BluetoothService.h
@@ -152,25 +152,25 @@ public:
   GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid,
                                        BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
    * Stop device discovery (platform specific implementation)
    *
    * @return NS_OK if discovery stopped correctly, false otherwise
    */
-  virtual nsresult
+  virtual void
   StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
    * Start device discovery (platform specific implementation)
    *
    * @return NS_OK if discovery stopped correctly, false otherwise
    */
-  virtual nsresult
+  virtual void
   StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
    * Set a property for the specified object
    *
    * @param aPropName Name of the property
    * @param aValue Boolean value
    * @param aRunnable Runnable to run on async reply
--- a/dom/bluetooth/bluetooth1/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth/bluetooth1/ipc/BluetoothParent.cpp
@@ -338,32 +338,28 @@ BluetoothRequestParent::DoRequest(const 
 }
 
 bool
 BluetoothRequestParent::DoRequest(const StartDiscoveryRequest& aRequest)
 {
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TStartDiscoveryRequest);
 
-  nsresult rv =
-    mService->StartDiscoveryInternal(mReplyRunnable.get());
-  NS_ENSURE_SUCCESS(rv, false);
+  mService->StartDiscoveryInternal(mReplyRunnable.get());
 
   return true;
 }
 
 bool
 BluetoothRequestParent::DoRequest(const StopDiscoveryRequest& aRequest)
 {
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TStopDiscoveryRequest);
 
-  nsresult rv =
-    mService->StopDiscoveryInternal(mReplyRunnable.get());
-  NS_ENSURE_SUCCESS(rv, false);
+  mService->StopDiscoveryInternal(mReplyRunnable.get());
 
   return true;
 }
 
 bool
 BluetoothRequestParent::DoRequest(const PairRequest& aRequest)
 {
   MOZ_ASSERT(mService);
--- a/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.cpp
@@ -119,30 +119,28 @@ BluetoothServiceChildProcess::GetPairedD
 {
   PairedDevicePropertiesRequest request;
   request.addresses().AppendElements(aDeviceAddresses);
 
   SendRequest(aRunnable, request);
   return NS_OK;
 }
 
-nsresult
+void
 BluetoothServiceChildProcess::StopDiscoveryInternal(
                                               BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, StopDiscoveryRequest());
-  return NS_OK;
 }
 
-nsresult
+void
 BluetoothServiceChildProcess::StartDiscoveryInternal(
                                               BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, StartDiscoveryRequest());
-  return NS_OK;
 }
 
 nsresult
 BluetoothServiceChildProcess::SetProperty(BluetoothObjectType aType,
                                           const BluetoothNamedValue& aValue,
                                           BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, SetPropertyRequest(aType, aValue));
@@ -290,17 +288,17 @@ BluetoothServiceChildProcess::ConfirmRec
   bool aConfirm,
   BluetoothReplyRunnable* aRunnable)
 {
   if(aConfirm) {
     SendRequest(aRunnable,
                 ConfirmReceivingFileRequest(nsString(aDeviceAddress)));
     return;
   }
-  
+
   SendRequest(aRunnable,
               DenyReceivingFileRequest(nsString(aDeviceAddress)));
 }
 
 void
 BluetoothServiceChildProcess::ConnectSco(BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, ConnectScoRequest());
--- a/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.h
@@ -38,20 +38,20 @@ public:
   GetPairedDevicePropertiesInternal(const nsTArray<nsString>& aDeviceAddresses,
                                     BluetoothReplyRunnable* aRunnable)
                                     override;
 
   virtual nsresult
   GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid,
                                        BluetoothReplyRunnable* aRunnable)
                                        override;
-  virtual nsresult
+  virtual void
   StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
 
-  virtual nsresult
+  virtual void
   StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
               BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult
--- a/dom/bluetooth/bluetooth2/BluetoothAdapter.cpp
+++ b/dom/bluetooth/bluetooth2/BluetoothAdapter.cpp
@@ -32,32 +32,34 @@ USING_BLUETOOTH_NAMESPACE
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothAdapter)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothAdapter,
                                                 DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDevices)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDiscoveryHandleInUse)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPairingReqs)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLeScanHandleArray)
 
   /**
    * Unregister the bluetooth signal handler after unlinked.
    *
    * This is needed to avoid ending up with exposing a deleted object to JS or
    * accessing deleted objects while receiving signals from parent process
    * after unlinked. Please see Bug 1138267 for detail informations.
    */
   UnregisterBluetoothSignalHandler(NS_LITERAL_STRING(KEY_ADAPTER), tmp);
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothAdapter,
                                                   DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDevices)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDiscoveryHandleInUse)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPairingReqs)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLeScanHandleArray)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 // QueryInterface implementation for BluetoothAdapter
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothAdapter)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(BluetoothAdapter, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(BluetoothAdapter, DOMEventTargetHelper)
@@ -105,16 +107,111 @@ public:
     BluetoothReplyRunnable::ReleaseMembers();
     mAdapter = nullptr;
   }
 
 private:
   nsRefPtr<BluetoothAdapter> mAdapter;
 };
 
+class StartLeScanTask final : public BluetoothReplyRunnable
+{
+public:
+  StartLeScanTask(BluetoothAdapter* aAdapter, Promise* aPromise,
+                  const nsTArray<nsString>& aServiceUuids)
+    : BluetoothReplyRunnable(nullptr, aPromise,
+                             NS_LITERAL_STRING("StartLeScan"))
+    , mAdapter(aAdapter)
+    , mServiceUuids(aServiceUuids)
+  {
+    MOZ_ASSERT(aPromise);
+    MOZ_ASSERT(aAdapter);
+  }
+
+  bool
+  ParseSuccessfulReply(JS::MutableHandle<JS::Value> aValue)
+  {
+    aValue.setUndefined();
+
+    AutoJSAPI jsapi;
+    NS_ENSURE_TRUE(jsapi.Init(mAdapter->GetParentObject()), false);
+    JSContext* cx = jsapi.cx();
+
+    const BluetoothValue& v = mReply->get_BluetoothReplySuccess().value();
+    NS_ENSURE_TRUE(v.type() == BluetoothValue::TnsString, false);
+
+    /**
+     * Create a new discovery handle and wrap it to return. Each
+     * discovery handle is one-time-use only.
+     */
+    nsRefPtr<BluetoothDiscoveryHandle> discoveryHandle =
+      BluetoothDiscoveryHandle::Create(mAdapter->GetParentObject(),
+                                       mServiceUuids, v.get_nsString(),
+                                       mAdapter);
+
+    if (!ToJSValue(cx, discoveryHandle, aValue)) {
+      JS_ClearPendingException(cx);
+      return false;
+    }
+
+    // Append a BluetoothDiscoveryHandle to LeScan handle array.
+    mAdapter->AppendLeScanHandle(discoveryHandle);
+
+    return true;
+  }
+
+  virtual void
+  ReleaseMembers() override
+  {
+    BluetoothReplyRunnable::ReleaseMembers();
+    mAdapter = nullptr;
+  }
+
+private:
+  nsRefPtr<BluetoothAdapter> mAdapter;
+  nsTArray<nsString> mServiceUuids;
+};
+
+class StopLeScanTask final : public BluetoothReplyRunnable
+{
+public:
+  StopLeScanTask(BluetoothAdapter* aAdapter,
+                 Promise* aPromise,
+                 const nsAString& aScanUuid)
+      : BluetoothReplyRunnable(nullptr, aPromise,
+                               NS_LITERAL_STRING("StopLeScan"))
+      , mAdapter(aAdapter)
+      , mScanUuid(aScanUuid)
+  {
+    MOZ_ASSERT(aPromise);
+    MOZ_ASSERT(aAdapter);
+    MOZ_ASSERT(!aScanUuid.IsEmpty());
+  }
+
+protected:
+  virtual bool
+  ParseSuccessfulReply(JS::MutableHandle<JS::Value> aValue) override
+  {
+    mAdapter->RemoveLeScanHandle(mScanUuid);
+    aValue.setUndefined();
+    return true;
+  }
+
+  virtual void
+  ReleaseMembers() override
+  {
+    BluetoothReplyRunnable::ReleaseMembers();
+    mAdapter = nullptr;
+  }
+
+private:
+  nsRefPtr<BluetoothAdapter> mAdapter;
+  nsString mScanUuid;
+};
+
 class GetDevicesTask : public BluetoothReplyRunnable
 {
 public:
   GetDevicesTask(BluetoothAdapter* aAdapterPtr, nsIDOMDOMRequest* aReq)
     : BluetoothReplyRunnable(aReq)
     , mAdapterPtr(aAdapterPtr)
   {
     MOZ_ASSERT(aReq && aAdapterPtr);
@@ -269,19 +366,20 @@ void
 BluetoothAdapter::SetPropertyByValue(const BluetoothNamedValue& aValue)
 {
   const nsString& name = aValue.name();
   const BluetoothValue& value = aValue.value();
   if (name.EqualsLiteral("State")) {
     mState = value.get_bool() ? BluetoothAdapterState::Enabled
                               : BluetoothAdapterState::Disabled;
 
-    // Clear saved devices when state changes to disabled
+    // Clear saved devices and LE scan handles when state changes to disabled
     if (mState == BluetoothAdapterState::Disabled) {
       mDevices.Clear();
+      mLeScanHandleArray.Clear();
     }
   } else if (name.EqualsLiteral("Name")) {
     mName = value.get_nsString();
   } else if (name.EqualsLiteral("Address")) {
     mAddress = value.get_nsString();
   } else if (name.EqualsLiteral("Discoverable")) {
     mDiscoverable = value.get_bool();
   } else if (name.EqualsLiteral("Discovering")) {
@@ -348,16 +446,20 @@ BluetoothAdapter::Notify(const Bluetooth
      * doing discovery operations.
      * The signal needs to be handled only if this adapter is holding a valid
      * discovery handle, which means that the discovery operation is triggered
      * by this adapter.
      */
     if (mDiscoveryHandleInUse) {
       HandleDeviceFound(v);
     }
+  } else if (aData.name().EqualsLiteral("LeDeviceFound")) {
+    if (!mLeScanHandleArray.IsEmpty()) {
+      HandleLeDeviceFound(v);
+    }
   } else if (aData.name().EqualsLiteral(DEVICE_PAIRED_ID)) {
     HandleDevicePaired(aData.value());
   } else if (aData.name().EqualsLiteral(DEVICE_UNPAIRED_ID)) {
     HandleDeviceUnpaired(aData.value());
   } else if (aData.name().EqualsLiteral(HFP_STATUS_CHANGED_ID) ||
              aData.name().EqualsLiteral(SCO_STATUS_CHANGED_ID) ||
              aData.name().EqualsLiteral(A2DP_STATUS_CHANGED_ID)) {
     MOZ_ASSERT(v.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
@@ -394,16 +496,36 @@ BluetoothAdapter::SetDiscoveryHandleInUs
   // Stop discovery handle in use from listening to "DeviceFound" signal
   if (mDiscoveryHandleInUse) {
     mDiscoveryHandleInUse->DisconnectFromOwner();
   }
 
   mDiscoveryHandleInUse = aDiscoveryHandle;
 }
 
+void
+BluetoothAdapter::AppendLeScanHandle(
+  BluetoothDiscoveryHandle* aDiscoveryHandle)
+{
+  mLeScanHandleArray.AppendElement(aDiscoveryHandle);
+}
+
+void
+BluetoothAdapter::RemoveLeScanHandle(const nsAString& aScanUuid)
+{
+  nsString uuid;
+  for (uint32_t i = 0; i < mLeScanHandleArray.Length(); ++i) {
+    mLeScanHandleArray[i]->GetLeScanUuid(uuid);
+    if (aScanUuid.Equals(uuid)) {
+      mLeScanHandleArray.RemoveElementAt(i);
+      break;
+    }
+  }
+}
+
 already_AddRefed<Promise>
 BluetoothAdapter::StartDiscovery(ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
@@ -432,19 +554,17 @@ BluetoothAdapter::StartDiscovery(ErrorRe
     if (!mDevices[i]->Paired()) {
       mDevices.RemoveElementAt(i);
     }
   }
 
   // Return BluetoothDiscoveryHandle in StartDiscoveryTask
   nsRefPtr<BluetoothReplyRunnable> result =
     new StartDiscoveryTask(this, promise);
-  BT_ENSURE_TRUE_REJECT(NS_SUCCEEDED(bs->StartDiscoveryInternal(result)),
-                        promise,
-                        NS_ERROR_DOM_OPERATION_ERR);
+  bs->StartDiscoveryInternal(result);
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 BluetoothAdapter::StopDiscovery(ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
@@ -470,19 +590,78 @@ BluetoothAdapter::StopDiscovery(ErrorRes
   BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
 
   BT_API2_LOGR();
 
   nsRefPtr<BluetoothReplyRunnable> result =
     new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
                                    promise,
                                    NS_LITERAL_STRING("StopDiscovery"));
-  BT_ENSURE_TRUE_REJECT(NS_SUCCEEDED(bs->StopDiscoveryInternal(result)),
+  bs->StopDiscoveryInternal(result);
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+BluetoothAdapter::StartLeScan(const nsTArray<nsString>& aServiceUuids,
+                              ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+  BT_ENSURE_TRUE_REJECT(mState == BluetoothAdapterState::Enabled,
                         promise,
-                        NS_ERROR_DOM_OPERATION_ERR);
+                        NS_ERROR_DOM_INVALID_STATE_ERR);
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
+
+  nsRefPtr<BluetoothReplyRunnable> result =
+    new StartLeScanTask(this, promise, aServiceUuids);
+  bs->StartLeScanInternal(aServiceUuids, result);
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+BluetoothAdapter::StopLeScan(BluetoothDiscoveryHandle& aDiscoveryHandle,
+                             ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+  BT_ENSURE_TRUE_REJECT(mState == BluetoothAdapterState::Enabled,
+                        promise,
+                        NS_ERROR_DOM_INVALID_STATE_ERR);
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
+
+  // Reject the request if there's no ongoing LE Scan using this handle.
+  BT_ENSURE_TRUE_REJECT(!mLeScanHandleArray.Contains(&aDiscoveryHandle),
+                        promise,
+                        NS_ERROR_DOM_BLUETOOTH_DONE);
+
+  nsString scanUuid;
+  aDiscoveryHandle.GetLeScanUuid(scanUuid);
+  nsRefPtr<BluetoothReplyRunnable> result =
+    new StopLeScanTask(this, promise, scanUuid);
+  bs->StopLeScanInternal(scanUuid, result);
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 BluetoothAdapter::SetName(const nsAString& aName, ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
@@ -875,16 +1054,52 @@ BluetoothAdapter::HandleDeviceFound(cons
     discoveredDevice = mDevices[index];
   }
 
   // Notify application of discovered device via discovery handle
   mDiscoveryHandleInUse->DispatchDeviceEvent(discoveredDevice);
 }
 
 void
+BluetoothAdapter::HandleLeDeviceFound(const BluetoothValue& aValue)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+
+  const InfallibleTArray<BluetoothNamedValue>& values =
+    aValue.get_ArrayOfBluetoothNamedValue();
+
+  int rssi = 0;
+  nsTArray<uint8_t> advData;
+  for (uint32_t i = 0; i < values.Length(); ++i) {
+    nsString name = values[i].name();
+    BluetoothValue value = values[i].value();
+    if (name.EqualsLiteral("Rssi")) {
+      MOZ_ASSERT(value.type() == BluetoothValue::Tint32_t);
+      rssi = value.get_int32_t();
+    } else if (name.EqualsLiteral("GattAdv")) {
+      MOZ_ASSERT(value.type() == BluetoothValue::TArrayOfuint8_t);
+      advData = value.get_ArrayOfuint8_t();
+    } else {
+      BT_WARNING("Receive an unexpected value name '%s'",
+                 NS_ConvertUTF16toUTF8(name).get());
+    }
+  }
+
+  // Create an individual scanned BluetoothDevice for each LeDeviceEvent even
+  // the device exists in adapter's devices array
+  nsRefPtr<BluetoothDevice> scannedDevice =
+    BluetoothDevice::Create(GetOwner(), aValue);
+
+  // Notify application of scanned devices via discovery handle
+  for (uint32_t i = 0; i < mLeScanHandleArray.Length(); ++i) {
+    mLeScanHandleArray[i]->DispatchLeDeviceEvent(scannedDevice, rssi, advData);
+  }
+}
+
+void
 BluetoothAdapter::HandleDevicePaired(const BluetoothValue& aValue)
 {
   if (NS_WARN_IF(mState != BluetoothAdapterState::Enabled)) {
     return;
   }
 
   MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
 
--- a/dom/bluetooth/bluetooth2/BluetoothAdapter.h
+++ b/dom/bluetooth/bluetooth2/BluetoothAdapter.h
@@ -95,16 +95,22 @@ public:
   already_AddRefed<Promise> Enable(ErrorResult& aRv);
   already_AddRefed<Promise> Disable(ErrorResult& aRv);
 
   already_AddRefed<Promise> SetName(const nsAString& aName, ErrorResult& aRv);
   already_AddRefed<Promise> SetDiscoverable(bool aDiscoverable,
                                             ErrorResult& aRv);
   already_AddRefed<Promise> StartDiscovery(ErrorResult& aRv);
   already_AddRefed<Promise> StopDiscovery(ErrorResult& aRv);
+
+  already_AddRefed<Promise> StartLeScan(
+    const nsTArray<nsString>& aServiceUuids, ErrorResult& aRv);
+  already_AddRefed<Promise> StopLeScan(
+    BluetoothDiscoveryHandle& aDiscoveryHandle, ErrorResult& aRv);
+
   already_AddRefed<Promise> Pair(const nsAString& aDeviceAddress,
                                  ErrorResult& aRv);
   already_AddRefed<Promise> Unpair(const nsAString& aDeviceAddress,
                                    ErrorResult& aRv);
 
   /**
    * Get a list of paired bluetooth devices.
    *
@@ -174,16 +180,31 @@ public:
    * |mDiscoveryHandleInUse| is set to the latest discovery handle when adapter
    * just starts discovery, and is reset to nullptr when discovery is stopped
    * by some adapter.
    *
    * @param aDiscoveryHandle [in] Discovery handle to set.
    */
   void SetDiscoveryHandleInUse(BluetoothDiscoveryHandle* aDiscoveryHandle);
 
+  /**
+   * Append a BluetoothDiscoveryHandle to LeScan handle array.
+   *
+   * @param aDiscoveryHandle [in] Discovery handle to be appended.
+   */
+  void AppendLeScanHandle(BluetoothDiscoveryHandle* aDiscoveryHandle);
+
+  /**
+   * Remove the BluetoothDiscoverHandle with the given UUID from LeScan handle
+   * array.
+   *
+   * @param aScanUuid [in] The UUID of the LE scan task.
+   */
+  void RemoveLeScanHandle(const nsAString& aScanUuid);
+
 private:
   BluetoothAdapter(nsPIDOMWindow* aOwner, const BluetoothValue& aValue);
   ~BluetoothAdapter();
 
   /**
    * Set adapter properties according to properties array.
    *
    * @param aValue [in] Properties array to set with
@@ -246,16 +267,23 @@ private:
    * @param aValue [in] Properties array of the unpaired device.
    *                    The array should contain two properties:
    *                    - nsString  'Address'
    *                    - bool      'Paired'
    */
   void HandleDeviceUnpaired(const BluetoothValue& aValue);
 
   /**
+   * Handle "LeDeviceFound" bluetooth signal.
+   *
+   * @param aValue [in] Properties array of the scanned device.
+   */
+  void HandleLeDeviceFound(const BluetoothValue& aValue);
+
+  /**
    * Fire BluetoothAttributeEvent to trigger onattributechanged event handler.
    */
   void DispatchAttributeEvent(const Sequence<nsString>& aTypes);
 
   /**
    * Fire BluetoothDeviceEvent to trigger
    * ondeviceparied/ondeviceunpaired event handler.
    *
@@ -334,16 +362,24 @@ private:
    *
    * This variable is set to the latest discovery handle when adapter just
    * starts discovery, and is reset to nullptr when discovery is stopped by
    * some adapter.
    */
   nsRefPtr<BluetoothDiscoveryHandle> mDiscoveryHandleInUse;
 
   /**
+   * Handles to fire 'ondevicefound' event handler for scanned device
+   *
+   * Each non-stopped LeScan process has a LeScan handle which is
+   * responsible to dispatch LeDeviceEvent.
+   */
+  nsTArray<nsRefPtr<BluetoothDiscoveryHandle> > mLeScanHandleArray;
+
+  /**
    * nsRefPtr array of BluetoothDevices created by this adapter. The array is
    * empty when adapter state is Disabled.
    *
    * Devices will be appended when
    *   1) adapter is enabling: Paired devices reported by stack.
    *   2) adapter is discovering: Discovered devices during discovery operation.
    *   3) adapter paired with a device: The paired device reported by stack.
    * Note devices with identical address won't be appended.
--- a/dom/bluetooth/bluetooth2/BluetoothDevice.cpp
+++ b/dom/bluetooth/bluetooth2/BluetoothDevice.cpp
@@ -157,16 +157,21 @@ BluetoothDevice::SetPropertyByValue(cons
   } else if (name.EqualsLiteral("UUIDs")) {
     // We assume the received uuids array is sorted without duplicate items.
     // If it's not, we require additional processing before assigning it
     // directly.
     mUuids = value.get_ArrayOfnsString();
     BluetoothDeviceBinding::ClearCachedUuidsValue(this);
   } else if (name.EqualsLiteral("Type")) {
     mType = ConvertUint32ToDeviceType(value.get_uint32_t());
+  } else if (name.EqualsLiteral("GattAdv")) {
+    MOZ_ASSERT(value.type() == BluetoothValue::TArrayOfuint8_t);
+    nsTArray<uint8_t> advData;
+    advData = value.get_ArrayOfuint8_t();
+    UpdatePropertiesFromAdvData(advData);
   } else {
     BT_WARNING("Not handling device property: %s",
                NS_ConvertUTF16toUTF8(name).get());
   }
 }
 
 already_AddRefed<Promise>
 BluetoothDevice::FetchUuids(ErrorResult& aRv)
@@ -316,14 +321,112 @@ BluetoothDevice::GetGatt()
                  nullptr);
   if (!mGatt) {
     mGatt = new BluetoothGatt(GetOwner(), mAddress);
   }
 
   return mGatt;
 }
 
+void
+BluetoothDevice::UpdatePropertiesFromAdvData(const nsTArray<uint8_t>& aAdvData)
+{
+  // According to BT Core Spec. Vol 3 - Ch 11, advertisement data consists of a
+  // significant part and a non-significant part.
+  // The significant part contains a sequence of AD structures. Each AD
+  // structure shall have a Length field of one octet, which contains the
+  // Length value, and a Data field of Length octets.
+  unsigned int offset = 0;
+  while (offset < aAdvData.Length()) {
+    int dataFieldLength = aAdvData[offset++];
+
+    // According to BT Core Spec, it only occurs to allow an early termination
+    // of the Advertising data.
+    if (dataFieldLength <= 0) {
+      break;
+    }
+
+    // Length of the data field which is composed by AD type (1 byte) and
+    // AD data (dataFieldLength -1 bytes)
+    int dataLength = dataFieldLength - 1;
+    if (offset + dataLength >= aAdvData.Length()) {
+      break;
+    }
+
+    // Update UUIDs and name of BluetoothDevice.
+    int type = aAdvData[offset++];
+    switch (type) {
+      case GAP_INCOMPLETE_UUID16:
+      case GAP_COMPLETE_UUID16:
+      case GAP_INCOMPLETE_UUID32:
+      case GAP_COMPLETE_UUID32:
+      case GAP_INCOMPLETE_UUID128:
+      case GAP_COMPLETE_UUID128: {
+        mUuids.Clear();
+
+        // The length of uint16_t UUID array
+        uint8_t len = 0;
+        if (GAP_INCOMPLETE_UUID16 && GAP_COMPLETE_UUID16) {
+          len = 1;
+        } else if (GAP_INCOMPLETE_UUID32 && GAP_COMPLETE_UUID32) {
+          len = 2;
+        } else {
+          len = 8;
+        }
+        uint16_t uuid[len];
+
+        while (dataLength > 0) {
+          // Read (len * 2) bytes from the data buffer and compose a 16-bits
+          // UUID array.
+          for (uint8_t i = 0; i < len; ++i) {
+            uuid[i] = aAdvData[offset++];
+            uuid[i] += (aAdvData[offset++] << 8);
+            dataLength -= 2;
+          }
+
+          char uuidStr[36];
+          if (type == GAP_INCOMPLETE_UUID16 || type == GAP_COMPLETE_UUID16) {
+            // Convert 16-bits UUID into string.
+            sprintf(uuidStr, "0000%04x-0000-1000-8000-00805f9b34fb", uuid[0]);
+          } else if (type == GAP_INCOMPLETE_UUID32 || type == GAP_COMPLETE_UUID32) {
+            // Convert 32-bits UUID into string.
+            sprintf(uuidStr, "%04x%04x-0000-1000-8000-00805f9b34fb",
+              uuid[1], uuid[0]);
+          } else if (type == GAP_INCOMPLETE_UUID128 || type == GAP_COMPLETE_UUID128) {
+            // Convert 128-bits UUID into string.
+            sprintf(uuidStr, "%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
+              uuid[7], uuid[6], uuid[5], uuid[4],
+              uuid[3], uuid[2], uuid[1], uuid[0]);
+          }
+          nsString uuidNsString;
+          uuidNsString.AssignLiteral(uuidStr);
+
+          mUuids.AppendElement(uuidNsString);
+        }
+
+        BluetoothDeviceBinding::ClearCachedUuidsValue(this);
+        break;
+      }
+      case GAP_SHORTENED_NAME:
+        if (!mName.IsEmpty()) break;
+      case GAP_COMPLETE_NAME: {
+        // Read device name from data buffer.
+        char deviceName[dataLength];
+        for (int i = 0; i < dataLength; ++i) {
+          deviceName[i] = aAdvData[offset++];
+        }
+
+        mName.AssignASCII(deviceName, dataLength);
+        break;
+      }
+      default:
+        offset += dataLength;
+        break;
+    }
+  }
+}
+
 JSObject*
 BluetoothDevice::WrapObject(JSContext* aContext,
                             JS::Handle<JSObject*> aGivenProto)
 {
   return BluetoothDeviceBinding::Wrap(aContext, this, aGivenProto);
 }
--- a/dom/bluetooth/bluetooth2/BluetoothDevice.h
+++ b/dom/bluetooth/bluetooth2/BluetoothDevice.h
@@ -139,16 +139,26 @@ private:
    * Check whether value of given device property has changed.
    *
    * @param aType  [in] Device property to check
    * @param aValue [in] New value of the device property
    */
   bool IsDeviceAttributeChanged(BluetoothDeviceAttribute aType,
                                 const BluetoothValue& aValue);
 
+  /**
+   * Parse advertising data to update device properties.
+   *
+   * Parse 'Advertising Data Type' from an inquiry response and set name, UUIDs
+   * and COD if they exist in ADV data.
+   *
+   * @param aAdvData [in] advertising data which provided by the LeScan result.
+   */
+   void UpdatePropertiesFromAdvData(const nsTArray<uint8_t>& aAdvData);
+
   /****************************************************************************
    * Variables
    ***************************************************************************/
   /**
    * BD address of this device.
    */
   nsString mAddress;
 
--- a/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.cpp
+++ b/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.cpp
@@ -1,65 +1,158 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=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/. */
 
-#include "BluetoothDiscoveryHandle.h"
 #include "BluetoothService.h"
-
-#include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/dom/BluetoothDeviceEvent.h"
 #include "mozilla/dom/BluetoothDiscoveryHandleBinding.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
+#include "mozilla/dom/bluetooth/BluetoothDiscoveryHandle.h"
+#include "mozilla/dom/bluetooth/BluetoothLeDeviceEvent.h"
+#include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "nsThreadUtils.h"
 
 USING_BLUETOOTH_NAMESPACE
 
+NS_IMPL_CYCLE_COLLECTION_INHERITED(BluetoothDiscoveryHandle,
+                                   DOMEventTargetHelper,
+                                   mAdapter)
+
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothDiscoveryHandle)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(BluetoothDiscoveryHandle, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(BluetoothDiscoveryHandle, DOMEventTargetHelper)
 
 BluetoothDiscoveryHandle::BluetoothDiscoveryHandle(nsPIDOMWindow* aWindow)
   : DOMEventTargetHelper(aWindow)
+  , mLeScanUuid(EmptyString())
+{
+  MOZ_ASSERT(aWindow);
+}
+
+BluetoothDiscoveryHandle::BluetoothDiscoveryHandle(
+  nsPIDOMWindow* aWindow,
+  const nsTArray<nsString>& aServiceUuids,
+  const nsAString& aLeScanUuid,
+  BluetoothAdapter* aAdapter)
+  : DOMEventTargetHelper(aWindow)
+  , mLeScanUuid(aLeScanUuid)
+  , mServiceUuids(aServiceUuids)
+  , mAdapter(aAdapter)
 {
   MOZ_ASSERT(aWindow);
 }
 
 BluetoothDiscoveryHandle::~BluetoothDiscoveryHandle()
 {
+  // Remove itself from the adapter's mLeScanHandleArray
+  if (!mLeScanUuid.IsEmpty() && mAdapter != nullptr) {
+    mAdapter->RemoveLeScanHandle(mLeScanUuid);
+  }
+}
+
+void
+BluetoothDiscoveryHandle::DisconnectFromOwner()
+{
+  DOMEventTargetHelper::DisconnectFromOwner();
+
+  // Remove itself from the adapter's mLeScanHandleArray
+  if (!mLeScanUuid.IsEmpty() && mAdapter != nullptr) {
+    mAdapter->RemoveLeScanHandle(mLeScanUuid);
+  }
 }
 
 // static
 already_AddRefed<BluetoothDiscoveryHandle>
 BluetoothDiscoveryHandle::Create(nsPIDOMWindow* aWindow)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aWindow);
 
   nsRefPtr<BluetoothDiscoveryHandle> handle =
     new BluetoothDiscoveryHandle(aWindow);
   return handle.forget();
 }
 
+already_AddRefed<BluetoothDiscoveryHandle>
+BluetoothDiscoveryHandle::Create(
+  nsPIDOMWindow* aWindow,
+  const nsTArray<nsString>& aServiceUuids,
+  const nsAString& aLeScanUuid,
+  BluetoothAdapter* aAdapter)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(aAdapter);
+
+  nsRefPtr<BluetoothDiscoveryHandle> handle =
+    new BluetoothDiscoveryHandle(aWindow, aServiceUuids, aLeScanUuid, aAdapter);
+  return handle.forget();
+}
+
 void
 BluetoothDiscoveryHandle::DispatchDeviceEvent(BluetoothDevice* aDevice)
 {
   MOZ_ASSERT(aDevice);
 
   BluetoothDeviceEventInit init;
   init.mDevice = aDevice;
 
   nsRefPtr<BluetoothDeviceEvent> event =
     BluetoothDeviceEvent::Constructor(this,
                                       NS_LITERAL_STRING("devicefound"),
                                       init);
   DispatchTrustedEvent(event);
 }
 
+void
+BluetoothDiscoveryHandle::DispatchLeDeviceEvent(BluetoothDevice* aLeDevice,
+  int32_t aRssi, nsTArray<uint8_t>& aScanRecord)
+{
+  MOZ_ASSERT(aLeDevice);
+
+  nsTArray<nsString> remoteUuids;
+  aLeDevice->GetUuids(remoteUuids);
+
+  bool hasUuidsFilter = !mServiceUuids.IsEmpty();
+  bool noAdvertisingUuid  = remoteUuids.IsEmpty();
+  // If a LE device doesn't advertise its service UUIDs, it can't possibly pass
+  // the UUIDs filter.
+  if (hasUuidsFilter && noAdvertisingUuid) {
+    return;
+  }
+
+  // The web API startLeScan() makes the device's adapter start seeking for
+  // remote LE devices advertising given service UUIDs.
+  // Since current Bluetooth stack can't filter the results of LeScan by UUIDs,
+  // gecko has to filter the results and dispach what API asked for.
+  bool matched = false;
+  for (size_t index = 0; index < remoteUuids.Length(); ++index) {
+    if (mServiceUuids.Contains(remoteUuids[index])) {
+      matched = true;
+      break;
+    }
+  }
+
+  // Dispach 'devicefound 'event only if
+  //  - the service UUIDs in the scan record matchs the given UUIDs.
+  //  - the given UUIDs is empty.
+  if (matched || mServiceUuids.IsEmpty()) {
+    nsRefPtr<BluetoothLeDeviceEvent> event =
+      BluetoothLeDeviceEvent::Constructor(this,
+                                          NS_LITERAL_STRING("devicefound"),
+                                          aLeDevice,
+                                          aRssi,
+                                          aScanRecord);
+    DispatchTrustedEvent(event);
+  }
+}
+
 JSObject*
 BluetoothDiscoveryHandle::WrapObject(JSContext* aCx,
                                      JS::Handle<JSObject*> aGivenProto)
 {
   return BluetoothDiscoveryHandleBinding::Wrap(aCx, this, aGivenProto);
 }
--- a/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.h
+++ b/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.h
@@ -2,41 +2,92 @@
 /* vim: set ts=8 sts=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/. */
 
 #ifndef mozilla_dom_bluetooth_bluetoothdiscoveryhandle_h
 #define mozilla_dom_bluetooth_bluetoothdiscoveryhandle_h
 
-#include "BluetoothCommon.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/bluetooth/BluetoothAdapter.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
-#include "mozilla/DOMEventTargetHelper.h"
 #include "nsISupportsImpl.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothDevice;
 
 class BluetoothDiscoveryHandle final : public DOMEventTargetHelper
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothDiscoveryHandle,
+                                           DOMEventTargetHelper)
 
   static already_AddRefed<BluetoothDiscoveryHandle>
     Create(nsPIDOMWindow* aWindow);
 
+  static already_AddRefed<BluetoothDiscoveryHandle>
+    Create(nsPIDOMWindow* aWindow,
+           const nsTArray<nsString>& aServiceUuids,
+           const nsAString& aLeScanUuid,
+           BluetoothAdapter* aAdapter);
+
   void DispatchDeviceEvent(BluetoothDevice* aDevice);
 
+  void DispatchLeDeviceEvent(BluetoothDevice* aLeDevice,
+                             int32_t aRssi,
+                             nsTArray<uint8_t>& aScanRecord);
+
   IMPL_EVENT_HANDLER(devicefound);
 
+  void GetLeScanUuid(nsString& aLeScanUuid) const
+  {
+    aLeScanUuid = mLeScanUuid;
+  }
+
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
+  virtual void DisconnectFromOwner() override;
+
 private:
   BluetoothDiscoveryHandle(nsPIDOMWindow* aWindow);
+
+  BluetoothDiscoveryHandle(nsPIDOMWindow* aWindow,
+                           const nsTArray<nsString>& aServiceUuids,
+                           const nsAString& aLeScanUuid,
+                           BluetoothAdapter* aAdapter);
+
   ~BluetoothDiscoveryHandle();
+
+  /**
+   * Random generated UUID of LE scan
+   *
+   * This UUID is used only when the handle is built for LE scan.
+   * If BluetoothDiscoveryHandle is built for classic discovery, the value would
+   * remain empty string during the entire life cycle.
+   */
+  nsString mLeScanUuid;
+
+  /**
+   * A DOMString array of service UUIDs to discover / scan for.
+   *
+   * This array is only used by LE scan. If BluetoothDiscoveryHandle is built
+   * for classic discovery, the array should be empty.
+   */
+  nsTArray<nsString> mServiceUuids;
+
+  /**
+   * The adapter which called startLeScan and created this discovery handle
+   *
+   * If BluetoothDiscoveryHandle is built for classic discovery, this value
+   * should be nullptr.
+   */
+  nsRefPtr<BluetoothAdapter> mAdapter;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_bluetoothdiscoveryhandle_h
--- a/dom/bluetooth/bluetooth2/BluetoothGatt.cpp
+++ b/dom/bluetooth/bluetooth2/BluetoothGatt.cpp
@@ -8,17 +8,16 @@
 #include "BluetoothService.h"
 #include "BluetoothUtils.h"
 #include "mozilla/dom/bluetooth/BluetoothCommon.h"
 #include "mozilla/dom/bluetooth/BluetoothGatt.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/dom/BluetoothGattBinding.h"
 #include "mozilla/dom/BluetoothGattCharacteristicEvent.h"
 #include "mozilla/dom/Promise.h"
-#include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 USING_BLUETOOTH_NAMESPACE
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothGatt)
@@ -72,37 +71,16 @@ BluetoothGatt::~BluetoothGatt()
       new BluetoothVoidReplyRunnable(nullptr);
     bs->UnregisterGattClientInternal(mClientIf, result);
   }
 
   UnregisterBluetoothSignalHandler(mAppUuid, this);
 }
 
 void
-BluetoothGatt::GenerateUuid(nsAString &aUuidString)
-{
-  nsresult rv;
-  nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
-    do_GetService("@mozilla.org/uuid-generator;1", &rv);
-  NS_ENSURE_SUCCESS_VOID(rv);
-
-  nsID uuid;
-  rv = uuidGenerator->GenerateUUIDInPlace(&uuid);
-  NS_ENSURE_SUCCESS_VOID(rv);
-
-  // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format
-  char uuidBuffer[NSID_LENGTH];
-  uuid.ToProvidedString(uuidBuffer);
-  NS_ConvertASCIItoUTF16 uuidString(uuidBuffer);
-
-  // Remove {} and the null terminator
-  aUuidString.Assign(Substring(uuidString, 1, NSID_LENGTH - 3));
-}
-
-void
 BluetoothGatt::DisconnectFromOwner()
 {
   DOMEventTargetHelper::DisconnectFromOwner();
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   if (mClientIf > 0) {
@@ -195,19 +173,19 @@ public:
   }
 
   bool
   ParseSuccessfulReply(JS::MutableHandle<JS::Value> aValue)
   {
     aValue.setUndefined();
 
     const BluetoothValue& v = mReply->get_BluetoothReplySuccess().value();
-    NS_ENSURE_TRUE(v.type() == BluetoothValue::Tuint32_t, false);
+    NS_ENSURE_TRUE(v.type() == BluetoothValue::Tint32_t, false);
 
-    aValue.setInt32(static_cast<int32_t>(v.get_uint32_t()));
+    aValue.setInt32(static_cast<int32_t>(v.get_int32_t()));
     return true;
   }
 };
 
 already_AddRefed<Promise>
 BluetoothGatt::ReadRemoteRssi(ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
--- a/dom/bluetooth/bluetooth2/BluetoothGatt.h
+++ b/dom/bluetooth/bluetooth2/BluetoothGatt.h
@@ -83,23 +83,16 @@ private:
    * Update mConnectionState to aState and fire
    * connectionstatechanged event to the application.
    *
    * @param aState [in] New connection state
    */
   void UpdateConnectionState(BluetoothConnectionState aState);
 
   /**
-   * Generate a random uuid.
-   *
-   * @param aUuidString [out] String to store the generated uuid.
-   */
-  void GenerateUuid(nsAString &aUuidString);
-
-  /**
    * Add newly discovered GATT services into mServices and update the cache
    * value of mServices.
    *
    * @param aValue [in] BluetoothValue which contains an array of
    *                    BluetoothGattServiceId of all discovered services.
    */
   void HandleServicesDiscovered(const BluetoothValue& aValue);
 
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluetooth2/BluetoothLeDeviceEvent.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/bluetooth/BluetoothLeDeviceEvent.h"
+
+#include "js/GCAPI.h"
+#include "jsfriendapi.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/BluetoothLeDeviceEventBinding.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/PrimitiveConversions.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/bluetooth/BluetoothDevice.h"
+
+USING_BLUETOOTH_NAMESPACE
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothLeDeviceEvent)
+
+NS_IMPL_ADDREF_INHERITED(BluetoothLeDeviceEvent, Event)
+NS_IMPL_RELEASE_INHERITED(BluetoothLeDeviceEvent, Event)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothLeDeviceEvent, Event)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDevice)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(BluetoothLeDeviceEvent, Event)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScanRecord)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothLeDeviceEvent, Event)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDevice)
+  tmp->mScanRecord = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothLeDeviceEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+BluetoothLeDeviceEvent::BluetoothLeDeviceEvent(mozilla::dom::EventTarget* aOwner)
+  : Event(aOwner, nullptr, nullptr)
+{
+  mozilla::HoldJSObjects(this);
+}
+
+BluetoothLeDeviceEvent::~BluetoothLeDeviceEvent()
+{
+  mScanRecord = nullptr;
+  mozilla::DropJSObjects(this);
+}
+
+JSObject*
+BluetoothLeDeviceEvent::WrapObjectInternal(JSContext* aCx,
+                                           JS::Handle<JSObject*> aGivenProto)
+{
+  return BluetoothLeDeviceEventBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<BluetoothLeDeviceEvent>
+BluetoothLeDeviceEvent::Constructor(
+  mozilla::dom::EventTarget* aOwner,
+  const nsAString& aType,
+  BluetoothDevice* const aDevice,
+  const int16_t aRssi,
+  const nsTArray<uint8_t>& aScanRecord)
+{
+  nsRefPtr<BluetoothLeDeviceEvent> e = new BluetoothLeDeviceEvent(aOwner);
+  bool trusted = e->Init(aOwner);
+  e->InitEvent(aType, false, false);
+  e->mDevice = aDevice;
+  e->mRssi = aRssi;
+  e->mRawScanRecord = aScanRecord;
+
+  e->SetTrusted(trusted);
+  return e.forget();
+}
+
+already_AddRefed<BluetoothLeDeviceEvent>
+BluetoothLeDeviceEvent::Constructor(
+  const GlobalObject& aGlobal,
+  const nsAString& aType,
+  const BluetoothLeDeviceEventInit& aEventInitDict,
+  ErrorResult& aRv)
+{
+  nsCOMPtr<mozilla::dom::EventTarget> owner =
+    do_QueryInterface(aGlobal.GetAsSupports());
+
+  nsRefPtr<BluetoothLeDeviceEvent> e = new BluetoothLeDeviceEvent(owner);
+  bool trusted = e->Init(owner);
+  e->InitEvent(aType, aEventInitDict.mBubbles, aEventInitDict.mCancelable);
+  e->mDevice = aEventInitDict.mDevice;
+  e->mRssi = aEventInitDict.mRssi;
+
+  aEventInitDict.mScanRecord.ComputeLengthAndData();
+  const uint8_t* data = aEventInitDict.mScanRecord.Data();
+  size_t length = aEventInitDict.mScanRecord.Length();
+  e->mScanRecord = ArrayBuffer::Create(aGlobal.Context(), length, data);
+  if (!e->mScanRecord) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return nullptr;
+  }
+
+  e->SetTrusted(trusted);
+  return e.forget();
+}
+
+BluetoothDevice*
+BluetoothLeDeviceEvent::Device() const
+{
+  return mDevice;
+}
+
+int16_t
+BluetoothLeDeviceEvent::Rssi() const
+{
+  return mRssi;
+}
+
+void
+BluetoothLeDeviceEvent::GetScanRecord(
+  JSContext* cx,
+  JS::MutableHandle<JSObject*> aScanRecord,
+  ErrorResult& aRv)
+{
+  if (!mScanRecord) {
+    mScanRecord = ArrayBuffer::Create(cx,
+                                      mRawScanRecord.Length(),
+                                      mRawScanRecord.Elements());
+    if (!mScanRecord) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return;
+    }
+    mRawScanRecord.Clear();
+  }
+  JS::ExposeObjectToActiveJS(mScanRecord);
+  aScanRecord.set(mScanRecord);
+
+  return;
+}
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluetooth2/BluetoothLeDeviceEvent.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_bluetooth_bluetoothledeviceevent_h
+#define mozilla_dom_bluetooth_bluetoothledeviceevent_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BluetoothLeDeviceEventBinding.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothLeDeviceEvent : public Event
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(BluetoothLeDeviceEvent,
+                                                         Event)
+protected:
+  virtual ~BluetoothLeDeviceEvent();
+  explicit BluetoothLeDeviceEvent(mozilla::dom::EventTarget* aOwner);
+
+  nsRefPtr<BluetoothDevice> mDevice;
+  int16_t mRssi;
+  JS::Heap<JSObject*> mScanRecord;
+
+public:
+  virtual JSObject* WrapObjectInternal(
+    JSContext* aCx,
+    JS::Handle<JSObject*> aGivenProto) override;
+
+  static already_AddRefed<BluetoothLeDeviceEvent>
+    Constructor(EventTarget* aOwner,
+                const nsAString& aType,
+                BluetoothDevice* const aDevice,
+                const int16_t aRssi,
+                const nsTArray<uint8_t>& aScanRecord);
+
+  static already_AddRefed<BluetoothLeDeviceEvent>
+    Constructor(const GlobalObject& aGlobal,
+                const nsAString& aType,
+                const BluetoothLeDeviceEventInit& aEventInitDict,
+                ErrorResult& aRv);
+
+  BluetoothDevice* Device() const;
+
+  int16_t Rssi() const;
+
+  void GetScanRecord(JSContext* cx,
+                     JS::MutableHandle<JSObject*> aScanRecord,
+                     ErrorResult& aRv);
+
+  private:
+    nsTArray<uint8_t> mRawScanRecord;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif // mozilla_dom_bluetooth_bluetoothledeviceevent_h
--- a/dom/bluetooth/bluetooth2/BluetoothService.h
+++ b/dom/bluetooth/bluetooth2/BluetoothService.h
@@ -171,29 +171,39 @@ public:
    * @return NS_OK on success, NS_ERROR_FAILURE otherwise
    */
   virtual nsresult
   FetchUuidsInternal(const nsAString& aDeviceAddress,
                      BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
    * Stop device discovery (platform specific implementation)
-   *
-   * @return NS_OK if discovery stopped correctly, false otherwise
    */
-  virtual nsresult
+  virtual void
   StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
    * Start device discovery (platform specific implementation)
-   *
-   * @return NS_OK if discovery stopped correctly, false otherwise
+   */
+  virtual void
+  StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0;
+
+  /**
+   * Stops an ongoing Bluetooth LE device scan.
    */
-  virtual nsresult
-  StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0;
+  virtual void
+  StopLeScanInternal(const nsAString& aScanUuid,
+                     BluetoothReplyRunnable* aRunnable) = 0;
+
+  /**
+   * Starts a Bluetooth LE device scan.
+   */
+  virtual void
+  StartLeScanInternal(const nsTArray<nsString>& aServiceUuids,
+                      BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
    * Set a property for the specified object
    *
    * @param aPropName Name of the property
    * @param aValue Boolean value
    * @param aRunnable Runnable to run on async reply
    *
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothParent.cpp
@@ -197,16 +197,20 @@ BluetoothParent::RecvPBluetoothRequestCo
     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::TStartLeScanRequest:
+      return actor->DoRequest(aRequest.get_StartLeScanRequest());
+    case Request::TStopLeScanRequest:
+      return actor->DoRequest(aRequest.get_StopLeScanRequest());
     case Request::TPairRequest:
       return actor->DoRequest(aRequest.get_PairRequest());
     case Request::TUnpairRequest:
       return actor->DoRequest(aRequest.get_UnpairRequest());
     case Request::TPairedDevicePropertiesRequest:
       return actor->DoRequest(aRequest.get_PairedDevicePropertiesRequest());
     case Request::TConnectedDevicePropertiesRequest:
       return actor->DoRequest(aRequest.get_ConnectedDevicePropertiesRequest());
@@ -398,32 +402,50 @@ BluetoothRequestParent::DoRequest(const 
 }
 
 bool
 BluetoothRequestParent::DoRequest(const StartDiscoveryRequest& aRequest)
 {
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TStartDiscoveryRequest);
 
-  nsresult rv =
-    mService->StartDiscoveryInternal(mReplyRunnable.get());
-  NS_ENSURE_SUCCESS(rv, false);
+  mService->StartDiscoveryInternal(mReplyRunnable.get());
 
   return true;
 }
 
 bool
 BluetoothRequestParent::DoRequest(const StopDiscoveryRequest& aRequest)
 {
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TStopDiscoveryRequest);
 
-  nsresult rv =
-    mService->StopDiscoveryInternal(mReplyRunnable.get());
-  NS_ENSURE_SUCCESS(rv, false);
+  mService->StopDiscoveryInternal(mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(const StartLeScanRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TStartLeScanRequest);
+
+  mService->StartLeScanInternal(aRequest.serviceUuids(), mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(const StopLeScanRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TStopLeScanRequest);
+
+  mService->StopLeScanInternal(aRequest.scanUuid(), mReplyRunnable.get());
 
   return true;
 }
 
 bool
 BluetoothRequestParent::DoRequest(const PairRequest& aRequest)
 {
   MOZ_ASSERT(mService);
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothParent.h
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothParent.h
@@ -142,16 +142,22 @@ protected:
 
   bool
   DoRequest(const StartDiscoveryRequest& aRequest);
 
   bool
   DoRequest(const StopDiscoveryRequest& aRequest);
 
   bool
+  DoRequest(const StartLeScanRequest& aRequest);
+
+  bool
+  DoRequest(const StopLeScanRequest& aRequest);
+
+  bool
   DoRequest(const PairRequest& aRequest);
 
   bool
   DoRequest(const UnpairRequest& aRequest);
 
   bool
   DoRequest(const PairedDevicePropertiesRequest& aRequest);
 
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
@@ -142,30 +142,44 @@ BluetoothServiceChildProcess::GetPairedD
 nsresult
 BluetoothServiceChildProcess::FetchUuidsInternal(
   const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, FetchUuidsRequest(nsString(aDeviceAddress)));
   return NS_OK;
 }
 
-nsresult
+void
 BluetoothServiceChildProcess::StopDiscoveryInternal(
-                                              BluetoothReplyRunnable* aRunnable)
+   BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, StopDiscoveryRequest());
-  return NS_OK;
+}
+
+void
+BluetoothServiceChildProcess::StartDiscoveryInternal(
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable, StartDiscoveryRequest());
 }
 
-nsresult
-BluetoothServiceChildProcess::StartDiscoveryInternal(
-                                              BluetoothReplyRunnable* aRunnable)
+void
+BluetoothServiceChildProcess::StopLeScanInternal(
+  const nsAString& aScanUuid,
+  BluetoothReplyRunnable* aRunnable)
 {
-  SendRequest(aRunnable, StartDiscoveryRequest());
-  return NS_OK;
+  SendRequest(aRunnable, StopLeScanRequest(nsString(aScanUuid)));
+}
+
+void
+BluetoothServiceChildProcess::StartLeScanInternal(
+  const nsTArray<nsString>& aServiceUuids,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable, StartLeScanRequest(aServiceUuids));
 }
 
 nsresult
 BluetoothServiceChildProcess::SetProperty(BluetoothObjectType aType,
                                           const BluetoothNamedValue& aValue,
                                           BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, SetPropertyRequest(aType, aValue));
@@ -324,17 +338,17 @@ BluetoothServiceChildProcess::ConfirmRec
   bool aConfirm,
   BluetoothReplyRunnable* aRunnable)
 {
   if(aConfirm) {
     SendRequest(aRunnable,
                 ConfirmReceivingFileRequest(nsString(aDeviceAddress)));
     return;
   }
-  
+
   SendRequest(aRunnable,
               DenyReceivingFileRequest(nsString(aDeviceAddress)));
 }
 
 void
 BluetoothServiceChildProcess::ConnectSco(BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, ConnectScoRequest());
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.h
@@ -48,22 +48,30 @@ public:
   virtual nsresult
   GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid,
                                        BluetoothReplyRunnable* aRunnable)
                                        override;
   virtual nsresult
   FetchUuidsInternal(const nsAString& aDeviceAddress,
                      BluetoothReplyRunnable* aRunnable) override;
 
-  virtual nsresult
+  virtual void
   StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
 
-  virtual nsresult
+  virtual void
   StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
 
+  virtual void
+  StopLeScanInternal(const nsAString& aScanUuid,
+                     BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  StartLeScanInternal(const nsTArray<nsString>& aServiceUuids,
+                      BluetoothReplyRunnable* aRunnable) override;
+
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
               BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult
   CreatePairedDeviceInternal(const nsAString& aAddress,
                              int aTimeout,
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothTypes.ipdlh
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothTypes.ipdlh
@@ -23,16 +23,17 @@ namespace bluetooth {
 
 /**
  * Value structure for returns from bluetooth. Currently modeled after dbus
  * returns, which can be a 32-bit int, an UTF16 string, a bool, or an array of
  * UTF16 strings. Can also hold key-value pairs for dictionary-ish access.
  */
 union BluetoothValue
 {
+  int32_t;
   uint32_t;
   nsString;
   bool;
   nsString[];
   uint8_t[];
   BluetoothNamedValue[];
   BluetoothGattId;
   BluetoothGattId[];
--- a/dom/bluetooth/bluetooth2/ipc/PBluetooth.ipdl
+++ b/dom/bluetooth/bluetooth2/ipc/PBluetooth.ipdl
@@ -48,16 +48,26 @@ struct GetPropertyRequest
 struct StartDiscoveryRequest
 {
 };
 
 struct StopDiscoveryRequest
 {
 };
 
+struct StartLeScanRequest
+{
+  nsString[] serviceUuids;
+};
+
+struct StopLeScanRequest
+{
+  nsString scanUuid;
+};
+
 struct PairRequest
 {
   nsString address;
   uint32_t timeoutMS;
 };
 
 struct UnpairRequest
 {
@@ -269,16 +279,18 @@ union Request
 {
   GetAdaptersRequest;
   StartBluetoothRequest;
   StopBluetoothRequest;
   SetPropertyRequest;
   GetPropertyRequest;
   StartDiscoveryRequest;
   StopDiscoveryRequest;
+  StartLeScanRequest;
+  StopLeScanRequest;
   PairRequest;
   UnpairRequest;
   PinReplyRequest;
   SspReplyRequest;
   SetPinCodeRequest;
   SetPasskeyRequest;
   ConfirmPairingConfirmationRequest;
   DenyPairingConfirmationRequest;
--- a/dom/bluetooth/bluez/BluetoothDBusService.cpp
+++ b/dom/bluetooth/bluez/BluetoothDBusService.cpp
@@ -2839,26 +2839,26 @@ BluetoothDBusService::SendSinkMessage(co
     return NS_ERROR_FAILURE;
   }
 
   MOZ_ASSERT(!sAdapterPath.IsEmpty());
   nsString objectPath = GetObjectPathFromAddress(sAdapterPath, aDeviceAddress);
   return SendAsyncDBusMessage(objectPath, DBUS_SINK_IFACE, aMessage, callback);
 }
 
-nsresult
+void
 BluetoothDBusService::StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable)
 {
-  return SendDiscoveryMessage("StopDiscovery", aRunnable);
+  SendDiscoveryMessage("StopDiscovery", aRunnable);
 }
 
-nsresult
+void
 BluetoothDBusService::StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable)
 {
-  return SendDiscoveryMessage("StartDiscovery", aRunnable);
+  SendDiscoveryMessage("StartDiscovery", aRunnable);
 }
 
 class BluetoothArrayOfDevicePropertiesReplyHandler : public DBusReplyHandler
 {
 public:
   BluetoothArrayOfDevicePropertiesReplyHandler(
     const nsTArray<nsString>& aDeviceAddresses,
     const FilterFunc aFilterFunc, BluetoothReplyRunnable* aRunnable)
@@ -4691,16 +4691,30 @@ BluetoothDBusService::UpdateNotification
   a2dp->GetAddress(deviceAddress);
 
   Task* task = new UpdateNotificationTask(deviceAddress, aEventId, aData);
   DispatchToDBusThread(task);
 }
 
 #ifdef MOZ_B2G_BT_API_V2
 void
+BluetoothDBusService::StartLeScanInternal(
+  const nsTArray<nsString>& aServiceUuids,
+  BluetoothReplyRunnable* aRunnable);
+{
+}
+
+void
+BluetoothDBusService::StopLeScanInternal(
+  const nsAString& aAppUuid,
+  BluetoothReplyRunnable* aRunnable);
+{
+}
+
+void
 BluetoothDBusService::ConnectGattClientInternal(
   const nsAString& aAppUuid, const nsAString& aDeviceAddress,
   BluetoothReplyRunnable* aRunnable)
 {
 }
 
 void
 BluetoothDBusService::DisconnectGattClientInternal(
--- a/dom/bluetooth/bluez/BluetoothDBusService.h
+++ b/dom/bluetooth/bluez/BluetoothDBusService.h
@@ -77,19 +77,19 @@ public:
   virtual nsresult GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid,
                                              BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult GetPairedDevicePropertiesInternal(
                                      const nsTArray<nsString>& aDeviceAddresses,
                                      BluetoothReplyRunnable* aRunnable) override;
 #endif
 
-  virtual nsresult StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
+  virtual void StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
 
-  virtual nsresult StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
+  virtual void StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
               BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult
   GetServiceChannel(const nsAString& aDeviceAddress,
@@ -230,16 +230,24 @@ public:
                   const nsAString& aMessage) override;
 
   virtual nsresult
   SendInputMessage(const nsAString& aDeviceAddresses,
                    const nsAString& aMessage) override;
 
 #ifdef MOZ_B2G_BT_API_V2
   virtual void
+  StartLeScanInternal(const nsTArray<nsString>& aServiceUuids,
+                      BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  StopLeScanInternal(const nsAString& aAppUuid,
+                     BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
   ConnectGattClientInternal(const nsAString& aAppUuid,
                             const nsAString& aDeviceAddress,
                             BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   DisconnectGattClientInternal(const nsAString& aAppUuid,
                                const nsAString& aDeviceAddress,
                                BluetoothReplyRunnable* aRunnable) override;
--- a/dom/bluetooth/moz.build
+++ b/dom/bluetooth/moz.build
@@ -29,16 +29,17 @@ if CONFIG['MOZ_B2G_BT']:
             'bluetooth2/BluetoothAdapter.cpp',
             'bluetooth2/BluetoothClassOfDevice.cpp',
             'bluetooth2/BluetoothDevice.cpp',
             'bluetooth2/BluetoothDiscoveryHandle.cpp',
             'bluetooth2/BluetoothGatt.cpp',
             'bluetooth2/BluetoothGattCharacteristic.cpp',
             'bluetooth2/BluetoothGattDescriptor.cpp',
             'bluetooth2/BluetoothGattService.cpp',
+            'bluetooth2/BluetoothLeDeviceEvent.cpp',
             'bluetooth2/BluetoothManager.cpp',
             'bluetooth2/BluetoothPairingHandle.cpp',
             'bluetooth2/BluetoothPairingListener.cpp',
             'bluetooth2/BluetoothProfileController.cpp',
             'bluetooth2/BluetoothReplyRunnable.cpp',
             'bluetooth2/BluetoothService.cpp',
             'bluetooth2/ipc/BluetoothChild.cpp',
             'bluetooth2/ipc/BluetoothParent.cpp',
@@ -167,16 +168,17 @@ if CONFIG['MOZ_B2G_BT_API_V2']:
         'bluetooth2/BluetoothAdapter.h',
         'bluetooth2/BluetoothClassOfDevice.h',
         'bluetooth2/BluetoothDevice.h',
         'bluetooth2/BluetoothDiscoveryHandle.h',
         'bluetooth2/BluetoothGatt.h',
         'bluetooth2/BluetoothGattCharacteristic.h',
         'bluetooth2/BluetoothGattDescriptor.h',
         'bluetooth2/BluetoothGattService.h',
+        'bluetooth2/BluetoothLeDeviceEvent.h',
         'bluetooth2/BluetoothManager.h',
         'bluetooth2/BluetoothPairingHandle.h',
         'bluetooth2/BluetoothPairingListener.h',
     ]
     IPDL_SOURCES += [
         'bluetooth2/ipc/BluetoothTypes.ipdlh',
         'bluetooth2/ipc/PBluetooth.ipdl',
         'bluetooth2/ipc/PBluetoothRequest.ipdl',
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -63,16 +63,20 @@ const kEventConstructors = {
   BluetoothDiscoveryStateChangedEvent:       { create: function (aName, aProps) {
                                                           return new BluetoothDiscoveryStateChangedEvent(aName, aProps);
                                                        },
                                              },
   BluetoothGattCharacteristicEvent:          { create: function (aName, aProps) {
                                                           return new BluetoothGattCharacteristicEvent(aName, aProps);
                                                        },
                                              },
+  BluetoothLeDeviceEvent:                    { create: function (aName, aProps) {
+                                                          return new BluetoothLeDeviceEvent(aName, aProps);
+                                                       },
+                                             },
   BluetoothPairingEvent:                     { create: function (aName, aProps) {
                                                           return new BluetoothPairingEvent(aName, aProps);
                                                        },
                                              },
   BluetoothStatusChangedEvent:               { create: function (aName, aProps) {
                                                           return new BluetoothStatusChangedEvent(aName, aProps);
                                                        },
                                              },
--- a/dom/webidl/BluetoothAdapter2.webidl
+++ b/dom/webidl/BluetoothAdapter2.webidl
@@ -92,16 +92,22 @@ interface BluetoothAdapter : EventTarget
 
   [NewObject]
   Promise<void> pair(DOMString deviceAddress);
   [NewObject]
   Promise<void> unpair(DOMString deviceAddress);
 
   sequence<BluetoothDevice> getPairedDevices();
 
+  [NewObject]
+  Promise<BluetoothDiscoveryHandle> startLeScan(sequence<DOMString> serviceUuids);
+
+  [NewObject]
+  Promise<void> stopLeScan(BluetoothDiscoveryHandle discoveryHandle);
+
   [NewObject, Throws, AvailableIn=CertifiedApps]
   DOMRequest getConnectedDevices(unsigned short serviceUuid);
 
   /**
    * Connect/Disconnect to a specific service of a target remote device.
    * To check the value of service UUIDs, please check "Bluetooth Assigned
    * Numbers" / "Service Discovery Protocol" for more information.
    *
new file mode 100644
--- /dev/null
+++ b/dom/webidl/BluetoothLeDeviceEvent.webidl
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+[CheckPermissions="bluetooth",
+ Constructor(DOMString type, optional BluetoothLeDeviceEventInit eventInitDict)]
+interface BluetoothLeDeviceEvent : Event
+{
+  readonly attribute BluetoothDevice device;
+  readonly attribute short rssi;
+  [Throws]
+  readonly attribute ArrayBuffer scanRecord;
+};
+
+dictionary BluetoothLeDeviceEventInit : EventInit
+{
+  required BluetoothDevice device;
+  short  rssi = 0;
+  required ArrayBuffer scanRecord;
+};
--- a/dom/webidl/Console.webidl
+++ b/dom/webidl/Console.webidl
@@ -11,34 +11,33 @@ interface Console {
   void info(any... data);
   void warn(any... data);
   void error(any... data);
   void _exception(any... data);
   void debug(any... data);
   void table(any... data);
   void trace();
   void dir(any... data);
+  void dirxml(any... data);
   void group(any... data);
   void groupCollapsed(any... data);
   void groupEnd(any... data);
   void time(optional any time);
   void timeEnd(optional any time);
 
   void profile(any... data);
   void profileEnd(any... data);
 
   void assert(boolean condition, any... data);
   void count(any... data);
 
   // No-op methods for compatibility with other browsers.
   [BinaryName="noopMethod"]
   void clear();
   [BinaryName="noopMethod"]
-  void dirxml();
-  [BinaryName="noopMethod"]
   void markTimeline();
   [BinaryName="noopMethod"]
   void timeline();
   [BinaryName="noopMethod"]
   void timelineEnd();
   [BinaryName="noopMethod"]
   void timeStamp();
 };
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -644,16 +644,17 @@ if CONFIG['MOZ_B2G_BT']:
             'BluetoothAdapter2.webidl',
             'BluetoothClassOfDevice.webidl',
             'BluetoothDevice2.webidl',
             'BluetoothDiscoveryHandle.webidl',
             'BluetoothGatt.webidl',
             'BluetoothGattCharacteristic.webidl',
             'BluetoothGattDescriptor.webidl',
             'BluetoothGattService.webidl',
+            'BluetoothLeDeviceEvent.webidl',
             'BluetoothManager2.webidl',
             'BluetoothPairingHandle.webidl',
             'BluetoothPairingListener.webidl',
         ]
     else:
         WEBIDL_FILES += [
             'BluetoothAdapter.webidl',
             'BluetoothDevice.webidl',
--- a/mobile/android/base/ZoomedView.java
+++ b/mobile/android/base/ZoomedView.java
@@ -12,63 +12,113 @@ import org.mozilla.gecko.gfx.PointUtils;
 import org.mozilla.gecko.mozglue.DirectBufferAllocator;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.Matrix;
+import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.PointF;
+import android.graphics.RectF;
+import android.graphics.Shader;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.RelativeLayout;
+import android.widget.TextView;
 
 import java.nio.ByteBuffer;
 import java.text.DecimalFormat;
 
 public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChangedListener,
         LayerView.ZoomedViewListener, GeckoEventListener {
     private static final String LOGTAG = "Gecko" + ZoomedView.class.getSimpleName();
 
-    private static final int DEFAULT_ZOOM_FACTOR = 3;
-    private static final int W_CAPTURED_VIEW_IN_PERCENT = 80;
+    private static final float[] ZOOM_FACTORS_LIST = {2.0f, 3.0f, 1.5f};
+    private static final int W_CAPTURED_VIEW_IN_PERCENT = 50;
     private static final int H_CAPTURED_VIEW_IN_PERCENT = 50;
     private static final int MINIMUM_DELAY_BETWEEN_TWO_RENDER_CALLS_NS = 1000000;
     private static final int DELAY_BEFORE_NEXT_RENDER_REQUEST_MS = 2000;
 
-    private int zoomFactor;
+    private float zoomFactor;
+    private int currentZoomFactorIndex;
     private ImageView zoomedImageView;
     private LayerView layerView;
     private int viewWidth;
-    private int viewHeight;
+    private int viewHeight; // Only the zoomed view height, no toolbar, no shadow ...
+    private int viewContainerWidth;
+    private int viewContainerHeight; // Zoomed view height with toolbar and other elements like shadow, ...
+    private int containterSize; // shadow, margin, ...
     private Point lastPosition;
     private boolean shouldSetVisibleOnUpdate;
     private PointF returnValue;
+    private ImageView closeButton;
+    private TextView changeZoomFactorButton;
+    private boolean toolbarOnTop;
+    private float offsetDueToToolBarPosition;
+    private int toolbarHeight;
+    private int cornerRadius;
 
     private boolean stopUpdateView;
 
     private int lastOrientation;
 
     private ByteBuffer buffer;
     private Runnable requestRenderRunnable;
     private long startTimeReRender;
     private long lastStartTimeReRender;
 
+    private class RoundedBitmapDrawable extends BitmapDrawable {
+        private Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
+        final float cornerRadius;
+        final boolean squareOnTopOfDrawable;
+
+        RoundedBitmapDrawable(Resources res, Bitmap bitmap, boolean squareOnTop, int radius) {
+            super(res, bitmap);
+            squareOnTopOfDrawable = squareOnTop;
+            final BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,
+                Shader.TileMode.CLAMP);
+            paint.setAntiAlias(true);
+            paint.setShader(shader);
+            cornerRadius = radius;
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            int height = getBounds().height();
+            int width = getBounds().width();
+            RectF rect = new RectF(0.0f, 0.0f, width, height);
+            canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint);
+
+            //draw rectangles over the corners we want to be square
+            if (squareOnTopOfDrawable) {
+                canvas.drawRect(0, 0, cornerRadius, cornerRadius, paint);
+                canvas.drawRect(width - cornerRadius, 0, width, cornerRadius, paint);
+            } else {
+                canvas.drawRect(0, height - cornerRadius, cornerRadius, height, paint);
+                canvas.drawRect(width - cornerRadius, height - cornerRadius, width, height, paint);
+            }
+        }
+    }
+
     private class ZoomedViewTouchListener implements View.OnTouchListener {
         private float originRawX;
         private float originRawY;
         private boolean dragged;
         private MotionEvent actionDownEvent;
 
         @Override
         public boolean onTouch(View view, MotionEvent event) {
@@ -82,42 +132,49 @@ public class ZoomedView extends FrameLay
                     dragged = true;
                 }
                 break;
 
             case MotionEvent.ACTION_UP:
                 if (dragged) {
                     dragged = false;
                 } else {
-                    GeckoEvent eClickInZoomedView = GeckoEvent.createBroadcastEvent("Gesture:ClickInZoomedView", "");
-                    GeckoAppShell.sendEventToGecko(eClickInZoomedView);
-                    layerView.dispatchTouchEvent(actionDownEvent);
-                    actionDownEvent.recycle();
-                    PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(event.getX(), event.getY());
-                    MotionEvent e = MotionEvent.obtain(event.getDownTime(), event.getEventTime(),
-                            MotionEvent.ACTION_UP, convertedPosition.x, convertedPosition.y,
-                            event.getMetaState());
-                    layerView.dispatchTouchEvent(e);
-                    e.recycle();
+                    if (isClickInZoomedView(event.getY())) {
+                        GeckoEvent eClickInZoomedView = GeckoEvent.createBroadcastEvent("Gesture:ClickInZoomedView", "");
+                        GeckoAppShell.sendEventToGecko(eClickInZoomedView);
+                        layerView.dispatchTouchEvent(actionDownEvent);
+                        actionDownEvent.recycle();
+                        PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(event.getX(), event.getY());
+                        MotionEvent e = MotionEvent.obtain(event.getDownTime(), event.getEventTime(),
+                                MotionEvent.ACTION_UP, convertedPosition.x, convertedPosition.y,
+                                event.getMetaState());
+                        layerView.dispatchTouchEvent(e);
+                        e.recycle();
+                    }
                 }
                 break;
 
             case MotionEvent.ACTION_DOWN:
                 dragged = false;
                 originRawX = event.getRawX();
                 originRawY = event.getRawY();
                 PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(event.getX(), event.getY());
                 actionDownEvent = MotionEvent.obtain(event.getDownTime(), event.getEventTime(),
                         MotionEvent.ACTION_DOWN, convertedPosition.x, convertedPosition.y,
                         event.getMetaState());
                 break;
             }
             return true;
         }
 
+        private boolean isClickInZoomedView(float y) {
+            return ((toolbarOnTop && y > toolbarHeight) ||
+                (!toolbarOnTop && y < ZoomedView.this.viewHeight));
+        }
+
         private boolean moveZoomedView(MotionEvent event) {
             RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) ZoomedView.this.getLayoutParams();
             if ((!dragged) && (Math.abs((int) (event.getRawX() - originRawX)) < PanZoomController.CLICK_THRESHOLD)
                     && (Math.abs((int) (event.getRawY() - originRawY)) < PanZoomController.CLICK_THRESHOLD)) {
                 // When the user just touches the screen ACTION_MOVE can be detected for a very small delta on position.
                 // In this case, the move is ignored if the delta is lower than 1 unit.
                 return false;
             }
@@ -138,54 +195,74 @@ public class ZoomedView extends FrameLay
 
     public ZoomedView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
     public ZoomedView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         returnValue = new PointF();
+        currentZoomFactorIndex = 0;
+        zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
         requestRenderRunnable = new Runnable() {
             @Override
             public void run() {
                 requestZoomedViewRender();
             }
         };
-        EventDispatcher.getInstance().registerGeckoThreadListener(this, "Gesture:nothingDoneOnLongPress",
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
                 "Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange");
     }
 
     void destroy() {
         ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Gesture:nothingDoneOnLongPress",
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
                 "Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange");
     }
 
     // This method (onFinishInflate) is called only when the zoomed view class is used inside
     // an xml structure <org.mozilla.gecko.ZoomedView ...
     // It won't be called if the class is used from java code like "new  ZoomedView(context);"
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        ImageView closeButton = (ImageView) findViewById(R.id.dialog_close);
+        closeButton = (ImageView) findViewById(R.id.dialog_close);
         closeButton.setOnClickListener(new View.OnClickListener() {
             public void onClick(View view) {
                 stopZoomDisplay();
             }
         });
 
+        changeZoomFactorButton = (TextView) findViewById(R.id.change_zoom_factor);
+        changeZoomFactorButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View view) {
+                changeZoomFactor();
+            }
+        });
+        setTextInZoomFactorButton(ZOOM_FACTORS_LIST[0]);
+
         zoomedImageView = (ImageView) findViewById(R.id.zoomed_image_view);
-        zoomedImageView.setOnTouchListener(new ZoomedViewTouchListener());
+        this.setOnTouchListener(new ZoomedViewTouchListener());
+
+        toolbarHeight = getResources().getDimensionPixelSize(R.dimen.zoomed_view_toolbar_height);
+        containterSize = getResources().getDimensionPixelSize(R.dimen.drawable_dropshadow_size);
+        cornerRadius = getResources().getDimensionPixelSize(R.dimen.button_corner_radius);
+
+        moveToolbar(true);
     }
 
     /*
      * Convert a click from ZoomedView. Return the position of the click in the
      * LayerView
      */
     private PointF getUnzoomedPositionFromPointInZoomedView(float x, float y) {
+        if (toolbarOnTop && y > toolbarHeight) {
+           y = y - toolbarHeight;
+        }
+
         ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
         PointF offset = metrics.getMarginOffset();
         final float parentWidth = metrics.getWidth();
         final float parentHeight = metrics.getHeight();
         RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
 
         returnValue.x = (int) ((x / zoomFactor) +     // Conversion of the x offset inside the zoomed view (using the scale factor)
 
@@ -194,24 +271,25 @@ public class ZoomedView extends FrameLay
                         /* Conversion of the left side position of the zoomed view
                          *   Minimum value for the left side of the zoomed view is 0
                          *     and we return 0 after conversion
                          *   Maximum value for the left side of the zoomed view is (parentWidth - offset.x - viewWidth)
                          *     and we return (parentWidth - offset.x - (viewWidth / zoomFactor)) after conversion.
                          */
                         (((float) params.leftMargin) - offset.x) *
                             ((parentWidth - offset.x - (viewWidth / zoomFactor)) /
-                            (parentWidth - offset.x - viewWidth)));
+                            (parentWidth - offset.x - viewContainerWidth)));
 
         // Same comments here vertically
         returnValue.y = (int) ((y / zoomFactor) +
-                        offset.y +
+                        offset.y -
+                        offsetDueToToolBarPosition +
                         (((float) params.topMargin) - offset.y) *
-                            ((parentHeight - offset.y - (viewHeight / zoomFactor)) /
-                            (parentHeight - offset.y - viewHeight)));
+                            ((parentHeight - offset.y + offsetDueToToolBarPosition - (viewHeight / zoomFactor)) /
+                            (parentHeight - offset.y - viewContainerHeight)));
 
         return returnValue;
     }
 
     /*
      * A touch point (x,y) occurs in LayerView, this point should be displayed
      * in the center of the zoomed view. The returned point is the position of
      * the Top-Left zoomed view point on the screen device
@@ -227,24 +305,24 @@ public class ZoomedView extends FrameLay
 
                         /* Conversion of the left side position of the zoomed view.
                          * See the comment in getUnzoomedPositionFromPointInZoomedView.
                          * The proportional factor is the same. It is used in a division
                          * and not in a multiplication to convert the position from
                          * the LayerView to the ZoomedView.
                          */
                         ((parentWidth - offset.x - (viewWidth / zoomFactor)) /
-                        (parentWidth - offset.x - viewWidth)))
+                        (parentWidth - offset.x - viewContainerWidth)))
 
                 + offset.x);     // The offset of the layerView
 
         // Same comments here vertically
-        returnValue.y = (int) ((((y - (viewHeight / (2 * zoomFactor)))) /
-                        ((parentHeight - offset.y - (viewHeight / zoomFactor)) /
-                        (parentHeight - offset.y - viewHeight)))
+        returnValue.y = (int) ((((y + offsetDueToToolBarPosition - (viewHeight / (2 * zoomFactor)))) /
+                        ((parentHeight - offset.y + offsetDueToToolBarPosition - (viewHeight / zoomFactor)) /
+                        (parentHeight - offset.y - viewContainerHeight)))
                 + offset.y);
 
         return returnValue;
     }
 
     private void moveZoomedView(ImmutableViewportMetrics metrics, float newLeftMargin, float newTopMargin) {
         final float parentWidth = metrics.getWidth();
         final float parentHeight = metrics.getHeight();
@@ -254,32 +332,69 @@ public class ZoomedView extends FrameLay
         int topMarginMin;
         int leftMarginMin;
         PointF offset = metrics.getMarginOffset();
         topMarginMin = (int) offset.y;
         leftMarginMin = (int) offset.x;
 
         if (newTopMargin < topMarginMin) {
             newLayoutParams.topMargin = topMarginMin;
-        } else if (newTopMargin + viewHeight > parentHeight) {
-            newLayoutParams.topMargin = (int) (parentHeight - viewHeight);
+        } else if (newTopMargin + viewContainerHeight > parentHeight) {
+            newLayoutParams.topMargin = (int) (parentHeight - viewContainerHeight);
         }
 
         if (newLeftMargin < leftMarginMin) {
             newLayoutParams.leftMargin = leftMarginMin;
-        } else if (newLeftMargin + viewWidth > parentWidth) {
-            newLayoutParams.leftMargin = (int) (parentWidth - viewWidth);
+        } else if (newLeftMargin + viewContainerWidth > parentWidth) {
+            newLayoutParams.leftMargin = (int) (parentWidth - viewContainerWidth);
+        }
+
+        if (newLayoutParams.topMargin < topMarginMin + 1) {
+            moveToolbar(false);
+        } else if (newLayoutParams.topMargin + viewContainerHeight > parentHeight - 1) {
+            moveToolbar(true);
         }
 
         setLayoutParams(newLayoutParams);
         PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(0, 0);
         lastPosition = PointUtils.round(convertedPosition);
         requestZoomedViewRender();
     }
 
+    private void moveToolbar(boolean moveTop) {
+        if (toolbarOnTop == moveTop) {
+            return;
+        }
+        toolbarOnTop = moveTop;
+        if (toolbarOnTop) {
+            offsetDueToToolBarPosition = toolbarHeight;
+        } else {
+            offsetDueToToolBarPosition = 0;
+        }
+
+        RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) zoomedImageView.getLayoutParams();
+        RelativeLayout.LayoutParams pChangeZoomFactorButton = (RelativeLayout.LayoutParams) changeZoomFactorButton.getLayoutParams();
+        RelativeLayout.LayoutParams pCloseButton = (RelativeLayout.LayoutParams) closeButton.getLayoutParams();
+
+        if (moveTop) {
+            p.addRule(RelativeLayout.BELOW, R.id.change_zoom_factor);
+            pChangeZoomFactorButton.addRule(RelativeLayout.BELOW, 0);
+            pCloseButton.addRule(RelativeLayout.BELOW, 0);
+        } else {
+            p.addRule(RelativeLayout.BELOW, 0);
+            pChangeZoomFactorButton.addRule(RelativeLayout.BELOW, R.id.zoomed_image_view);
+            pCloseButton.addRule(RelativeLayout.BELOW, R.id.zoomed_image_view);
+        }
+        pChangeZoomFactorButton.addRule(RelativeLayout.ALIGN_LEFT, R.id.zoomed_image_view);
+        pCloseButton.addRule(RelativeLayout.ALIGN_RIGHT, R.id.zoomed_image_view);
+        zoomedImageView.setLayoutParams(p);
+        changeZoomFactorButton.setLayoutParams(pChangeZoomFactorButton);
+        closeButton.setLayoutParams(pCloseButton);
+    }
+
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         // In case of orientation change, the zoomed view update is stopped until the orientation change
         // is completed. At this time, the function onMetricsChanged is called and the
         // zoomed view update is restarted again.
         if (lastOrientation != newConfig.orientation) {
             shouldBlockUpdate(true);
@@ -294,23 +409,22 @@ public class ZoomedView extends FrameLay
 
         RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
         setCapturedSize(viewport);
         moveZoomedView(viewport, params.leftMargin, params.topMargin);
     }
 
     private void setCapturedSize(ImmutableViewportMetrics metrics) {
         float parentMinSize = Math.min(metrics.getWidth(), metrics.getHeight());
-        // For metrics.zoomFactor lower than 1, the zoom factor of the zoomed view is calculated
-        // to get always the same size for the content in the zoomed view.
-        // For metrics.zoomFactor greater than 1, the zoom factor is always set to the default
-        // value DEFAULT_ZOOM_FACTOR, thus the zoomed view is always a zoom of the normal view.
-        zoomFactor = Math.max(DEFAULT_ZOOM_FACTOR, (int) (DEFAULT_ZOOM_FACTOR / metrics.zoomFactor));
-        viewWidth = (int) (parentMinSize * W_CAPTURED_VIEW_IN_PERCENT / (zoomFactor * 100.0)) * zoomFactor;
-        viewHeight = (int) (parentMinSize * H_CAPTURED_VIEW_IN_PERCENT / (zoomFactor * 100.0)) * zoomFactor;
+        viewWidth = (int) ((parentMinSize * W_CAPTURED_VIEW_IN_PERCENT / (zoomFactor * 100.0)) * zoomFactor);
+        viewHeight = (int) ((parentMinSize * H_CAPTURED_VIEW_IN_PERCENT / (zoomFactor * 100.0)) * zoomFactor);
+        viewContainerHeight = viewHeight + toolbarHeight +
+                2 * containterSize; // Top and bottom shadows
+        viewContainerWidth = viewWidth +
+                2 * containterSize; // Right and left shadows
         // Display in zoomedview is corrupted when width is an odd number
         // More details about this issue here: bug 776906 comment 11
         viewWidth &= ~0x1;
     }
 
     private void shouldBlockUpdate(boolean shouldBlockUpdate) {
         stopUpdateView = shouldBlockUpdate;
     }
@@ -338,23 +452,41 @@ public class ZoomedView extends FrameLay
         ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
         if (layerView != null) {
             layerView.setOnMetricsChangedZoomedViewportListener(null);
             layerView.removeZoomedViewListener(this);
             layerView = null;
         }
     }
 
+    private void changeZoomFactor() {
+        if (currentZoomFactorIndex < ZOOM_FACTORS_LIST.length - 1) {
+            currentZoomFactorIndex++;
+        } else {
+            currentZoomFactorIndex = 0;
+        }
+        zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
+
+        ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
+        refreshZoomedViewSize(metrics);
+        setTextInZoomFactorButton(zoomFactor);
+    }
+
+    private void setTextInZoomFactorButton(float zoom) {
+        final String percentageValue = Integer.toString((int) (100*zoom));
+        changeZoomFactorButton.setText(getResources().getString(R.string.percent, percentageValue));
+    }
+
     @Override
     public void handleMessage(final String event, final JSONObject message) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 try {
-                    if (event.equals("Gesture:nothingDoneOnLongPress") || event.equals("Gesture:clusteredLinksClicked")) {
+                    if (event.equals("Gesture:clusteredLinksClicked")) {
                         final JSONObject clickPosition = message.getJSONObject("clickPosition");
                         int left = clickPosition.getInt("x");
                         int top = clickPosition.getInt("y");
                         // Start to display inside the zoomedView
                         LayerView geckoAppLayerView = GeckoAppShell.getLayerView();
                         if (geckoAppLayerView != null) {
                             startZoomDisplay(geckoAppLayerView, left, top);
                         }
@@ -368,16 +500,20 @@ public class ZoomedView extends FrameLay
                     Log.e(LOGTAG, "JSON exception", e);
                 }
             }
         });
     }
 
     private void moveUsingGeckoPosition(int leftFromGecko, int topFromGecko) {
         ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
+        final float parentHeight = metrics.getHeight();
+        // moveToolbar is called before getZoomedViewTopLeftPositionFromTouchPosition in order to
+        // correctly center vertically the zoomed area
+        moveToolbar((topFromGecko * metrics.zoomFactor > parentHeight / 2));
         PointF convertedPosition = getZoomedViewTopLeftPositionFromTouchPosition((leftFromGecko * metrics.zoomFactor),
                 (topFromGecko * metrics.zoomFactor));
         moveZoomedView(metrics, convertedPosition.x, convertedPosition.y);
     }
 
     @Override
     public void onMetricsChanged(final ImmutableViewportMetrics viewport) {
         // It can be called from a Gecko thread (forceViewportMetrics in GeckoLayerClient).
@@ -401,18 +537,18 @@ public class ZoomedView extends FrameLay
         final Bitmap sb3 = Bitmap.createBitmap(viewWidth, viewHeight, getBitmapConfig());
         if (sb3 != null) {
             data.rewind();
             try {
                 sb3.copyPixelsFromBuffer(data);
             } catch (Exception iae) {
                 Log.w(LOGTAG, iae.toString());
             }
-            BitmapDrawable ob3 = new BitmapDrawable(getResources(), sb3);
             if (zoomedImageView != null) {
+                RoundedBitmapDrawable ob3 = new RoundedBitmapDrawable(getResources(), sb3, toolbarOnTop, cornerRadius);
                 zoomedImageView.setImageDrawable(ob3);
             }
         }
         if (shouldSetVisibleOnUpdate) {
             this.setVisibility(View.VISIBLE);
             shouldSetVisibleOnUpdate = false;
         }
         lastStartTimeReRender = startTimeReRender;
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -609,16 +609,24 @@ just addresses the organization to follo
 <!-- Miscellaneous -->
 <!-- LOCALIZATION NOTE (ellipsis): This text is appended to a piece of text that does not fit in the
      designated space. Use the unicode ellipsis char, \u2026, or use "..." if \u2026 doesn't suit
      traditions in your locale. -->
 <!ENTITY ellipsis "…">
 
 <!ENTITY colon ":">
 
+<!-- LOCALIZATION NOTE (percent): The percent sign is appended after a number to
+     display a percentage value. formatS is the number, #37 is the code to display a percent sign.
+     This format string is typically used by getString method, in such method the percent sign
+     is a reserved caracter. In order to display one percent sign in the result of getString,
+     double percent signs must be inserted in the format string.
+     This entity is used in the zoomed view to display the zoom factor-->
+<!ENTITY percent "&formatS;&#37;&#37;">
+
 <!-- These are only used for accessibility for the done and overflow-menu buttons in the actionbar.
      They are never shown to users -->
 <!ENTITY actionbar_menu "Menu">
 <!ENTITY actionbar_done "Done">
 
 <!-- Voice search in the awesome bar -->
 <!ENTITY voicesearch_prompt "Speak now">
 <!ENTITY voicesearch_failed_title "&brandShortName; Voice Search">
--- a/mobile/android/base/resources/layout/zoomed_view.xml
+++ b/mobile/android/base/resources/layout/zoomed_view.xml
@@ -5,29 +5,42 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/.
 -->
 
 <org.mozilla.gecko.ZoomedView xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:gecko="http://schemas.android.com/apk/res-auto"
     android:id="@+id/zoomed_view_container"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:layout_alignParentLeft="true"
-    android:layout_alignParentTop="true"
-    android:background="@android:color/white"
-    android:visibility="gone" >
+    android:background="@drawable/dropshadow"
+    android:padding="@dimen/drawable_dropshadow_size"
+    android:visibility="gone">
 
-
-    <ImageView
-        android:id="@+id/zoomed_image_view"
+    <RelativeLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:background="#000000"
-        android:padding="1dip" />
-
-    <ImageView
-        android:id="@+id/dialog_close"
-        android:background="@drawable/close"
-        android:layout_height="20dp"
-        android:layout_width="20dp"
-        android:layout_gravity ="top|right" />
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:background="@drawable/toolbar_grey_round">
+        <TextView
+            android:id="@+id/change_zoom_factor"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/zoomed_view_toolbar_height"
+            android:background="@android:color/transparent"
+            android:padding="12dip"
+            android:layout_alignLeft="@+id/zoomed_image_view"
+            android:textSize="16sp"
+            android:textColor="@color/text_and_tabs_tray_grey"/>
+        <ImageView
+            android:id="@+id/dialog_close"
+            android:scaleType="center"
+            android:layout_width="@dimen/zoomed_view_toolbar_height"
+            android:layout_height="@dimen/zoomed_view_toolbar_height"
+            android:layout_alignRight="@id/zoomed_image_view"
+            android:src="@drawable/close_edit_mode_selector"/>
+        <ImageView
+            android:id="@id/zoomed_image_view"
+            android:layout_below="@id/change_zoom_factor"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+    </RelativeLayout>
 
 </org.mozilla.gecko.ZoomedView>
\ No newline at end of file
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -200,16 +200,20 @@
     <dimen name="tab_history_title_fading_width">50dp</dimen>
     <dimen name="tab_history_title_margin_right">15dp</dimen>
     <dimen name="tab_history_title_text_size">14sp</dimen>
     <dimen name="tab_history_bg_width">2dp</dimen>
     <dimen name="tab_history_border_padding">2dp</dimen>
 
     <dimen name="horizontal_drag_area">256dp</dimen>
 
+    <!-- ZoomedView dimensions. -->
+    <dimen name="zoomed_view_toolbar_height">44dp</dimen>
+    <dimen name="drawable_dropshadow_size">3dp</dimen>
+
     <!-- Find-In-Page dialog dimensions. -->
     <dimen name="find_in_page_text_margin_left">5dip</dimen>
     <dimen name="find_in_page_text_margin_right">12dip</dimen>
     <dimen name="find_in_page_text_padding_left">10dip</dimen>
     <dimen name="find_in_page_text_padding_right">10dip</dimen>
     <dimen name="find_in_page_status_margin_right">10dip</dimen>
     <dimen name="find_in_page_matchcase_padding">10dip</dimen>
     <dimen name="find_in_page_control_margin_top">2dip</dimen>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -523,10 +523,12 @@
   <string name="voicesearch_failed_message_recoverable">&voicesearch_failed_message_recoverable;</string>
   <string name="voicesearch_failed_retry">&voicesearch_failed_retry;</string>
 
   <!-- Miscellaneous -->
   <string name="ellipsis">&ellipsis;</string>
 
   <string name="colon">&colon;</string>
 
+  <string name="percent">&percent;</string>
+
   <string name="remote_tabs_last_synced">&remote_tabs_last_synced;</string>
 </resources>
--- a/toolkit/devtools/server/actors/root.js
+++ b/toolkit/devtools/server/actors/root.js
@@ -152,16 +152,19 @@ RootActor.prototype = {
     // that returns the font faces used on a node
     getUsedFontFaces: true,
     // Trait added in Gecko 38, indicating that all features necessary for
     // grabbing allocations from the MemoryActor are available for the performance tool
     memoryActorAllocations: true,
     // Added in Gecko 40, indicating that the backend isn't stupid about
     // sending resumption packets on tab navigation.
     noNeedToFakeResumptionOnNavigation: true,
+    // Added in Firefox 40. Indicates that the backend supports registering custom
+    // commands through the WebConsoleCommands API.
+    webConsoleCommands: true,
     // Whether root actor exposes tab actors
     // if allowChromeProcess is true, you can fetch a ChromeActor instance
     // to debug chrome and any non-content ressource via getProcess request
     // if allocChromeProcess is defined, but not true, it means that root actor
     // no longer expose tab actors, but also that getProcess forbids
     // exposing actors for security reasons
     get allowChromeProcess() {
       return DebuggerServer.allowChromeProcess;
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -627,28 +627,113 @@ BrowserTabList.prototype.onCloseWindow =
       }
     }
   }, "BrowserTabList.prototype.onCloseWindow's delayed body"), 0);
 }, "BrowserTabList.prototype.onCloseWindow");
 
 exports.BrowserTabList = BrowserTabList;
 
 /**
- * Creates a tab actor for handling requests to a browser tab, like
- * attaching and detaching. TabActor respects the actor factories
- * registered with DebuggerServer.addTabActor.
+ * Creates a TabActor whose main goal is to manage lifetime and
+ * expose the tab actors being registered via DebuggerServer.registerModule.
+ * But also track the lifetime of the document being tracked.
+ *
+ * ### Main requests:
+ *
+ * `attach`/`detach` requests:
+ *  - start/stop document watching:
+ *    Starts watching for new documents and emits `tabNavigated` and
+ *    `frameUpdate` over RDP.
+ *  - retrieve the thread actor:
+ *    Instantiates a ThreadActor that can be later attached to in order to
+ *    debug JS sources in the document.
+ * `switchToFrame`:
+ *  Change the targeted document of the whole TabActor, and its child tab actors
+ *  to an iframe or back to its original document.
+ *
+ * Most of the TabActor properties (like `chromeEventHandler` or `docShells`)
+ * are meant to be used by the various child tab actors.
+ *
+ * ### RDP events:
+ *
+ *  - `tabNavigated`:
+ *    Sent when the tab is about to navigate or has just navigated to
+ *    a different document.
+ *    This event contains the following attributes:
+ *     * url (string) The new URI being loaded.
+ *     * nativeConsoleAPI (boolean) `false` if the console API of the page has been
+ *                                          overridden (e.g. by Firebug),
+ *                                  `true`  if the Gecko implementation is used.
+ *     * state (string) `start` if we just start requesting the new URL,
+ *                      `stop`  if the new URL is done loading.
+ *     * isFrameSwitching (boolean) Indicates the event is dispatched when
+ *                                  switching the TabActor context to
+ *                                  a different frame. When we switch to
+ *                                  an iframe, there is no document load.
+ *                                  The targeted document is most likely
+ *                                  going to be already done loading.
+ *     * title (string) The document title being loaded.
+ *                      (sent only on state=stop)
+ *
+ *  - `frameUpdate`:
+ *    Sent when there was a change in the child frames contained in the document
+ *    or when the tab's context was switched to another frame.
+ *    This event can have four different forms depending on the type of incident:
+ *    * One or many frames are updated:
+ *      { frames: [{ id, url, title, parentID }, ...] }
+ *    * One frame got destroyed:
+ *      { frames: [{ id, destroy: true }]}
+ *    * All frames got destroyed:
+ *      { destroyAll: true }
+ *    * We switched the context of the TabActor to a specific frame:
+ *      { selected: #id }
+ *
+ * ### Internal, non-rdp events:
+ * Various events are also dispatched on the TabActor itself that are not
+ * related to RDP, so, not sent to the client. They all relate to the documents
+ * tracked by the TabActor (its main targeted document, but also any of its iframes).
+ *  - will-navigate
+ *    This event fires once navigation starts.
+ *    All pending user prompts are dealt with,
+ *    but it is fired before the first request starts.
+ *  - navigate
+ *    This event is fired once the document's readyState is "complete".
+ *  - window-ready
+ *    This event is fired on three distinct scenarios:
+ *     * When a new Window object is crafted, equivalent of `DOMWindowCreated`.
+ *       It is dispatched before any page script is executed.
+ *     * We will have already received a window-ready event for this window
+ *       when it was created, but we received a window-destroyed event when
+ *       it was frozen into the bfcache, and now the user navigated back to
+ *       this page, so it's now live again and we should resume handling it.
+ *     * For each existing document, when an `attach` request is received.
+ *       At this point scripts in the page will be already loaded.
+ *  - window-destroyed
+ *    This event is fired in two cases:
+ *     * When the window object is destroyed, i.e. when the related document
+ *       is garbage collected. This can happen when the tab is closed or the
+ *       iframe is removed from the DOM.
+ *       It is equivalent of `inner-window-destroyed` event.
+ *     * When the page goes into the bfcache and gets frozen.
+ *       The equivalent of `pagehide`.
+ *  - changed-toplevel-document
+ *    This event fires when we switch the TabActor targeted document
+ *    to one of its iframes, or back to its original top document.
+ *    It is dispatched between window-destroyed and window-ready.
+ *
+ * Note that *all* these events are dispatched in the following order
+ * when we switch the context of the TabActor to a given iframe:
+ *   will-navigate, window-destroyed, changed-toplevel-document, window-ready, navigate
  *
  * This class is subclassed by BrowserTabActor and
  * ContentActor. Subclasses are expected to implement a getter
- * the docShell properties.
+ * for the docShell property.
  *
  * @param aConnection DebuggerServerConnection
  *        The conection to the client.
- * @param aChromeEventHandler
- *        An object on which listen for DOMWindowCreated and pageshow events.
  */
 function TabActor(aConnection)
 {
   this.conn = aConnection;
   this._tabActorPool = null;
   // A map of actor names to actor instances provided by extensions.
   this._extraActors = {};
   this._exited = false;
--- a/toolkit/devtools/webconsole/test/test_commands_registration.html
+++ b/toolkit/devtools/webconsole/test/test_commands_registration.html
@@ -20,16 +20,21 @@ let tests;
 
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let {WebConsoleCommands} = devtools.require("devtools/toolkit/webconsole/utils");
 
 function evaluateJS(input) {
   return new Promise((resolve) => gState.client.evaluateJS(input, resolve));
 }
 
+function* evaluateJSAndCheckResult(input, result) {
+  let response = yield evaluateJS(input);
+  checkObject(response, {result});
+}
+
 function startTest()
 {
   removeEventListener("load", startTest);
 
   attachConsole(["PageError"], onAttach, true);
 }
 
 function onAttach(aState, aResponse)
@@ -133,16 +138,44 @@ tests = [
       input: command,
       result: ">o_/"
     });
     is(document.getElementById("quack").textContent, ">o_/",
         "#foo textContent should equal to \">o_/\"");
     WebConsoleCommands.unregister("$foo");
     ok(!WebConsoleCommands.hasCommand("$foo"), "$foo should be unregistered");
     nextTest();
+  }),
+
+  Task.async(function* unregisterAfterOverridingTwice() {
+    WebConsoleCommands.register("keys", (owner, obj) => "command 1");
+    info("checking the value of the first override");
+    yield evaluateJSAndCheckResult("keys('foo');", "command 1");
+
+    let orig = WebConsoleCommands.getCommand("keys");
+    WebConsoleCommands.register("keys", (owner, obj) => {
+      if (obj === "quack")
+        return "bang!";
+      return orig(owner, obj);
+    });
+
+    info("checking the values after the second override");
+    yield evaluateJSAndCheckResult("keys({});", "command 1");
+    yield evaluateJSAndCheckResult("keys('quack');", "bang!");
+
+    WebConsoleCommands.unregister("keys");
+
+    info("checking the value after unregistration (should restore " +
+      "the original command)");
+    yield evaluateJSAndCheckResult("keys({});", {
+      class: "Array",
+      preview: {items: []}
+    });
+    nextTest();
+
   })
 ];
 
 function testEnd()
 {
   // If this is the first run, reload the page and do it again.
   // Otherwise, end the test.
   delete top.foo;
--- a/toolkit/devtools/webconsole/utils.js
+++ b/toolkit/devtools/webconsole/utils.js
@@ -1524,16 +1524,29 @@ ConsoleAPIListener.prototype =
  * WebConsole commands manager.
  *
  * Defines a set of functions /variables ("commands") that are available from
  * the Web Console but not from the web page.
  *
  */
 let WebConsoleCommands = {
   _registeredCommands: new Map(),
+  _originalCommands: new Map(),
+
+  /**
+   * @private
+   * Reserved for built-in commands. To register a command from the code of an
+   * add-on, see WebConsoleCommands.register instead.
+   *
+   * @see WebConsoleCommands.register
+   */
+  _registerOriginal: function (name, command) {
+    this.register(name, command);
+    this._originalCommands.set(name, this.getCommand(name));
+  },
 
   /**
    * Register a new command.
    * @param {string} name The command name (exemple: "$")
    * @param {(function|object)} command The command to register.
    *  It can be a function so the command is a function (like "$()"),
    *  or it can also be a property descriptor to describe a getter / value (like
    *  "$0").
@@ -1559,20 +1572,26 @@ let WebConsoleCommands = {
    */
   register: function(name, command) {
     this._registeredCommands.set(name, command);
   },
 
   /**
    * Unregister a command.
    *
+   * If the command being unregister overrode a built-in command,
+   * the latter is restored.
+   *
    * @param {string} name The name of the command
    */
   unregister: function(name) {
     this._registeredCommands.delete(name);
+    if (this._originalCommands.has(name)) {
+      this.register(name, this._originalCommands.get(name));
+    }
   },
 
   /**
    * Returns a command by its name.
    *
    * @param {string} name The name of the command.
    *
    * @return {(function|object)} The command.
@@ -1606,57 +1625,57 @@ exports.WebConsoleCommands = WebConsoleC
 /**
  * Find a node by ID.
  *
  * @param string aId
  *        The ID of the element you want.
  * @return nsIDOMNode or null
  *         The result of calling document.querySelector(aSelector).
  */
-WebConsoleCommands.register("$", function JSTH_$(aOwner, aSelector)
+WebConsoleCommands._registerOriginal("$", function JSTH_$(aOwner, aSelector)
 {
   return aOwner.window.document.querySelector(aSelector);
 });
 
 /**
  * Find the nodes matching a CSS selector.
  *
  * @param string aSelector
  *        A string that is passed to window.document.querySelectorAll.
  * @return nsIDOMNodeList
  *         Returns the result of document.querySelectorAll(aSelector).
  */
-WebConsoleCommands.register("$$", function JSTH_$$(aOwner, aSelector)
+WebConsoleCommands._registerOriginal("$$", function JSTH_$$(aOwner, aSelector)
 {
   return aOwner.window.document.querySelectorAll(aSelector);
 });
 
 /**
  * Returns the result of the last console input evaluation
  *
  * @return object|undefined
  * Returns last console evaluation or undefined
  */
-WebConsoleCommands.register("$_", {
+WebConsoleCommands._registerOriginal("$_", {
   get: function(aOwner) {
     return aOwner.consoleActor.getLastConsoleInputEvaluation();
   }
 });
 
 
 /**
  * Runs an xPath query and returns all matched nodes.
  *
  * @param string aXPath
  *        xPath search query to execute.
  * @param [optional] nsIDOMNode aContext
  *        Context to run the xPath query on. Uses window.document if not set.
  * @return array of nsIDOMNode
  */
-WebConsoleCommands.register("$x", function JSTH_$x(aOwner, aXPath, aContext)
+WebConsoleCommands._registerOriginal("$x", function JSTH_$x(aOwner, aXPath, aContext)
 {
   let nodes = new aOwner.window.wrappedJSObject.Array();
   let doc = aOwner.window.document;
   aContext = aContext || doc;
 
   let results = doc.evaluate(aXPath, aContext, null,
                              Ci.nsIDOMXPathResult.ANY_TYPE, null);
   let node;
@@ -1668,93 +1687,93 @@ WebConsoleCommands.register("$x", functi
 });
 
 /**
  * Returns the currently selected object in the highlighter.
  *
  * @return Object representing the current selection in the
  *         Inspector, or null if no selection exists.
  */
-WebConsoleCommands.register("$0", {
+WebConsoleCommands._registerOriginal("$0", {
   get: function(aOwner) {
     return aOwner.makeDebuggeeValue(aOwner.selectedNode);
   }
 });
 
 /**
  * Clears the output of the WebConsole.
  */
-WebConsoleCommands.register("clear", function JSTH_clear(aOwner)
+WebConsoleCommands._registerOriginal("clear", function JSTH_clear(aOwner)
 {
   aOwner.helperResult = {
     type: "clearOutput",
   };
 });
 
 /**
  * Clears the input history of the WebConsole.
  */
-WebConsoleCommands.register("clearHistory", function JSTH_clearHistory(aOwner)
+WebConsoleCommands._registerOriginal("clearHistory", function JSTH_clearHistory(aOwner)
 {
   aOwner.helperResult = {
     type: "clearHistory",
   };
 });
 
 /**
  * Returns the result of Object.keys(aObject).
  *
  * @param object aObject
  *        Object to return the property names from.
  * @return array of strings
  */
-WebConsoleCommands.register("keys", function JSTH_keys(aOwner, aObject)
+WebConsoleCommands._registerOriginal("keys", function JSTH_keys(aOwner, aObject)
 {
   return aOwner.window.wrappedJSObject.Object.keys(WebConsoleUtils.unwrap(aObject));
 });
 
 /**
  * Returns the values of all properties on aObject.
  *
  * @param object aObject
  *        Object to display the values from.
  * @return array of string
  */
-WebConsoleCommands.register("values", function JSTH_values(aOwner, aObject)
+WebConsoleCommands._registerOriginal("values", function JSTH_values(aOwner, aObject)
 {
   let arrValues = new aOwner.window.wrappedJSObject.Array();
   let obj = WebConsoleUtils.unwrap(aObject);
 
   for (let prop in obj) {
     arrValues.push(obj[prop]);
   }
 
   return arrValues;
 });
 
 /**
  * Opens a help window in MDN.
  */
-WebConsoleCommands.register("help", function JSTH_help(aOwner)
+WebConsoleCommands._registerOriginal("help", function JSTH_help(aOwner)
 {
   aOwner.helperResult = { type: "help" };
 });
 
 /**
  * Change the JS evaluation scope.
  *
  * @param DOMElement|string|window aWindow
  *        The window object to use for eval scope. This can be a string that
  *        is used to perform document.querySelector(), to find the iframe that
  *        you want to cd() to. A DOMElement can be given as well, the
  *        .contentWindow property is used. Lastly, you can directly pass
  *        a window object. If you call cd() with no arguments, the current
  *        eval scope is cleared back to its default (the top window).
  */
-WebConsoleCommands.register("cd", function JSTH_cd(aOwner, aWindow)
+WebConsoleCommands._registerOriginal("cd", function JSTH_cd(aOwner, aWindow)
 {
   if (!aWindow) {
     aOwner.consoleActor.evalWindow = null;
     aOwner.helperResult = { type: "cd" };
     return;
   }
 
   if (typeof aWindow == "string") {
@@ -1773,17 +1792,17 @@ WebConsoleCommands.register("cd", functi
 });
 
 /**
  * Inspects the passed aObject. This is done by opening the PropertyPanel.
  *
  * @param object aObject
  *        Object to inspect.
  */
-WebConsoleCommands.register("inspect", function JSTH_inspect(aOwner, aObject)
+WebConsoleCommands._registerOriginal("inspect", function JSTH_inspect(aOwner, aObject)
 {
   let dbgObj = aOwner.makeDebuggeeValue(aObject);
   let grip = aOwner.createValueGrip(dbgObj);
   aOwner.helperResult = {
     type: "inspectObject",
     input: aOwner.evalInput,
     object: grip,
   };
@@ -1791,17 +1810,17 @@ WebConsoleCommands.register("inspect", f
 
 /**
  * Prints aObject to the output.
  *
  * @param object aObject
  *        Object to print to the output.
  * @return string
  */
-WebConsoleCommands.register("pprint", function JSTH_pprint(aOwner, aObject)
+WebConsoleCommands._registerOriginal("pprint", function JSTH_pprint(aOwner, aObject)
 {
   if (aObject === null || aObject === undefined || aObject === true ||
       aObject === false) {
     aOwner.helperResult = {
       type: "error",
       message: "helperFuncUnsupportedTypeError",
     };
     return null;
@@ -1838,17 +1857,17 @@ WebConsoleCommands.register("pprint", fu
 
 /**
  * Print the String representation of a value to the output, as-is.
  *
  * @param any aValue
  *        A value you want to output as a string.
  * @return void
  */
-WebConsoleCommands.register("print", function JSTH_print(aOwner, aValue)
+WebConsoleCommands._registerOriginal("print", function JSTH_print(aOwner, aValue)
 {
   aOwner.helperResult = { rawOutput: true };
   if (typeof aValue === "symbol") {
     return Symbol.prototype.toString.call(aValue);
   }
   // Waiving Xrays here allows us to see a closer representation of the
   // underlying object. This may execute arbitrary content code, but that
   // code will run with content privileges, and the result will be rendered
@@ -1858,17 +1877,17 @@ WebConsoleCommands.register("print", fun
 
 /**
  * Copy the String representation of a value to the clipboard.
  *
  * @param any aValue
  *        A value you want to copy as a string.
  * @return void
  */
-WebConsoleCommands.register("copy", function JSTH_copy(aOwner, aValue)
+WebConsoleCommands._registerOriginal("copy", function JSTH_copy(aOwner, aValue)
 {
   let payload;
   try {
     if (aValue instanceof Ci.nsIDOMElement) {
       payload = aValue.outerHTML;
     } else if (typeof aValue == "string") {
       payload = aValue;
     } else {