Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 14 Jul 2014 15:22:19 +0200
changeset 215732 ad5f8677a2bf516ceb130a7d116e955a5b16a8a8
parent 215731 8b9e1310637c46d865b3f180efb36e0ba6ea72d5 (current diff)
parent 215708 340b19c14d3d388b7ae1a15bff9695cb0141ac0a (diff)
child 215733 25e80aa9ebd99d1a951345d5a27a37075b758376
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
browser/devtools/inspector/test/browser_inspector_infobar.js
mobile/android/search/java/org/mozilla/search/DetailActivity.java
mobile/android/search/java/org/mozilla/search/autocomplete/AutoCompleteFragment.java
mobile/android/search/java/org/mozilla/search/stream/CardStreamFragment.java
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -925,17 +925,17 @@ let RemoteDebugger = {
 
     if (!DebuggerServer.initialized) {
       // Can this really happen if we are running?
       this._running = false;
       return;
     }
 
     try {
-      DebuggerServer.closeListener();
+      DebuggerServer.closeAllListeners();
     } catch (e) {
       dump('Unable to stop debugger server: ' + e + '\n');
     }
     this._running = false;
   }
 }
 
 let KeyboardHelper = {
--- a/b2g/components/test/mochitest/mochitest.ini
+++ b/b2g/components/test/mochitest/mochitest.ini
@@ -2,13 +2,12 @@
 run-if = toolkit == "gonk"
 support-files =
   permission_handler_chrome.js
   SandboxPromptTest.html
   filepicker_path_handler_chrome.js
   systemapp_helper.js
 
 [test_sandbox_permission.html]
-skip-if = true # bug 984274 - frequent timeouts
 [test_filepicker_path.html]
 [test_permission_deny.html]
 [test_permission_gum_remember.html]
 [test_systemapp.html]
--- 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="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="8154cd4605bbdd64e59efa45b06b5d77032dc5c0"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="88e0a972280bb35847c010b8c3f1481fa80f3847"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bf9aaf39dd5a6491925a022db167c460f8207d34"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
   <!-- 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="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8154cd4605bbdd64e59efa45b06b5d77032dc5c0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="88e0a972280bb35847c010b8c3f1481fa80f3847"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="276ce45e78b09c4a4ee643646f691d22804754c1">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8154cd4605bbdd64e59efa45b06b5d77032dc5c0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="88e0a972280bb35847c010b8c3f1481fa80f3847"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
--- 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="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="8154cd4605bbdd64e59efa45b06b5d77032dc5c0"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="88e0a972280bb35847c010b8c3f1481fa80f3847"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bf9aaf39dd5a6491925a022db167c460f8207d34"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8154cd4605bbdd64e59efa45b06b5d77032dc5c0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="88e0a972280bb35847c010b8c3f1481fa80f3847"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "d7e63029d487bfaba57940d36396d701ddc01655", 
+    "revision": "168c5ba3a072b6c6b30bcc93586f833cc97fff4a", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="8154cd4605bbdd64e59efa45b06b5d77032dc5c0"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="88e0a972280bb35847c010b8c3f1481fa80f3847"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="8154cd4605bbdd64e59efa45b06b5d77032dc5c0"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="88e0a972280bb35847c010b8c3f1481fa80f3847"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8154cd4605bbdd64e59efa45b06b5d77032dc5c0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="88e0a972280bb35847c010b8c3f1481fa80f3847"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="8154cd4605bbdd64e59efa45b06b5d77032dc5c0"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="88e0a972280bb35847c010b8c3f1481fa80f3847"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9ff55cd0aefea23e4c60e5844c155c6ebc2e632b"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -534,16 +534,17 @@ nsBrowserContentHandler.prototype = {
       }
     }
 #endif
   },
 
   helpInfo : "  -browser           Open a browser window.\n" +
              "  -new-window  <url> Open <url> in a new window.\n" +
              "  -new-tab     <url> Open <url> in a new tab.\n" +
+             "  -private-window <url> Open <url> in a new private window.\n" +
 #ifdef XP_WIN
              "  -preferences       Open Options dialog.\n" +
 #else
              "  -preferences       Open Preferences dialog.\n" +
 #endif
              "  -search     <term> Search <term> with your default search engine.\n",
 
   /* nsIBrowserHandler */
--- a/browser/devtools/commandline/test/browser_cmd_csscoverage_oneshot.js
+++ b/browser/devtools/commandline/test/browser_cmd_csscoverage_oneshot.js
@@ -18,17 +18,22 @@ let test = asyncTest(function*() {
   let options = yield helpers.openTab(PAGE_3);
   yield helpers.openToolbar(options);
 
   let usage = yield csscoverage.getUsage(options.target);
 
   yield navigate(usage, options);
   yield checkPages(usage);
   yield checkEditorReport(usage);
-  yield checkPageReport(usage);
+  // usage.createPageReport is not supported for usage.oneshot data as of
+  // bug 1035300 because the page report assumed we have preload data which
+  // oneshot can't gather. The ideal solution is to have a special no-preload
+  // mode for the page report, but since oneshot isn't needed for the UI to
+  // function, we're currently not supporting page report for oneshot data
+  // yield checkPageReport(usage);
 
   yield helpers.closeToolbar(options);
   yield helpers.closeTab(options);
 });
 
 /**
  * Just check current page
  */
--- a/browser/devtools/inspector/test/browser.ini
+++ b/browser/devtools/inspector/test/browser.ini
@@ -3,39 +3,41 @@ skip-if = e10s # Bug ?????? - devtools t
 subsuite = devtools
 support-files =
   doc_inspector_breadcrumbs.html
   doc_inspector_delete-selected-node-01.html
   doc_inspector_delete-selected-node-02.html
   doc_inspector_gcli-inspect-command.html
   doc_inspector_highlighter-comments.html
   doc_inspector_highlighter.html
-  doc_inspector_infobar.html
+  browser_inspector_infobar_01.html
+  browser_inspector_infobar_02.html
   doc_inspector_menu.html
   doc_inspector_remove-iframe-during-load.html
   doc_inspector_search.html
   doc_inspector_search-suggestions.html
   doc_inspector_select-last-selected-01.html
   doc_inspector_select-last-selected-02.html
+  browser_inspector_highlight_after_transition.html
   head.js
 
 [browser_inspector_breadcrumbs.js]
 [browser_inspector_delete-selected-node-01.js]
 [browser_inspector_delete-selected-node-02.js]
 [browser_inspector_delete-selected-node-03.js]
 [browser_inspector_destroy-after-navigation.js]
 [browser_inspector_gcli-inspect-command.js]
 [browser_inspector_highlighter-01.js]
 [browser_inspector_highlighter-02.js]
 [browser_inspector_highlighter-03.js]
 [browser_inspector_highlighter-comments.js]
 [browser_inspector_highlighter-iframes.js]
 [browser_inspector_iframe-navigation.js]
-[browser_inspector_infobar.js]
-skip-if = true # Bug 1028609
+[browser_inspector_infobar_01.js]
+[browser_inspector_infobar_02.js]
 [browser_inspector_initialization.js]
 [browser_inspector_inspect-object-element.js]
 [browser_inspector_invalidate.js]
 [browser_inspector_keyboard-shortcuts.js]
 [browser_inspector_menu.js]
 [browser_inspector_navigation.js]
 [browser_inspector_picker-stop-on-destroy.js]
 [browser_inspector_picker-stop-on-tool-change.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_highlight_after_transition.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+
+  <style>
+    div {
+      opacity: 0;
+      height: 0;
+      background: red;
+      border-top: 1px solid #888;
+      transition-property: height, opacity;
+      transition-duration: 3000ms;
+      transition-timing-function: ease-in-out, ease-in-out, linear;
+    }
+
+    div[visible] {
+      opacity: 1;
+      height: 200px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_highlight_after_transition.js
@@ -0,0 +1,35 @@
+/* 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";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/inspector/" +
+                 "test/browser_inspector_highlight_after_transition.html";
+
+// Test that the nodeinfobar is never displayed above the top or below the
+// bottom of the content area.
+let test = asyncTest(function*() {
+  info("Loading the test document and opening the inspector");
+
+  yield addTab(TEST_URI);
+
+  let {inspector} = yield openInspector();
+
+  yield checkDivHeight(inspector);
+
+  gBrowser.removeCurrentTab();
+});
+
+function* checkDivHeight(inspector) {
+  let div = getNode("div");
+
+  div.setAttribute("visible", "true");
+
+  yield once(div, "transitionend");
+  yield selectAndHighlightNode(div, inspector);
+
+  let height = div.getBoundingClientRect().height;
+
+  is (height, 201, "div is the correct height");
+}
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-02.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-02.js
@@ -10,35 +10,35 @@
 
 const TEST_URI = TEST_URL_ROOT + "doc_inspector_highlighter.html";
 
 let test = asyncTest(function*() {
   let { inspector } = yield openInspectorForURL(TEST_URI);
 
   info("Selecting the simple, non-transformed DIV");
   let div = getNode("#simple-div");
-  yield selectNode(div, inspector, "highlight");
+  yield selectAndHighlightNode(div, inspector);
 
   testSimpleDivHighlighted(div);
   yield zoomTo(2);
   testZoomedSimpleDivHighlighted(div);
   yield zoomTo(1);
 
   info("Selecting the rotated DIV");
   let rotated = getNode("#rotated-div");
   let onBoxModelUpdate = waitForBoxModelUpdate();
-  yield selectNode(rotated, inspector, "highlight");
+  yield selectAndHighlightNode(rotated, inspector);
   yield onBoxModelUpdate;
 
   testMouseOverRotatedHighlights(rotated);
 
   info("Selecting the zero width height DIV");
   let zeroWidthHeight = getNode("#widthHeightZero-div");
   let onBoxModelUpdate = waitForBoxModelUpdate();
-  yield selectNode(zeroWidthHeight, inspector, "highlight");
+  yield selectAndHighlightNode(zeroWidthHeight, inspector);
   yield onBoxModelUpdate;
 
   testMouseOverWidthHeightZeroDiv(zeroWidthHeight);
 
 });
 
 function testSimpleDivHighlighted(div) {
   ok(isHighlighting(), "The highlighter is shown");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_infobar_01.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+
+  <style>
+    body {
+      width: 100%;
+      height: 100%;
+    }
+    div {
+      position: absolute;
+      height: 100px;
+      width: 500px;
+    }
+
+    #bottom {
+      bottom: 0px;
+      background: blue;
+    }
+
+    #vertical {
+      height: 100%;
+      background: green;
+    }
+   </style>
+ </head>
+ <body>
+  <div id="vertical">Vertical</div>
+  <div id="top" class="class1 class2">Top</div>
+  <div id="bottom">Bottom</div>
+ </body>
+ </html>
rename from browser/devtools/inspector/test/browser_inspector_infobar.js
rename to browser/devtools/inspector/test/browser_inspector_infobar_01.js
--- a/browser/devtools/inspector/test/browser_inspector_infobar.js
+++ b/browser/devtools/inspector/test/browser_inspector_infobar_01.js
@@ -1,17 +1,16 @@
 /* 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";
 
 const TEST_URI = "http://example.com/browser/browser/devtools/inspector/" +
-                 "test/doc_inspector_infobar.html";
-const DOORHANGER_ARROW_HEIGHT = 5;
+                 "test/browser_inspector_infobar_01.html";
 
 // Test that hovering over nodes in the markup-view shows the highlighter over
 // those nodes
 let test = asyncTest(function*() {
   info("Loading the test document and opening the inspector");
 
   yield addTab(TEST_URI);
 
@@ -46,43 +45,32 @@ let test = asyncTest(function*() {
     {
       node: doc.querySelector("body"),
       position: "bottom",
       tag: "BODY",
       id: "",
       classes: ""
       // No dims as they will vary between computers
     },
-    {
-      node: doc.querySelector("#farbottom"),
-      position: "top",
-      tag: "DIV",
-      id: "#farbottom",
-      classes: "",
-      dims: "500 x 100"
-    },
   ];
 
   for (let currTest of testData) {
     yield testPosition(currTest, inspector);
   }
 
-  yield checkInfoBarAboveTop(inspector);
-  yield checkInfoBarBelowFindbar(inspector);
-
   gBrowser.removeCurrentTab();
 });
 
 function* testPosition(currTest, inspector) {
   let browser = gBrowser.selectedBrowser;
   let stack = browser.parentNode;
 
   info("Testing " + currTest.id);
 
-  yield selectNode(currTest.node, inspector, "highlight");
+  yield selectAndHighlightNode(currTest.node, inspector);
 
   let container = stack.querySelector(".highlighter-nodeinfobar-positioner");
   is(container.getAttribute("position"),
     currTest.position, "node " + currTest.id + ": position matches.");
 
   let tagNameLabel = stack.querySelector(".highlighter-nodeinfobar-tagname");
   is(tagNameLabel.textContent, currTest.tag,
     "node " + currTest.id + ": tagName matches.");
@@ -96,55 +84,8 @@ function* testPosition(currTest, inspect
   is(classesBox.textContent, currTest.classes,
     "node " + currTest.id  + ": classes match.");
 
   if (currTest.dims) {
     let dimBox = stack.querySelector(".highlighter-nodeinfobar-dimensions");
     is(dimBox.textContent, currTest.dims, "node " + currTest.id  + ": dims match.");
   }
 }
-
-function* checkInfoBarAboveTop(inspector) {
-  yield selectNode("#abovetop", inspector);
-
-  let positioner = getPositioner();
-  let insideContent = parseInt(positioner.style.top, 10) >= -DOORHANGER_ARROW_HEIGHT;
-
-  ok(insideContent, "Infobar is inside the content window (top = " +
-                    parseInt(positioner.style.top, 10) + ", content = '" +
-                    positioner.textContent +"')");
-}
-
-function* checkInfoBarBelowFindbar(inspector) {
-  gFindBar.open();
-
-  let body = content.document.body;
-  let farBottom = body.querySelector("#farbottom");
-  farBottom.scrollIntoView();
-
-  // Wait for scrollIntoView
-  yield waitForTick();
-
-  body.scrollTop -= 130;
-  yield selectNode(farBottom, inspector);
-
-  let positioner = getPositioner();
-  let insideContent = parseInt(positioner.style.top, 10) >= -DOORHANGER_ARROW_HEIGHT;
-
-  ok(insideContent, "Infobar does not overlap the findbar (top = " +
-                    parseInt(positioner.style.top, 10) + ", content = '" +
-                    positioner.textContent +"')");
-
-  gFindBar.close();
-}
-
-function getPositioner() {
-  let browser = gBrowser.selectedBrowser;
-  let stack = browser.parentNode;
-
-  return stack.querySelector(".highlighter-nodeinfobar-positioner");
-}
-
-function waitForTick() {
-  let deferred = promise.defer();
-  executeSoon(deferred.resolve);
-  return deferred.promise;
-}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_infobar_02.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+
+  <style>
+    body {
+      width: 100%;
+      height: 100%;
+    }
+
+    div {
+      position: absolute;
+      height: 100px;
+      width: 500px;
+    }
+
+    #below-bottom {
+      bottom: -200px;
+      background: red;
+    }
+
+    #above-top {
+      top: -200px;
+      background: black;
+      color: white;
+    }";
+  </style>
+</head>
+<body>
+  <div id="above-top">Above top</div>
+  <div id="below-bottom">Far bottom</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_infobar_02.js
@@ -0,0 +1,65 @@
+/* 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";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/inspector/" +
+                 "test/browser_inspector_infobar_02.html";
+const DOORHANGER_ARROW_HEIGHT = 5;
+
+// Test that the nodeinfobar is never displayed above the top or below the
+// bottom of the content area.
+let test = asyncTest(function*() {
+  info("Loading the test document and opening the inspector");
+
+  yield addTab(TEST_URI);
+
+  let {inspector} = yield openInspector();
+
+  yield checkInfoBarAboveTop(inspector);
+  yield checkInfoBarBelowFindbar(inspector);
+
+  gBrowser.removeCurrentTab();
+});
+
+function* checkInfoBarAboveTop(inspector) {
+  yield selectAndHighlightNode("#above-top", inspector);
+
+  let positioner = getPositioner();
+  let positionerTop = parseInt(positioner.style.top, 10);
+  let insideContent = positionerTop >= -DOORHANGER_ARROW_HEIGHT;
+
+  ok(insideContent, "Infobar is inside the content window (top = " +
+                    positionerTop + ", content = '" +
+                    positioner.textContent +"')");
+}
+
+function* checkInfoBarBelowFindbar(inspector) {
+  gFindBar.open();
+
+  // Ensure that the findbar is fully open.
+  yield once(gFindBar, "transitionend");
+  yield selectAndHighlightNode("#below-bottom", inspector);
+
+  let positioner = getPositioner();
+  let positionerBottom =
+    positioner.getBoundingClientRect().bottom - DOORHANGER_ARROW_HEIGHT;
+  let findBarTop = gFindBar.getBoundingClientRect().top;
+
+  let insideContent = positionerBottom <= findBarTop;
+
+  ok(insideContent, "Infobar does not overlap the findbar (findBarTop = " +
+                    findBarTop + ", positionerBottom = " + positionerBottom +
+                    ", content = '" + positioner.textContent +"')");
+
+  gFindBar.close();
+  yield once(gFindBar, "transitionend");
+}
+
+function getPositioner() {
+  let browser = gBrowser.selectedBrowser;
+  let stack = browser.parentNode;
+
+  return stack.querySelector(".highlighter-nodeinfobar-positioner");
+}
--- a/browser/devtools/inspector/test/head.js
+++ b/browser/devtools/inspector/test/head.js
@@ -128,33 +128,51 @@ function getNode(nodeOrSelector, options
     return node;
   }
 
   info("Looking for a node but selector was not a string.");
   return nodeOrSelector;
 }
 
 /**
+ * Highlight a node and set the inspector's current selection to the node or
+ * the first match of the given css selector.
+ * @param {String|DOMNode} nodeOrSelector
+ * @param {InspectorPanel} inspector
+ *        The instance of InspectorPanel currently loaded in the toolbox
+ * @return a promise that resolves when the inspector is updated with the new
+ * node
+ */
+function selectAndHighlightNode(nodeOrSelector, inspector) {
+  info("Highlighting and selecting the node " + nodeOrSelector);
+
+  let node = getNode(nodeOrSelector);
+  let updated = inspector.toolbox.once("highlighter-ready");
+  inspector.selection.setNode(node, "test-highlight");
+  return updated;
+
+}
+
+/**
  * Set the inspector's current selection to a node or to the first match of the
- * given css selector
- * @param {InspectorPanel} inspector The instance of InspectorPanel currently
- * loaded in the toolbox
- * @param {String} reason Defaults to "test" which instructs the inspector not
- * to highlight the node upon selection
- * @param {String} reason Defaults to "test" which instructs the inspector not
- * to highlight the node upon selection
+ * given css selector.
+ * @param {String|DOMNode} nodeOrSelector
+ * @param {InspectorPanel} inspector
+ *        The instance of InspectorPanel currently loaded in the toolbox
+ * @param {String} reason
+ *        Defaults to "test" which instructs the inspector not to highlight the
+ *        node upon selection
  * @return a promise that resolves when the inspector is updated with the new
  * node
  */
 function selectNode(nodeOrSelector, inspector, reason="test") {
   info("Selecting the node " + nodeOrSelector);
+
   let node = getNode(nodeOrSelector);
-  let updated = inspector.once("inspector-updated", () => {
-    is(inspector.selection.node, node, "Correct node was selected");
-  });
+  let updated = inspector.once("inspector-updated");
   inspector.selection.setNode(node, reason);
   return updated;
 }
 
 /**
  * Open the inspector in a tab with given URL.
  * @param {string} url  The URL to open.
  * @return A promise that is resolved once the tab and inspector have loaded
--- a/browser/devtools/layoutview/test/head.js
+++ b/browser/devtools/layoutview/test/head.js
@@ -97,29 +97,49 @@ let destroyToolbox = Task.async(function
  */
 function getNode(nodeOrSelector) {
   return typeof nodeOrSelector === "string" ?
     content.document.querySelector(nodeOrSelector) :
     nodeOrSelector;
 }
 
 /**
+ * Highlight a node and set the inspector's current selection to the node or
+ * the first match of the given css selector.
+ * @param {String|DOMNode} nodeOrSelector
+ * @param {InspectorPanel} inspector
+ *        The instance of InspectorPanel currently loaded in the toolbox
+ * @return a promise that resolves when the inspector is updated with the new
+ * node
+ */
+function selectAndHighlightNode(nodeOrSelector, inspector) {
+  info("Highlighting and selecting the node " + nodeOrSelector);
+
+  let node = getNode(nodeOrSelector);
+  let updated = inspector.toolbox.once("highlighter-ready");
+  inspector.selection.setNode(node, "test-highlight");
+  return updated;
+
+}
+
+/**
  * Set the inspector's current selection to a node or to the first match of the
- * given css selector
- * @param {InspectorPanel} inspector The instance of InspectorPanel currently
- * loaded in the toolbox
- * @param {String} reason Defaults to "test" which instructs the inspector not
- * to highlight the node upon selection
- * @param {String} reason Defaults to "test" which instructs the inspector not
- * to highlight the node upon selection
+ * given css selector.
+ * @param {String|DOMNode} nodeOrSelector
+ * @param {InspectorPanel} inspector
+ *        The instance of InspectorPanel currently loaded in the toolbox
+ * @param {String} reason
+ *        Defaults to "test" which instructs the inspector not to highlight the
+ *        node upon selection
  * @return a promise that resolves when the inspector is updated with the new
  * node
  */
 function selectNode(nodeOrSelector, inspector, reason="test") {
   info("Selecting the node " + nodeOrSelector);
+
   let node = getNode(nodeOrSelector);
   let updated = inspector.once("inspector-updated");
   inspector.selection.setNode(node, reason);
   return updated;
 }
 
 /**
  * Open the toolbox, with the inspector tool visible.
--- a/browser/devtools/markupview/test/head.js
+++ b/browser/devtools/markupview/test/head.js
@@ -129,26 +129,49 @@ function openInspector() {
 function getNode(nodeOrSelector) {
   info("Getting the node for '" + nodeOrSelector + "'");
   return typeof nodeOrSelector === "string" ?
     content.document.querySelector(nodeOrSelector) :
     nodeOrSelector;
 }
 
 /**
- * Set the inspector's current selection to a node or to the first match of the
- * given css selector
+ * Highlight a node and set the inspector's current selection to the node or
+ * the first match of the given css selector.
  * @param {String|DOMNode} nodeOrSelector
- * @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
- * @param {String} reason Defaults to "test" which instructs the inspector not to highlight the node upon selection
+ * @param {InspectorPanel} inspector
+ *        The instance of InspectorPanel currently loaded in the toolbox
+ * @return a promise that resolves when the inspector is updated with the new
+ * node
+ */
+function selectAndHighlightNode(nodeOrSelector, inspector) {
+  info("Highlighting and selecting the node " + nodeOrSelector);
+
+  let node = getNode(nodeOrSelector);
+  let updated = inspector.toolbox.once("highlighter-ready");
+  inspector.selection.setNode(node, "test-highlight");
+  return updated;
+
+}
+
+/**
+ * Set the inspector's current selection to a node or to the first match of the
+ * given css selector.
+ * @param {String|DOMNode} nodeOrSelector
+ * @param {InspectorPanel} inspector
+ *        The instance of InspectorPanel currently loaded in the toolbox
+ * @param {String} reason
+ *        Defaults to "test" which instructs the inspector not to highlight the
+ *        node upon selection
  * @return a promise that resolves when the inspector is updated with the new
  * node
  */
 function selectNode(nodeOrSelector, inspector, reason="test") {
-  info("Selecting the node for '" + nodeOrSelector + "'");
+  info("Selecting the node " + nodeOrSelector);
+
   let node = getNode(nodeOrSelector);
   let updated = inspector.once("inspector-updated");
   inspector.selection.setNode(node, reason);
   return updated;
 }
 
 /**
  * Get the MarkupContainer object instance that corresponds to the given
--- a/browser/devtools/profiler/test/browser_profiler_remote.js
+++ b/browser/devtools/profiler/test/browser_profiler_remote.js
@@ -17,20 +17,20 @@ let ProfilerController = temp.ProfilerCo
 
 function test() {
   waitForExplicitFinish();
   Services.prefs.setBoolPref(REMOTE_ENABLED, true);
 
   loadTab(URL, function onTabLoad(tab, browser) {
     DebuggerServer.init(function () true);
     DebuggerServer.addBrowserActors();
-    is(DebuggerServer._socketConnections, 0);
+    is(DebuggerServer.listeningSockets, 0);
 
     DebuggerServer.openListener(2929);
-    is(DebuggerServer._socketConnections, 1);
+    is(DebuggerServer.listeningSockets, 1);
 
     let transport = debuggerSocketConnect("127.0.0.1", 2929);
     let client = new DebuggerClient(transport);
     client.connect(function onClientConnect() {
       let target = { isRemote: true, client: client };
       let controller = new ProfilerController(target);
 
       controller.connect(function onControllerConnect() {
@@ -47,9 +47,9 @@ function test() {
         window.addEventListener("Debugger:Shutdown", onShutdown, true);
 
         client.close(function () {
           gBrowser.removeTab(tab);
         });
       });
     });
   });
-}
\ No newline at end of file
+}
--- a/browser/devtools/styleinspector/test/head.js
+++ b/browser/devtools/styleinspector/test/head.js
@@ -124,28 +124,49 @@ function addTab(url) {
  */
 function getNode(nodeOrSelector) {
   return typeof nodeOrSelector === "string" ?
     content.document.querySelector(nodeOrSelector) :
     nodeOrSelector;
 }
 
 /**
+ * Highlight a node and set the inspector's current selection to the node or
+ * the first match of the given css selector.
+ * @param {String|DOMNode} nodeOrSelector
+ * @param {InspectorPanel} inspector
+ *        The instance of InspectorPanel currently loaded in the toolbox
+ * @return a promise that resolves when the inspector is updated with the new
+ * node
+ */
+function selectAndHighlightNode(nodeOrSelector, inspector) {
+  info("Highlighting and selecting the node " + nodeOrSelector);
+
+  let node = getNode(nodeOrSelector);
+  let updated = inspector.toolbox.once("highlighter-ready");
+  inspector.selection.setNode(node, "test-highlight");
+  return updated;
+
+}
+
+/**
  * Set the inspector's current selection to a node or to the first match of the
- * given css selector
- * @param {InspectorPanel} inspector The instance of InspectorPanel currently
- * loaded in the toolbox
- * @param {String} reason Defaults to "test" which instructs the inspector not
- * to highlight the node upon selection
- * @param {String} reason Defaults to "test" which instructs the inspector not to highlight the node upon selection
+ * given css selector.
+ * @param {String|DOMNode} nodeOrSelector
+ * @param {InspectorPanel} inspector
+ *        The instance of InspectorPanel currently loaded in the toolbox
+ * @param {String} reason
+ *        Defaults to "test" which instructs the inspector not to highlight the
+ *        node upon selection
  * @return a promise that resolves when the inspector is updated with the new
  * node
  */
 function selectNode(nodeOrSelector, inspector, reason="test") {
   info("Selecting the node " + nodeOrSelector);
+
   let node = getNode(nodeOrSelector);
   let updated = inspector.once("inspector-updated");
   inspector.selection.setNode(node, reason);
   return updated;
 }
 
 /**
  * Set the inspector's current selection to null so that no node is selected
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -146,16 +146,23 @@ AudioChannelService::RegisterType(AudioC
   if (mDisabled) {
     return;
   }
 
   AudioChannelInternalType type = GetInternalType(aChannel, true);
   mChannelCounters[type].AppendElement(aChildID);
 
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
+
+    // We must keep the childIds in order to decide which app is allowed to play
+    // with then telephony channel.
+    if (aChannel == AudioChannel::Telephony) {
+      RegisterTelephonyChild(aChildID);
+    }
+
     // Since there is another telephony registered, we can unregister old one
     // immediately.
     if (mDeferTelChannelTimer && aChannel == AudioChannel::Telephony) {
       mDeferTelChannelTimer->Cancel();
       mDeferTelChannelTimer = nullptr;
       UnregisterTypeInternal(aChannel, mTimerElementHidden, mTimerChildID,
                              false);
     }
@@ -229,25 +236,31 @@ AudioChannelService::UnregisterType(Audi
 {
   if (mDisabled) {
     return;
   }
 
   // There are two reasons to defer the decrease of telephony channel.
   // 1. User can have time to remove device from his ear before music resuming.
   // 2. Give BT SCO to be disconnected before starting to connect A2DP.
-  if (XRE_GetProcessType() == GeckoProcessType_Default &&
-      aChannel == AudioChannel::Telephony &&
-      (mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN].Length() +
-       mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].Length()) == 1) {
-    mTimerElementHidden = aElementHidden;
-    mTimerChildID = aChildID;
-    mDeferTelChannelTimer = do_CreateInstance("@mozilla.org/timer;1");
-    mDeferTelChannelTimer->InitWithCallback(this, 1500, nsITimer::TYPE_ONE_SHOT);
-    return;
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+
+    if (aChannel == AudioChannel::Telephony) {
+      UnregisterTelephonyChild(aChildID);
+    }
+
+    if (aChannel == AudioChannel::Telephony &&
+        (mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN].Length() +
+         mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].Length()) == 1) {
+      mTimerElementHidden = aElementHidden;
+      mTimerChildID = aChildID;
+      mDeferTelChannelTimer = do_CreateInstance("@mozilla.org/timer;1");
+      mDeferTelChannelTimer->InitWithCallback(this, 1500, nsITimer::TYPE_ONE_SHOT);
+      return;
+    }
   }
 
   UnregisterTypeInternal(aChannel, aElementHidden, aChildID, aWithVideo);
 }
 
 void
 AudioChannelService::UnregisterTypeInternal(AudioChannel aChannel,
                                             bool aElementHidden,
@@ -353,17 +366,17 @@ AudioChannelService::GetStateInternal(Au
 
   SendAudioChannelChangedNotification(aChildID);
 
   // Let play any visible audio channel.
   if (!aElementHidden) {
     if (CheckVolumeFadedCondition(newType, aElementHidden)) {
       return AUDIO_CHANNEL_STATE_FADED;
     }
-    return AUDIO_CHANNEL_STATE_NORMAL;
+    return CheckTelephonyPolicy(aChannel, aChildID);
   }
 
   // We are not visible, maybe we have to mute.
   if (newType == AUDIO_CHANNEL_INT_NORMAL_HIDDEN ||
       (newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
        // One process can have multiple content channels; and during the
        // transition from foreground to background, its content channels will be
        // updated with correct visibility status one by one. All its content
@@ -382,17 +395,44 @@ AudioChannelService::GetStateInternal(Au
   if (ChannelsActiveWithHigherPriorityThan(newType)) {
     MOZ_ASSERT(newType != AUDIO_CHANNEL_INT_NORMAL_HIDDEN);
     if (CheckVolumeFadedCondition(newType, aElementHidden)) {
       return AUDIO_CHANNEL_STATE_FADED;
     }
     return AUDIO_CHANNEL_STATE_MUTED;
   }
 
-  return AUDIO_CHANNEL_STATE_NORMAL;
+  return CheckTelephonyPolicy(aChannel, aChildID);
+}
+
+AudioChannelState
+AudioChannelService::CheckTelephonyPolicy(AudioChannel aChannel,
+                                          uint64_t aChildID)
+{
+  // Only the latest childID is allowed to play with telephony channel.
+  if (aChannel != AudioChannel::Telephony) {
+    return AUDIO_CHANNEL_STATE_NORMAL;
+  }
+
+  MOZ_ASSERT(!mTelephonyChildren.IsEmpty());
+
+#if DEBUG
+  bool found = false;
+  for (uint32_t i = 0, len = mTelephonyChildren.Length(); i < len; ++i) {
+    if (mTelephonyChildren[i].mChildID == aChildID) {
+      found = true;
+      break;
+    }
+  }
+
+  MOZ_ASSERT(found);
+#endif
+
+  return mTelephonyChildren.LastElement().mChildID == aChildID
+           ? AUDIO_CHANNEL_STATE_NORMAL : AUDIO_CHANNEL_STATE_MUTED;
 }
 
 bool
 AudioChannelService::CheckVolumeFadedCondition(AudioChannelInternalType aType,
                                                bool aElementHidden)
 {
   // Only normal & content channels are considered
   if (aType > AUDIO_CHANNEL_INT_CONTENT_HIDDEN) {
@@ -981,8 +1021,44 @@ AudioChannelService::GetDefaultAudioChan
     for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
       if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
         aString = audioChannel;
         break;
       }
     }
   }
 }
+
+void
+AudioChannelService::RegisterTelephonyChild(uint64_t aChildID)
+{
+  for (uint32_t i = 0, len = mTelephonyChildren.Length(); i < len; ++i) {
+    if (mTelephonyChildren[i].mChildID == aChildID) {
+      ++mTelephonyChildren[i].mInstances;
+
+      if (i != len - 1) {
+        TelephonyChild child = mTelephonyChildren[i];
+        mTelephonyChildren.RemoveElementAt(i);
+        mTelephonyChildren.AppendElement(child);
+      }
+
+      return;
+    }
+  }
+
+  mTelephonyChildren.AppendElement(TelephonyChild(aChildID));
+}
+
+void
+AudioChannelService::UnregisterTelephonyChild(uint64_t aChildID)
+{
+  for (uint32_t i = 0, len = mTelephonyChildren.Length(); i < len; ++i) {
+    if (mTelephonyChildren[i].mChildID == aChildID) {
+      if (!--mTelephonyChildren[i].mInstances) {
+        mTelephonyChildren.RemoveElementAt(i);
+      }
+
+      return;
+    }
+  }
+
+  MOZ_ASSERT(false, "This should not happen.");
+}
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -145,16 +145,21 @@ protected:
   /* Update the internal type value following the visibility changes */
   void UpdateChannelType(AudioChannel aChannel, uint64_t aChildID,
                          bool aElementHidden, bool aElementWasHidden);
 
   /* Send the default-volume-channel-changed notification */
   void SetDefaultVolumeControlChannelInternal(int32_t aChannel,
                                               bool aHidden, uint64_t aChildID);
 
+  AudioChannelState CheckTelephonyPolicy(AudioChannel aChannel,
+                                         uint64_t aChildID);
+  void RegisterTelephonyChild(uint64_t aChildID);
+  void UnregisterTelephonyChild(uint64_t aChildID);
+
   AudioChannelService();
   virtual ~AudioChannelService();
 
   enum AudioChannelInternalType {
     AUDIO_CHANNEL_INT_NORMAL = 0,
     AUDIO_CHANNEL_INT_NORMAL_HIDDEN,
     AUDIO_CHANNEL_INT_CONTENT,
     AUDIO_CHANNEL_INT_CONTENT_HIDDEN,
@@ -220,16 +225,29 @@ protected:
 #endif
   nsTArray<uint64_t> mChannelCounters[AUDIO_CHANNEL_INT_LAST];
 
   int32_t mCurrentHigherChannel;
   int32_t mCurrentVisibleHigherChannel;
 
   nsTArray<uint64_t> mWithVideoChildIDs;
 
+  // Telephony Channel policy is "LIFO", the last app to require the resource is
+  // allowed to play. The others are muted.
+  struct TelephonyChild {
+    uint64_t mChildID;
+    uint32_t mInstances;
+
+    explicit TelephonyChild(uint64_t aChildID)
+      : mChildID(aChildID)
+      , mInstances(1)
+    {}
+  };
+  nsTArray<TelephonyChild> mTelephonyChildren;
+
   // mPlayableHiddenContentChildID stores the ChildID of the process which can
   // play content channel(s) in the background.
   // A background process contained content channel(s) will become playable:
   //   1. When this background process registers its content channel(s) in
   //   AudioChannelService and there is no foreground process with registered
   //   content channel(s).
   //   2. When this process goes from foreground into background and there is
   //   no foreground process with registered content channel(s).
copy from dom/base/test/audio.ogg
copy to dom/audiochannel/tests/audio.ogg
new file mode 100644
--- /dev/null
+++ b/dom/audiochannel/tests/file_telephonyPolicy.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test Telephony Channel Policy</title>
+</head>
+<body>
+<div id="container"></div>
+  <script type="application/javascript;version=1.7">
+
+  var audio = new Audio();
+  audio.mozAudioChannelType = 'telephony';
+  audio.src = "audio.ogg";
+  audio.play();
+
+  </script>
+</body>
+</html>
--- a/dom/audiochannel/tests/mochitest.ini
+++ b/dom/audiochannel/tests/mochitest.ini
@@ -1,7 +1,11 @@
 [DEFAULT]
 support-files =
+  audio.ogg
   file_audio.html
+  file_telephonyPolicy.html
   AudioChannelChromeScript.js
 
+[test_telephonyPolicy.html]
+skip-if = (toolkit == 'gonk' || e10s)
 [test_audioChannelChange.html]
 skip-if = (toolkit != 'gonk')
new file mode 100644
--- /dev/null
+++ b/dom/audiochannel/tests/test_telephonyPolicy.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test the Telephony Channel Policy</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="container"></div>
+  <script type="application/javascript;version=1.7">
+
+function mainApp() {
+  var audio = new Audio();
+  audio.mozAudioChannelType = 'telephony';
+  audio.src = "audio.ogg";
+  audio.loop = true;
+  audio.play();
+
+  audio.addEventListener('mozinterruptbegin', function() {
+    ok(true, "This element has been muted!");
+  }, false);
+
+  audio.addEventListener('mozinterruptend', function() {
+    ok(true, "This element has been unmuted!");
+    audio.pause();
+    runTest();
+  }, false);
+
+  setTimeout(runTest, 600);
+}
+
+function newApp() {
+  var iframe = document.createElement('iframe');
+  iframe.setAttribute('mozbrowser', true);
+  // That needs to be an app.
+  iframe.setAttribute('mozapp', 'https://acertified.com/manifest.webapp');
+  iframe.src = "file_telephonyPolicy.html";
+  document.body.appendChild(iframe);
+}
+
+var tests = [
+  // Permissions
+  function() {
+    SpecialPowers.pushPermissions(
+      [{ "type": "browser", "allow": 1, "context": document },
+       { "type": "embed-apps", "allow": 1, "context": document },
+       { "type": "webapps-manage", "allow": 1, "context": document },
+       { "type": "audio-channel-telephony", "allow": 1, "context": document }], runTest);
+  },
+
+  // Preferences
+  function() {
+    SpecialPowers.pushPrefEnv({"set": [["dom.ipc.browser_frames.oop_by_default", true],
+                                       ["media.useAudioChannelService", true],
+                                       ["media.defaultAudioChannel", "telephony"],
+                                       ["dom.mozBrowserFramesEnabled", true],
+                                       ["network.disable.ipc.security", true]]}, runTest);
+  },
+
+  // Run 2 apps
+  mainApp,
+  newApp,
+];
+
+function runTest() {
+  if (!tests.length) {
+    finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+function finish() {
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+  </script>
+</body>
+</html>
--- a/dom/bluetooth2/BluetoothAdapter.cpp
+++ b/dom/bluetooth2/BluetoothAdapter.cpp
@@ -15,16 +15,17 @@
 #include "mozilla/dom/BluetoothAttributeEvent.h"
 #include "mozilla/dom/BluetoothDeviceEvent.h"
 #include "mozilla/dom/BluetoothStatusChangedEvent.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/LazyIdleThread.h"
 
 #include "BluetoothAdapter.h"
+#include "BluetoothClassOfDevice.h"
 #include "BluetoothDevice.h"
 #include "BluetoothDiscoveryHandle.h"
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "BluetoothUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
@@ -59,37 +60,42 @@ NS_IMPL_RELEASE_INHERITED(BluetoothAdapt
 class StartDiscoveryTask : public BluetoothReplyRunnable
 {
  public:
   StartDiscoveryTask(BluetoothAdapter* aAdapter, Promise* aPromise)
     : BluetoothReplyRunnable(nullptr, aPromise,
                              NS_LITERAL_STRING("StartDiscovery"))
     , mAdapter(aAdapter)
   {
-    MOZ_ASSERT(aPromise && aAdapter);
+    MOZ_ASSERT(aPromise);
+    MOZ_ASSERT(aAdapter);
   }
 
   bool
   ParseSuccessfulReply(JS::MutableHandle<JS::Value> aValue)
   {
-    BT_API2_LOGR();
     aValue.setUndefined();
 
     AutoJSAPI jsapi;
     NS_ENSURE_TRUE(jsapi.Init(mAdapter->GetParentObject()), false);
+    JSContext* cx = jsapi.cx();
 
-    // Wrap BluetoothDiscoveryHandle to return
-    JSContext* cx = jsapi.cx();
+    /**
+     * 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());
     if (!ToJSValue(cx, discoveryHandle, aValue)) {
       JS_ClearPendingException(cx);
       return false;
     }
 
+    // Set the created discovery handle as the one in use.
+    mAdapter->SetDiscoveryHandleInUse(discoveryHandle);
     return true;
   }
 
   virtual void
   ReleaseMembers() MOZ_OVERRIDE
   {
     BluetoothReplyRunnable::ReleaseMembers();
     mAdapter = nullptr;
@@ -200,16 +206,17 @@ public:
 
 static int kCreatePairedDeviceTimeout = 50000; // unit: msec
 
 BluetoothAdapter::BluetoothAdapter(nsPIDOMWindow* aWindow,
                                    const BluetoothValue& aValue)
   : DOMEventTargetHelper(aWindow)
   , mJsUuids(nullptr)
   , mJsDeviceAddresses(nullptr)
+  , mDiscoveryHandleInUse(nullptr)
   , mState(BluetoothAdapterState::Disabled)
   , mDiscoverable(false)
   , mDiscovering(false)
   , mPairable(false)
   , mPowered(false)
   , mIsRooted(false)
 {
   MOZ_ASSERT(aWindow);
@@ -268,27 +275,30 @@ BluetoothAdapter::Root()
 }
 
 void
 BluetoothAdapter::SetPropertyByValue(const BluetoothNamedValue& aValue)
 {
   const nsString& name = aValue.name();
   const BluetoothValue& value = aValue.value();
   if (name.EqualsLiteral("State")) {
-    bool isEnabled = value.get_bool();
-    mState = isEnabled ? BluetoothAdapterState::Enabled
-                       : BluetoothAdapterState::Disabled;
+    mState = value.get_bool() ? BluetoothAdapterState::Enabled
+                              : BluetoothAdapterState::Disabled;
   } else if (name.EqualsLiteral("Name")) {
     mName = value.get_nsString();
   } else if (name.EqualsLiteral("Address")) {
     mAddress = value.get_nsString();
   } else if (name.EqualsLiteral("Discoverable")) {
     mDiscoverable = value.get_bool();
   } else if (name.EqualsLiteral("Discovering")) {
     mDiscovering = value.get_bool();
+    if (!mDiscovering) {
+      // Reset discovery handle in use to nullptr
+      SetDiscoveryHandleInUse(nullptr);
+    }
   } else if (name.EqualsLiteral("Pairable")) {
     mPairable = value.get_bool();
   } else if (name.EqualsLiteral("Powered")) {
     mPowered = value.get_bool();
   } else if (name.EqualsLiteral("PairableTimeout")) {
     mPairableTimeout = value.get_uint32_t();
   } else if (name.EqualsLiteral("DiscoverableTimeout")) {
     mDiscoverableTimeout = value.get_uint32_t();
@@ -385,33 +395,48 @@ BluetoothAdapter::Notify(const Bluetooth
 
     DispatchTrustedEvent(event);
   } else {
     BT_WARNING("Not handling adapter signal: %s",
                NS_ConvertUTF16toUTF8(aData.name()).get());
   }
 }
 
+void
+BluetoothAdapter::SetDiscoveryHandleInUse(
+  BluetoothDiscoveryHandle* aDiscoveryHandle)
+{
+  // Stop discovery handle in use from listening to "DeviceFound" signal
+  if (mDiscoveryHandleInUse) {
+    mDiscoveryHandleInUse->DisconnectFromOwner();
+  }
+
+  mDiscoveryHandleInUse = aDiscoveryHandle;
+}
+
 already_AddRefed<Promise>
 BluetoothAdapter::StartStopDiscovery(bool aStart, ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
   nsRefPtr<Promise> promise = new Promise(global);
 
   /**
    * Ensure
    * - adapter does not already start/stop discovering,
+   *   (note we reject here to ensure each resolved promise of startDiscovery
+   *    returns a BluetoothDiscoveryHandle)
    * - adapter is already enabled, and
    * - BluetoothService is available
    */
-  BT_ENSURE_TRUE_RESOLVE(mDiscovering != aStart, JS::UndefinedHandleValue);
+  BT_ENSURE_TRUE_REJECT(mDiscovering != aStart,
+                        NS_ERROR_DOM_INVALID_STATE_ERR);
   BT_ENSURE_TRUE_REJECT(mState == BluetoothAdapterState::Enabled,
                         NS_ERROR_DOM_INVALID_STATE_ERR);
   BluetoothService* bs = BluetoothService::Get();
   BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
 
   BT_API2_LOGR("aStart %d", aStart);
   nsresult rv;
   if (aStart) {
@@ -819,22 +844,20 @@ BluetoothAdapter::ConvertStringToAdapter
   return BluetoothAdapterAttribute::Unknown;
 }
 
 bool
 BluetoothAdapter::IsAdapterAttributeChanged(BluetoothAdapterAttribute aType,
                                             const BluetoothValue& aValue)
 {
   switch(aType) {
-    case BluetoothAdapterAttribute::State: {
+    case BluetoothAdapterAttribute::State:
       MOZ_ASSERT(aValue.type() == BluetoothValue::Tbool);
-      bool isEnabled = aValue.get_bool();
-      return isEnabled ? mState != BluetoothAdapterState::Enabled
-                       : mState != BluetoothAdapterState::Disabled;
-    }
+      return aValue.get_bool() ? mState != BluetoothAdapterState::Enabled
+                               : mState != BluetoothAdapterState::Disabled;
     case BluetoothAdapterAttribute::Name:
       MOZ_ASSERT(aValue.type() == BluetoothValue::TnsString);
       return !mName.Equals(aValue.get_nsString());
     case BluetoothAdapterAttribute::Address:
       MOZ_ASSERT(aValue.type() == BluetoothValue::TnsString);
       return !mAddress.Equals(aValue.get_nsString());
     case BluetoothAdapterAttribute::Discoverable:
       MOZ_ASSERT(aValue.type() == BluetoothValue::Tbool);
@@ -923,17 +946,17 @@ BluetoothAdapter::Connect(BluetoothDevic
   }
 
   nsRefPtr<DOMRequest> request = new DOMRequest(win);
   nsRefPtr<BluetoothVoidReplyRunnable> results =
     new BluetoothVoidReplyRunnable(request);
 
   nsAutoString address;
   aDevice.GetAddress(address);
-  uint32_t deviceClass = aDevice.Class();
+  uint32_t deviceClass = aDevice.Cod()->ToUint32();
   uint16_t serviceUuid = 0;
   if (aServiceUuid.WasPassed()) {
     serviceUuid = aServiceUuid.Value();
   }
 
   BluetoothService* bs = BluetoothService::Get();
   if (!bs) {
     aRv.Throw(NS_ERROR_FAILURE);
--- a/dom/bluetooth2/BluetoothAdapter.h
+++ b/dom/bluetooth2/BluetoothAdapter.h
@@ -20,16 +20,17 @@ class DOMRequest;
 struct MediaMetaData;
 struct MediaPlayStatus;
 }
 }
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothDevice;
+class BluetoothDiscoveryHandle;
 class BluetoothSignal;
 class BluetoothNamedValue;
 class BluetoothValue;
 
 class BluetoothAdapter : public DOMEventTargetHelper
                        , public BluetoothSignalObserver
 {
 public:
@@ -88,16 +89,27 @@ public:
     return mDiscoverableTimeout;
   }
 
   void GetDevices(JSContext* aContext, JS::MutableHandle<JS::Value> aDevices,
                   ErrorResult& aRv);
   void GetUuids(JSContext* aContext, JS::MutableHandle<JS::Value> aUuids,
                 ErrorResult& aRv);
 
+  /**
+   * Update this adapter's discovery handle in use (mDiscoveryHandleInUse).
+   *
+   * |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] the discovery handle to set.
+   */
+  void SetDiscoveryHandleInUse(BluetoothDiscoveryHandle* aDiscoveryHandle);
+
   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<DOMRequest>
     Pair(const nsAString& aDeviceAddress, ErrorResult& aRv);
@@ -184,16 +196,17 @@ private:
                                  const BluetoothValue& aValue);
   void HandlePropertyChanged(const BluetoothValue& aValue);
   void DispatchAttributeEvent(const nsTArray<nsString>& aTypes);
   BluetoothAdapterAttribute
     ConvertStringToAdapterAttribute(const nsAString& aString);
 
   JS::Heap<JSObject*> mJsUuids;
   JS::Heap<JSObject*> mJsDeviceAddresses;
+  nsRefPtr<BluetoothDiscoveryHandle> mDiscoveryHandleInUse;
   BluetoothAdapterState mState;
   nsString mAddress;
   nsString mName;
   bool mDiscoverable;
   bool mDiscovering;
   bool mPairable;
   bool mPowered;
   uint32_t mPairableTimeout;
--- a/dom/bluetooth2/BluetoothDevice.cpp
+++ b/dom/bluetooth2/BluetoothDevice.cpp
@@ -1,65 +1,90 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "base/basictypes.h"
+#include "BluetoothClassOfDevice.h"
 #include "BluetoothDevice.h"
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "BluetoothUtils.h"
 
-#include "nsDOMClassInfo.h"
-#include "nsTArrayHelpers.h"
+#include "mozilla/dom/bluetooth/BluetoothTypes.h"
+#include "mozilla/dom/BluetoothAttributeEvent.h"
+#include "mozilla/dom/BluetoothDevice2Binding.h"
+#include "mozilla/dom/Promise.h"
 
-#include "mozilla/dom/bluetooth/BluetoothTypes.h"
-#include "mozilla/dom/BluetoothDevice2Binding.h"
-#include "mozilla/dom/ScriptSettings.h"
+using namespace mozilla;
+using namespace mozilla::dom;
 
 USING_BLUETOOTH_NAMESPACE
 
-DOMCI_DATA(BluetoothDevice, BluetoothDevice)
-
-NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothDevice)
-
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(BluetoothDevice,
-                                               DOMEventTargetHelper)
-  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJsUuids)
-  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJsServices)
-NS_IMPL_CYCLE_COLLECTION_TRACE_END
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothDevice,
-                                                  DOMEventTargetHelper)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothDevice,
-                                                DOMEventTargetHelper)
-  tmp->Unroot();
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
+NS_IMPL_CYCLE_COLLECTION_INHERITED(BluetoothDevice, DOMEventTargetHelper, mCod)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothDevice)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(BluetoothDevice, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(BluetoothDevice, DOMEventTargetHelper)
 
+class FetchUuidsTask : public BluetoothReplyRunnable
+{
+public:
+  FetchUuidsTask(Promise* aPromise,
+                 const nsAString& aName,
+                 BluetoothDevice* aDevice)
+    : BluetoothReplyRunnable(nullptr /* DOMRequest */, aPromise, aName)
+    , mDevice(aDevice)
+  {
+    MOZ_ASSERT(aPromise);
+    MOZ_ASSERT(aDevice);
+  }
+
+  bool ParseSuccessfulReply(JS::MutableHandle<JS::Value> aValue)
+  {
+    aValue.setUndefined();
+
+    const BluetoothValue& v = mReply->get_BluetoothReplySuccess().value();
+    NS_ENSURE_TRUE(v.type() == BluetoothValue::TArrayOfnsString, false);
+    const InfallibleTArray<nsString>& uuids = v.get_ArrayOfnsString();
+
+    AutoJSAPI jsapi;
+    NS_ENSURE_TRUE(jsapi.Init(mDevice->GetParentObject()), false);
+
+    JSContext* cx = jsapi.cx();
+    if (!ToJSValue(cx, uuids, aValue)) {
+      BT_WARNING("Cannot create JS array!");
+      JS_ClearPendingException(cx);
+      return false;
+    }
+
+    return true;
+  }
+
+  virtual void ReleaseMembers() MOZ_OVERRIDE
+  {
+    BluetoothReplyRunnable::ReleaseMembers();
+    mDevice = nullptr;
+  }
+
+private:
+  nsRefPtr<BluetoothDevice> mDevice;
+};
+
 BluetoothDevice::BluetoothDevice(nsPIDOMWindow* aWindow,
                                  const BluetoothValue& aValue)
   : DOMEventTargetHelper(aWindow)
-  , mJsUuids(nullptr)
-  , mJsServices(nullptr)
-  , mIsRooted(false)
 {
   MOZ_ASSERT(aWindow);
   MOZ_ASSERT(IsDOMBinding());
 
+  mCod = BluetoothClassOfDevice::Create(aWindow);
+
   const InfallibleTArray<BluetoothNamedValue>& values =
     aValue.get_ArrayOfBluetoothNamedValue();
   for (uint32_t i = 0; i < values.Length(); ++i) {
     SetPropertyByValue(values[i]);
   }
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
@@ -67,104 +92,76 @@ BluetoothDevice::BluetoothDevice(nsPIDOM
 }
 
 BluetoothDevice::~BluetoothDevice()
 {
   BluetoothService* bs = BluetoothService::Get();
   // bs can be null on shutdown, where destruction might happen.
   NS_ENSURE_TRUE_VOID(bs);
   bs->UnregisterBluetoothSignalHandler(mAddress, this);
-  Unroot();
 }
 
 void
 BluetoothDevice::DisconnectFromOwner()
 {
   DOMEventTargetHelper::DisconnectFromOwner();
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
   bs->UnregisterBluetoothSignalHandler(mAddress, this);
 }
 
 void
-BluetoothDevice::Root()
-{
-  if (!mIsRooted) {
-    mozilla::HoldJSObjects(this);
-    mIsRooted = true;
-  }
-}
-
-void
-BluetoothDevice::Unroot()
-{
-  if (mIsRooted) {
-    mJsUuids = nullptr;
-    mJsServices = nullptr;
-    mozilla::DropJSObjects(this);
-    mIsRooted = false;
-  }
-}
-
-void
 BluetoothDevice::SetPropertyByValue(const BluetoothNamedValue& aValue)
 {
   const nsString& name = aValue.name();
   const BluetoothValue& value = aValue.value();
   if (name.EqualsLiteral("Name")) {
     mName = value.get_nsString();
   } else if (name.EqualsLiteral("Address")) {
     mAddress = value.get_nsString();
-  } else if (name.EqualsLiteral("Class")) {
-    mClass = value.get_uint32_t();
-  } else if (name.EqualsLiteral("Icon")) {
-    mIcon = value.get_nsString();
-  } else if (name.EqualsLiteral("Connected")) {
-    mConnected = value.get_bool();
+  } else if (name.EqualsLiteral("Cod")) {
+    mCod->Update(value.get_uint32_t());
   } else if (name.EqualsLiteral("Paired")) {
     mPaired = value.get_bool();
   } 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();
-
-    AutoJSAPI jsapi;
-    if (!jsapi.Init(GetOwner())) {
-      BT_WARNING("Failed to initialise AutoJSAPI!");
-      return;
-    }
-    JSContext* cx = jsapi.cx();
-    JS::Rooted<JSObject*> uuids(cx);
-    if (NS_FAILED(nsTArrayToJSArray(cx, mUuids, &uuids))) {
-      BT_WARNING("Cannot set JS UUIDs object!");
-      return;
-    }
-    mJsUuids = uuids;
-    Root();
-  } else if (name.EqualsLiteral("Services")) {
-    mServices = value.get_ArrayOfnsString();
+    BluetoothDeviceBinding::ClearCachedUuidsValue(this);
+  } else {
+    BT_WARNING("Not handling device property: %s",
+               NS_ConvertUTF16toUTF8(name).get());
+  }
+}
 
-    AutoJSAPI jsapi;
-    if (!jsapi.Init(GetOwner())) {
-      BT_WARNING("Failed to initialise AutoJSAPI!");
-      return;
-    }
-    JSContext* cx = jsapi.cx();
-    JS::Rooted<JSObject*> services(cx);
-    if (NS_FAILED(nsTArrayToJSArray(cx, mServices, &services))) {
-      BT_WARNING("Cannot set JS Services object!");
-      return;
-    }
-    mJsServices = services;
-    Root();
-  } else {
-    nsCString warningMsg;
-    warningMsg.AssignLiteral("Not handling device property: ");
-    warningMsg.Append(NS_ConvertUTF16toUTF8(name));
-    BT_WARNING(warningMsg.get());
+already_AddRefed<Promise>
+BluetoothDevice::FetchUuids(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
   }
+
+  nsRefPtr<Promise> promise = new Promise(global);
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
+
+  nsRefPtr<BluetoothReplyRunnable> result =
+    new FetchUuidsTask(promise,
+                       NS_LITERAL_STRING("FetchUuids"),
+                       this);
+
+  nsresult rv = bs->FetchUuidsInternal(mAddress, result);
+  BT_ENSURE_TRUE_REJECT(NS_SUCCEEDED(rv), NS_ERROR_DOM_OPERATION_ERR);
+
+  return promise.forget();
 }
 
 // static
 already_AddRefed<BluetoothDevice>
 BluetoothDevice::Create(nsPIDOMWindow* aWindow,
                         const BluetoothValue& aValue)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -172,65 +169,121 @@ BluetoothDevice::Create(nsPIDOMWindow* a
 
   nsRefPtr<BluetoothDevice> device = new BluetoothDevice(aWindow, aValue);
   return device.forget();
 }
 
 void
 BluetoothDevice::Notify(const BluetoothSignal& aData)
 {
-  BT_LOGD("[D] %s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(aData.name()).get());
+  BT_LOGD("[D] %s", NS_ConvertUTF16toUTF8(aData.name()).get());
 
   BluetoothValue v = aData.value();
   if (aData.name().EqualsLiteral("PropertyChanged")) {
-    MOZ_ASSERT(v.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+    HandlePropertyChanged(v);
+  } else {
+    BT_WARNING("Not handling device signal: %s",
+               NS_ConvertUTF16toUTF8(aData.name()).get());
+  }
+}
 
-    const InfallibleTArray<BluetoothNamedValue>& arr =
-      v.get_ArrayOfBluetoothNamedValue();
+BluetoothDeviceAttribute
+BluetoothDevice::ConvertStringToDeviceAttribute(const nsAString& aString)
+{
+  using namespace
+    mozilla::dom::BluetoothDeviceAttributeValues;
 
-    for (uint32_t i = 0, propCount = arr.Length(); i < propCount; ++i) {
-      SetPropertyByValue(arr[i]);
+  for (size_t index = 0; index < ArrayLength(strings) - 1; index++) {
+    if (aString.LowerCaseEqualsASCII(strings[index].value,
+                                     strings[index].length)) {
+      return static_cast<BluetoothDeviceAttribute>(index);
     }
-  } else {
-#ifdef DEBUG
-    nsCString warningMsg;
-    warningMsg.AssignLiteral("Not handling device signal: ");
-    warningMsg.Append(NS_ConvertUTF16toUTF8(aData.name()));
-    BT_WARNING(warningMsg.get());
-#endif
+  }
+
+  return BluetoothDeviceAttribute::Unknown;
+}
+
+bool
+BluetoothDevice::IsDeviceAttributeChanged(BluetoothDeviceAttribute aType,
+                                          const BluetoothValue& aValue)
+{
+  switch (aType) {
+    case BluetoothDeviceAttribute::Cod:
+      MOZ_ASSERT(aValue.type() == BluetoothValue::Tuint32_t);
+      return !mCod->Equals(aValue.get_uint32_t());
+    case BluetoothDeviceAttribute::Name:
+      MOZ_ASSERT(aValue.type() == BluetoothValue::TnsString);
+      return !mName.Equals(aValue.get_nsString());
+    case BluetoothDeviceAttribute::Paired:
+      MOZ_ASSERT(aValue.type() == BluetoothValue::Tbool);
+      return mPaired != aValue.get_bool();
+    case BluetoothDeviceAttribute::Uuids: {
+      MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfnsString);
+      const InfallibleTArray<nsString>& uuids = aValue.get_ArrayOfnsString();
+      // We assume the received uuids array is sorted without duplicate items.
+      // If it's not, we require additional processing before comparing it
+      // directly.
+      return mUuids != uuids;
+    }
+    default:
+      BT_WARNING("Type %d is not handled", uint32_t(aType));
+      return false;
   }
 }
 
 void
-BluetoothDevice::GetUuids(JSContext* aContext,
-                           JS::MutableHandle<JS::Value> aUuids,
-                           ErrorResult& aRv)
+BluetoothDevice::HandlePropertyChanged(const BluetoothValue& aValue)
 {
-  if (!mJsUuids) {
-    BT_WARNING("UUIDs not yet set!");
-    aRv.Throw(NS_ERROR_FAILURE);
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+
+  const InfallibleTArray<BluetoothNamedValue>& arr =
+    aValue.get_ArrayOfBluetoothNamedValue();
+
+  nsTArray<nsString> types;
+  for (uint32_t i = 0, propCount = arr.Length(); i < propCount; ++i) {
+    BluetoothDeviceAttribute type =
+      ConvertStringToDeviceAttribute(arr[i].name());
+
+    // Non-BluetoothDeviceAttribute properties
+    if (type == BluetoothDeviceAttribute::Unknown) {
+      SetPropertyByValue(arr[i]);
+      continue;
+    }
+
+    // BluetoothDeviceAttribute properties
+    if (IsDeviceAttributeChanged(type, arr[i].value())) {
+      SetPropertyByValue(arr[i]);
+      BT_APPEND_ENUM_STRING(types, BluetoothDeviceAttribute, type);
+    }
+  }
+
+  DispatchAttributeEvent(types);
+}
+
+void
+BluetoothDevice::DispatchAttributeEvent(const nsTArray<nsString>& aTypes)
+{
+  NS_ENSURE_TRUE_VOID(aTypes.Length());
+
+  AutoJSAPI jsapi;
+  NS_ENSURE_TRUE_VOID(jsapi.Init(GetOwner()));
+  JSContext* cx = jsapi.cx();
+  JS::Rooted<JS::Value> value(cx);
+
+  if (!ToJSValue(cx, aTypes, &value)) {
+    JS_ClearPendingException(cx);
     return;
   }
 
-  JS::ExposeObjectToActiveJS(mJsUuids);
-  aUuids.setObject(*mJsUuids);
-}
-
-void
-BluetoothDevice::GetServices(JSContext* aCx,
-                             JS::MutableHandle<JS::Value> aServices,
-                             ErrorResult& aRv)
-{
-  if (!mJsServices) {
-    BT_WARNING("Services not yet set!");
-    aRv.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
-  JS::ExposeObjectToActiveJS(mJsServices);
-  aServices.setObject(*mJsServices);
+  RootedDictionary<BluetoothAttributeEventInit> init(cx);
+  init.mAttrs = value;
+  nsRefPtr<BluetoothAttributeEvent> event =
+    BluetoothAttributeEvent::Constructor(this,
+                                         NS_LITERAL_STRING("attributechanged"),
+                                         init);
+  DispatchTrustedEvent(event);
 }
 
 JSObject*
 BluetoothDevice::WrapObject(JSContext* aContext)
 {
   return BluetoothDeviceBinding::Wrap(aContext, this);
 }
--- a/dom/bluetooth2/BluetoothDevice.h
+++ b/dom/bluetooth2/BluetoothDevice.h
@@ -4,110 +4,105 @@
  * 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_bluetoothdevice_h__
 #define mozilla_dom_bluetooth_bluetoothdevice_h__
 
 #include "mozilla/Attributes.h"
 #include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/BluetoothDevice2Binding.h"
 #include "BluetoothCommon.h"
 #include "nsString.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace dom {
+  class Promise;
+}
+}
 
 BEGIN_BLUETOOTH_NAMESPACE
 
+class BluetoothClassOfDevice;
 class BluetoothNamedValue;
 class BluetoothValue;
 class BluetoothSignal;
 class BluetoothSocket;
 
-class BluetoothDevice : public DOMEventTargetHelper
-                      , public BluetoothSignalObserver
+class BluetoothDevice MOZ_FINAL : public DOMEventTargetHelper
+                                , public BluetoothSignalObserver
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(BluetoothDevice,
-                                                         DOMEventTargetHelper)
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothDevice,
+                                           DOMEventTargetHelper)
 
   static already_AddRefed<BluetoothDevice>
   Create(nsPIDOMWindow* aOwner, const BluetoothValue& aValue);
 
   void Notify(const BluetoothSignal& aParam);
 
   void GetAddress(nsString& aAddress) const
   {
     aAddress = mAddress;
   }
 
+  BluetoothClassOfDevice* Cod() const
+  {
+    return mCod;
+  }
+
   void GetName(nsString& aName) const
   {
     aName = mName;
   }
 
-  void GetIcon(nsString& aIcon) const
-  {
-    aIcon = mIcon;
-  }
-
-  uint32_t Class() const
-  {
-    return mClass;
-  }
-
   bool Paired() const
   {
     return mPaired;
   }
 
-  bool Connected() const
-  {
-    return mConnected;
+  void GetUuids(nsTArray<nsString>& aUuids) {
+    aUuids = mUuids;
   }
 
-  void GetUuids(JSContext* aContext, JS::MutableHandle<JS::Value> aUuids,
-                ErrorResult& aRv);
-  void GetServices(JSContext* aContext, JS::MutableHandle<JS::Value> aServices,
-                   ErrorResult& aRv);
-
-  nsISupports*
-  ToISupports()
-  {
-    return static_cast<EventTarget*>(this);
-  }
+  already_AddRefed<Promise> FetchUuids(ErrorResult& aRv);
 
   void SetPropertyByValue(const BluetoothNamedValue& aValue);
 
-  void Unroot();
+  BluetoothDeviceAttribute
+  ConvertStringToDeviceAttribute(const nsAString& aString);
+
+  bool
+  IsDeviceAttributeChanged(BluetoothDeviceAttribute aType,
+                           const BluetoothValue& aValue);
+
+  void HandlePropertyChanged(const BluetoothValue& aValue);
+
+  void DispatchAttributeEvent(const nsTArray<nsString>& aTypes);
+
+  IMPL_EVENT_HANDLER(attributechanged);
 
   nsPIDOMWindow* GetParentObject() const
   {
      return GetOwner();
   }
 
-  virtual JSObject*
-    WrapObject(JSContext* aCx) MOZ_OVERRIDE;
-
+  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
   virtual void DisconnectFromOwner() MOZ_OVERRIDE;
 
 private:
   BluetoothDevice(nsPIDOMWindow* aOwner, const BluetoothValue& aValue);
   ~BluetoothDevice();
-  void Root();
-
-  JS::Heap<JSObject*> mJsUuids;
-  JS::Heap<JSObject*> mJsServices;
 
   nsString mAddress;
+  nsRefPtr<BluetoothClassOfDevice> mCod;
   nsString mName;
-  nsString mIcon;
-  uint32_t mClass;
-  bool mConnected;
   bool mPaired;
-  bool mIsRooted;
   nsTArray<nsString> mUuids;
-  nsTArray<nsString> mServices;
 
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth2/BluetoothDiscoveryHandle.cpp
+++ b/dom/bluetooth2/BluetoothDiscoveryHandle.cpp
@@ -86,13 +86,20 @@ BluetoothDiscoveryHandle::Notify(const B
   if (aData.name().EqualsLiteral("DeviceFound")) {
     DispatchDeviceEvent(aData.value());
   } else {
     BT_WARNING("Not handling discovery handle signal: %s",
                NS_ConvertUTF16toUTF8(aData.name()).get());
   }
 }
 
+void
+BluetoothDiscoveryHandle::DisconnectFromOwner()
+{
+  DOMEventTargetHelper::DisconnectFromOwner();
+  ListenToBluetoothSignal(false);
+}
+
 JSObject*
 BluetoothDiscoveryHandle::WrapObject(JSContext* aCx)
 {
   return BluetoothDiscoveryHandleBinding::Wrap(aCx, this);
 }
--- a/dom/bluetooth2/BluetoothDiscoveryHandle.h
+++ b/dom/bluetooth2/BluetoothDiscoveryHandle.h
@@ -25,16 +25,18 @@ public:
   NS_DECL_ISUPPORTS_INHERITED
 
   static already_AddRefed<BluetoothDiscoveryHandle>
     Create(nsPIDOMWindow* aWindow);
 
   IMPL_EVENT_HANDLER(devicefound);
 
   void Notify(const BluetoothSignal& aData); // BluetoothSignalObserver
+
+  virtual void DisconnectFromOwner() MOZ_OVERRIDE;
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
 private:
   BluetoothDiscoveryHandle(nsPIDOMWindow* aWindow);
   ~BluetoothDiscoveryHandle();
 
   /**
    * Start/Stop listening to bluetooth signal.
--- a/dom/bluetooth2/BluetoothService.h
+++ b/dom/bluetooth2/BluetoothService.h
@@ -147,16 +147,26 @@ public:
    *
    * @return NS_OK on success, NS_ERROR_FAILURE otherwise
    */
   virtual nsresult
   GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid,
                                        BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
+   * Returns up-to-date uuids of given device address,
+   * implemented via a platform specific methood.
+   *
+   * @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
   StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
--- a/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp
@@ -58,16 +58,17 @@ static InfallibleTArray<nsString> sAdapt
 static BluetoothInterface* sBtInterface;
 static nsTArray<nsRefPtr<BluetoothProfileController> > sControllerArray;
 static InfallibleTArray<BluetoothNamedValue> sRemoteDevicesPack;
 static nsTArray<int> sRequestedDeviceCountArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sChangeAdapterStateRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sChangeDiscoveryRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sSetPropertyRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sGetDeviceRunnableArray;
+static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sFetchUuidsRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sBondingRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sUnbondingRunnableArray;
 
 // Static variables below should only be used on *callback thread*
 
 
 // Atomic static variables
 static Atomic<bool> sAdapterDiscoverable(false);
@@ -118,20 +119,21 @@ public:
 
   NS_IMETHOD
   Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     // Bluetooth just enabled, clear profile controllers and runnable arrays.
     sControllerArray.Clear();
+    sChangeDiscoveryRunnableArray.Clear();
+    sSetPropertyRunnableArray.Clear();
+    sGetDeviceRunnableArray.Clear();
+    sFetchUuidsRunnableArray.Clear();
     sBondingRunnableArray.Clear();
-    sChangeDiscoveryRunnableArray.Clear();
-    sGetDeviceRunnableArray.Clear();
-    sSetPropertyRunnableArray.Clear();
     sUnbondingRunnableArray.Clear();
 
     // Bluetooth scan mode is NONE by default
     bt_scan_mode_t mode = BT_SCAN_MODE_CONNECTABLE;
     bt_property_t prop;
     prop.type = BT_PROPERTY_ADAPTER_SCAN_MODE;
     prop.val = (void*)&mode;
     prop.len = sizeof(mode);
@@ -189,107 +191,16 @@ public:
 
     return NS_OK;
   }
 };
 
 /**
  *  Static callback functions
  */
-static void
-ClassToIcon(uint32_t aClass, nsAString& aRetIcon)
-{
-  switch ((aClass & 0x1f00) >> 8) {
-    case 0x01:
-      aRetIcon.AssignLiteral("computer");
-      break;
-    case 0x02:
-      switch ((aClass & 0xfc) >> 2) {
-        case 0x01:
-        case 0x02:
-        case 0x03:
-        case 0x05:
-          aRetIcon.AssignLiteral("phone");
-          break;
-        case 0x04:
-          aRetIcon.AssignLiteral("modem");
-          break;
-      }
-      break;
-    case 0x03:
-      aRetIcon.AssignLiteral("network-wireless");
-      break;
-    case 0x04:
-      switch ((aClass & 0xfc) >> 2) {
-        case 0x01:
-        case 0x02:
-        case 0x06:
-          aRetIcon.AssignLiteral("audio-card");
-          break;
-        case 0x0b:
-        case 0x0c:
-        case 0x0d:
-          aRetIcon.AssignLiteral("camera-video");
-          break;
-        default:
-          aRetIcon.AssignLiteral("audio-card");
-          break;
-      }
-      break;
-    case 0x05:
-      switch ((aClass & 0xc0) >> 6) {
-        case 0x00:
-          switch ((aClass && 0x1e) >> 2) {
-            case 0x01:
-            case 0x02:
-              aRetIcon.AssignLiteral("input-gaming");
-              break;
-          }
-          break;
-        case 0x01:
-          aRetIcon.AssignLiteral("input-keyboard");
-          break;
-        case 0x02:
-          switch ((aClass && 0x1e) >> 2) {
-            case 0x05:
-              aRetIcon.AssignLiteral("input-tablet");
-              break;
-            default:
-              aRetIcon.AssignLiteral("input-mouse");
-              break;
-          }
-      }
-      break;
-    case 0x06:
-      if (aClass & 0x80) {
-        aRetIcon.AssignLiteral("printer");
-        break;
-      }
-      if (aClass & 0x20) {
-        aRetIcon.AssignLiteral("camera-photo");
-        break;
-      }
-      break;
-  }
-
-  if (aRetIcon.IsEmpty()) {
-    if (HAS_AUDIO(aClass)) {
-      /**
-       * Property 'Icon' may be missed due to CoD of major class is TOY(0x08).
-       * But we need to assign Icon as audio-card if service class is 'Audio'.
-       * This is for PTS test case TC_AG_COD_BV_02_I. As HFP specification
-       * defines that service class is 'Audio' can be considered as HFP HF.
-       */
-      aRetIcon.AssignLiteral("audio-card");
-    } else {
-      BT_LOGR("No icon to match class: %x", aClass);
-    }
-  }
-}
-
 static ControlPlayStatus
 PlayStatusStringToControlPlayStatus(const nsAString& aPlayStatus)
 {
   ControlPlayStatus playStatus = ControlPlayStatus::PLAYSTATUS_UNKNOWN;
   if (aPlayStatus.EqualsLiteral("STOPPED")) {
     playStatus = ControlPlayStatus::PLAYSTATUS_STOPPED;
   } else if (aPlayStatus.EqualsLiteral("PLAYING")) {
     playStatus = ControlPlayStatus::PLAYSTATUS_PLAYING;
@@ -471,32 +382,45 @@ public:
   , mRemoteDeviceBdAddress(aRemoteDeviceBdAddress)
   { }
 
   NS_IMETHOD
   Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
-    if (sRequestedDeviceCountArray.IsEmpty()) {
-      // This is possible because the callback would be called after turning
-      // Bluetooth on.
-      return NS_OK;
-    }
 
     // Update to registered BluetoothDevice objects
     BluetoothSignal signal(NS_LITERAL_STRING("PropertyChanged"),
                            mRemoteDeviceBdAddress, mProps);
     nsRefPtr<DistributeBluetoothSignalTask>
       t = new DistributeBluetoothSignalTask(signal);
     if (NS_FAILED(NS_DispatchToMainThread(t))) {
       BT_WARNING("Failed to dispatch to main thread!");
       return NS_OK;
     }
 
+    // FetchUuids task
+    if (!sFetchUuidsRunnableArray.IsEmpty()) {
+      // mProps contains Address and Uuids only
+      DispatchBluetoothReply(sFetchUuidsRunnableArray[0],
+                             mProps[1].value() /* Uuids */,
+                             EmptyString());
+      sFetchUuidsRunnableArray.RemoveElementAt(0);
+
+      return NS_OK;
+    }
+
+    // GetDevices task
+    if (sRequestedDeviceCountArray.IsEmpty()) {
+      // This is possible because the callback would be called after turning
+      // Bluetooth on.
+      return NS_OK;
+    }
+
     // Use address as the index
     sRemoteDevicesPack.AppendElement(
       BluetoothNamedValue(mRemoteDeviceBdAddress, mProps));
 
     if (--sRequestedDeviceCountArray[0] == 0) {
       if (!sGetDeviceRunnableArray.IsEmpty()) {
         DispatchBluetoothReply(sGetDeviceRunnableArray[0],
                                sRemoteDevicesPack, EmptyString());
@@ -510,16 +434,17 @@ public:
     return NS_OK;
   }
 };
 
 /**
  * RemoteDevicePropertiesCallback will be called, as the following conditions:
  * 1. When BT is turning on, bluedroid automatically execute this callback
  * 2. When get_remote_device_properties()
+ * 3. When get_remote_services()
  */
 static void
 RemoteDevicePropertiesCallback(bt_status_t aStatus, bt_bdaddr_t *aBdAddress,
                                int aNumProperties, bt_property_t *aProperties)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
   InfallibleTArray<BluetoothNamedValue> props;
@@ -531,21 +456,32 @@ RemoteDevicePropertiesCallback(bt_status
   for (int i = 0; i < aNumProperties; ++i) {
     bt_property_t p = aProperties[i];
 
     if (p.type == BT_PROPERTY_BDNAME) {
       BluetoothValue propertyValue = NS_ConvertUTF8toUTF16((char*)p.val);
       BT_APPEND_NAMED_VALUE(props, "Name", propertyValue);
     } else if (p.type == BT_PROPERTY_CLASS_OF_DEVICE) {
       uint32_t cod = *(uint32_t*)p.val;
-      BT_APPEND_NAMED_VALUE(props, "Class", cod);
+      BT_APPEND_NAMED_VALUE(props, "Cod", cod);
+    } else if (p.type == BT_PROPERTY_UUIDS) {
+      nsTArray<nsString> uuids;
 
-      nsString icon;
-      ClassToIcon(cod, icon);
-      BT_APPEND_NAMED_VALUE(props, "Icon", icon);
+      // Construct a sorted uuid set
+      for (uint32_t j = 0; j < p.len / sizeof(bt_uuid_t); j++) {
+        nsAutoString uuid;
+        bt_uuid_t* pUuid = (bt_uuid_t*)p.val + j;
+        UuidToString(pUuid, uuid);
+
+        if (!uuids.Contains(uuid)) { // filter out duplicate uuids
+          uuids.InsertElementSorted(uuid);
+        }
+      }
+
+      BT_APPEND_NAMED_VALUE(props, "UUIDs", uuids);
     } else {
       BT_LOGD("Other non-handled device properties. Type: %d", p.type);
     }
   }
 
   // Redirect to main thread to avoid racing problem
   NS_DispatchToMainThread(
     new RemoteDevicePropertiesCallbackTask(props, remoteDeviceBdAddress));
@@ -569,22 +505,17 @@ DeviceFoundCallback(int aNumProperties, 
 
       BT_APPEND_NAMED_VALUE(propertiesArray, "Address", propertyValue);
     } else if (p.type == BT_PROPERTY_BDNAME) {
       propertyValue = NS_ConvertUTF8toUTF16((char*)p.val);
       BT_APPEND_NAMED_VALUE(propertiesArray, "Name", propertyValue);
     } else if (p.type == BT_PROPERTY_CLASS_OF_DEVICE) {
       uint32_t cod = *(uint32_t*)p.val;
       propertyValue = cod;
-      BT_APPEND_NAMED_VALUE(propertiesArray, "Class", propertyValue);
-
-      nsString icon;
-      ClassToIcon(cod, icon);
-      propertyValue = icon;
-      BT_APPEND_NAMED_VALUE(propertiesArray, "Icon", propertyValue);
+      BT_APPEND_NAMED_VALUE(propertiesArray, "Cod", cod);
     } else {
       BT_LOGD("Not handled remote device property: %d", p.type);
     }
   }
 
   BluetoothValue value = propertiesArray;
   BluetoothSignal signal(NS_LITERAL_STRING("DeviceFound"),
                          NS_LITERAL_STRING(KEY_DISCOVERY_HANDLE), value);
@@ -1246,16 +1177,61 @@ BluetoothServiceBluedroid::StopDiscovery
   ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
 
   sChangeDiscoveryRunnableArray.AppendElement(aRunnable);
   sBtInterface->CancelDiscovery(new CancelDiscoveryResultHandler(aRunnable));
 
   return NS_OK;
 }
 
+class GetRemoteServicesResultHandler MOZ_FINAL : public BluetoothResultHandler
+{
+public:
+  GetRemoteServicesResultHandler(BluetoothReplyRunnable* aRunnable)
+  : mRunnable(aRunnable)
+  { }
+
+  void OnError(int aStatus) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    sFetchUuidsRunnableArray.RemoveElement(mRunnable);
+    ReplyStatusError(mRunnable, aStatus, NS_LITERAL_STRING("FetchUuids"));
+  }
+
+private:
+  BluetoothReplyRunnable* mRunnable;
+};
+
+nsresult
+BluetoothServiceBluedroid::FetchUuidsInternal(
+  const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
+
+  /*
+   * get_remote_services request will not be performed by bluedroid
+   * if it is currently discovering nearby remote devices.
+   */
+  if (sAdapterDiscovering) {
+    sBtInterface->CancelDiscovery(new CancelDiscoveryResultHandler(aRunnable));
+  }
+
+  bt_bdaddr_t addressType;
+  StringToBdAddressType(aDeviceAddress, &addressType);
+
+  sFetchUuidsRunnableArray.AppendElement(aRunnable);
+
+  sBtInterface->GetRemoteServices(&addressType,
+    new GetRemoteServicesResultHandler(aRunnable));
+
+  return NS_OK;
+}
+
 class SetAdapterPropertyResultHandler MOZ_FINAL : public BluetoothResultHandler
 {
 public:
   SetAdapterPropertyResultHandler(BluetoothReplyRunnable* aRunnable)
   : mRunnable(aRunnable)
   { }
 
   void OnError(int aStatus) MOZ_OVERRIDE
@@ -1761,9 +1737,8 @@ void
 BluetoothServiceBluedroid::IgnoreWaitingCall(BluetoothReplyRunnable* aRunnable)
 {
 }
 
 void
 BluetoothServiceBluedroid::ToggleCalls(BluetoothReplyRunnable* aRunnable)
 {
 }
-
--- a/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.h
+++ b/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.h
@@ -31,16 +31,20 @@ public:
   virtual nsresult
   GetConnectedDevicePropertiesInternal(uint16_t aProfileId,
                                        BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   GetPairedDevicePropertiesInternal(const nsTArray<nsString>& aDeviceAddress,
                                     BluetoothReplyRunnable* aRunnable);
 
+  virtual nsresult
+  FetchUuidsInternal(const nsAString& aDeviceAddress,
+                     BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
   virtual nsresult StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
   virtual nsresult StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
               BluetoothReplyRunnable* aRunnable);
 
--- a/dom/bluetooth2/bluedroid/BluetoothUtils.cpp
+++ b/dom/bluetooth2/bluedroid/BluetoothUtils.cpp
@@ -44,16 +44,39 @@ BdAddressTypeToString(bt_bdaddr_t* aBdAd
 
   sprintf(bdstr, "%02x:%02x:%02x:%02x:%02x:%02x",
           (int)addr[0],(int)addr[1],(int)addr[2],
           (int)addr[3],(int)addr[4],(int)addr[5]);
 
   aRetBdAddress = NS_ConvertUTF8toUTF16(bdstr);
 }
 
+void
+UuidToString(bt_uuid_t* aUuid, nsAString& aString) {
+  char uuidStr[37];
+
+  uint32_t uuid0, uuid4;
+  uint16_t uuid1, uuid2, uuid3, uuid5;
+
+  memcpy(&uuid0, &(aUuid->uu[0]), sizeof(uint32_t));
+  memcpy(&uuid1, &(aUuid->uu[4]), sizeof(uint16_t));
+  memcpy(&uuid2, &(aUuid->uu[6]), sizeof(uint16_t));
+  memcpy(&uuid3, &(aUuid->uu[8]), sizeof(uint16_t));
+  memcpy(&uuid4, &(aUuid->uu[10]), sizeof(uint32_t));
+  memcpy(&uuid5, &(aUuid->uu[14]), sizeof(uint16_t));
+
+  sprintf(uuidStr, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x",
+          ntohl(uuid0), ntohs(uuid1),
+          ntohs(uuid2), ntohs(uuid3),
+          ntohl(uuid4), ntohs(uuid5));
+
+  aString.Truncate();
+  aString.AssignLiteral(uuidStr);
+}
+
 bool
 SetJsObject(JSContext* aContext,
             const BluetoothValue& aValue,
             JS::Handle<JSObject*> aObj)
 {
   MOZ_ASSERT(aContext && aObj);
 
   if (aValue.type() != BluetoothValue::TArrayOfBluetoothNamedValue) {
--- a/dom/bluetooth2/bluedroid/BluetoothUtils.h
+++ b/dom/bluetooth2/bluedroid/BluetoothUtils.h
@@ -21,16 +21,19 @@ class BluetoothReplyRunnable;
 void
 StringToBdAddressType(const nsAString& aBdAddress,
                       bt_bdaddr_t *aRetBdAddressType);
 
 void
 BdAddressTypeToString(bt_bdaddr_t* aBdAddressType,
                       nsAString& aRetBdAddress);
 
+void
+UuidToString(bt_uuid_t* aUuid, nsAString& aString);
+
 bool
 SetJsObject(JSContext* aContext,
             const BluetoothValue& aValue,
             JS::Handle<JSObject*> aObj);
 
 bool
 BroadcastSystemMessage(const nsAString& aType,
                        const BluetoothValue& aData);
--- a/dom/bluetooth2/bluez/BluetoothDBusService.cpp
+++ b/dom/bluetooth2/bluez/BluetoothDBusService.cpp
@@ -2809,16 +2809,23 @@ BluetoothDBusService::GetPairedDevicePro
                                                      GetPairedDevicesFilter,
                                                      aRunnable);
   Task* task = new ProcessRemainingDeviceAddressesTask(handler, aRunnable);
   DispatchToDBusThread(task);
 
   return NS_OK;
 }
 
+nsresult
+FetchUuidsInternal(const nsAString& aDeviceAddress,
+                   BluetoothReplyRunnable* aRunnable)
+{
+  return NS_OK;
+}
+
 class SetPropertyTask : public Task
 {
 public:
   SetPropertyTask(BluetoothObjectType aType,
                   const nsACString& aName,
                   BluetoothReplyRunnable* aRunnable)
     : mType(aType)
     , mName(aName)
--- a/dom/bluetooth2/bluez/BluetoothDBusService.h
+++ b/dom/bluetooth2/bluez/BluetoothDBusService.h
@@ -57,16 +57,20 @@ public:
   virtual nsresult
   GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid,
                                        BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual nsresult
   GetPairedDevicePropertiesInternal(const nsTArray<nsString>& aDeviceAddresses,
                                     BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
+  virtual nsresult
+  FetchUuidsInternal(const nsAString& aDeviceAddress,
+                     BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
   virtual nsresult StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual nsresult StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
               BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
--- a/dom/bluetooth2/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth2/ipc/BluetoothParent.cpp
@@ -205,16 +205,18 @@ BluetoothParent::RecvPBluetoothRequestCo
     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());
+    case Request::TFetchUuidsRequest:
+      return actor->DoRequest(aRequest.get_FetchUuidsRequest());
     case Request::TSetPinCodeRequest:
       return actor->DoRequest(aRequest.get_SetPinCodeRequest());
     case Request::TSetPasskeyRequest:
       return actor->DoRequest(aRequest.get_SetPasskeyRequest());
     case Request::TConfirmPairingConfirmationRequest:
       return actor->DoRequest(aRequest.get_ConfirmPairingConfirmationRequest());
     case Request::TDenyPairingConfirmationRequest:
       return actor->DoRequest(aRequest.get_DenyPairingConfirmationRequest());
@@ -427,29 +429,43 @@ BluetoothRequestParent::DoRequest(const 
   nsresult rv =
     mService->GetPairedDevicePropertiesInternal(aRequest.addresses(),
                                                 mReplyRunnable.get());
   NS_ENSURE_SUCCESS(rv, false);
   return true;
 }
 
 bool
-BluetoothRequestParent::DoRequest(const ConnectedDevicePropertiesRequest& aRequest)
+BluetoothRequestParent::DoRequest(
+    const ConnectedDevicePropertiesRequest& aRequest)
 {
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TConnectedDevicePropertiesRequest);
   nsresult rv =
     mService->GetConnectedDevicePropertiesInternal(aRequest.serviceUuid(),
                                                    mReplyRunnable.get());
   NS_ENSURE_SUCCESS(rv, false);
 
   return true;
 }
 
 bool
+BluetoothRequestParent::DoRequest(const FetchUuidsRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TFetchUuidsRequest);
+  nsresult rv =
+    mService->FetchUuidsInternal(aRequest.address(), mReplyRunnable.get());
+
+  NS_ENSURE_SUCCESS(rv, false);
+
+  return true;
+}
+
+bool
 BluetoothRequestParent::DoRequest(const SetPinCodeRequest& aRequest)
 {
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TSetPinCodeRequest);
 
   bool result =
     mService->SetPinCodeInternal(aRequest.path(),
                                  aRequest.pincode(),
--- a/dom/bluetooth2/ipc/BluetoothParent.h
+++ b/dom/bluetooth2/ipc/BluetoothParent.h
@@ -149,20 +149,24 @@ protected:
   bool
   DoRequest(const PairRequest& aRequest);
 
   bool
   DoRequest(const UnpairRequest& aRequest);
 
   bool
   DoRequest(const PairedDevicePropertiesRequest& aRequest);
+
   bool
   DoRequest(const ConnectedDevicePropertiesRequest& aRequest);
 
   bool
+  DoRequest(const FetchUuidsRequest& aRequest);
+
+  bool
   DoRequest(const SetPinCodeRequest& aRequest);
 
   bool
   DoRequest(const SetPasskeyRequest& aRequest);
 
   bool
   DoRequest(const ConfirmPairingConfirmationRequest& aRequest);
 
--- a/dom/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
@@ -120,29 +120,38 @@ BluetoothServiceChildProcess::StopIntern
 nsresult
 BluetoothServiceChildProcess::GetConnectedDevicePropertiesInternal(
                                               uint16_t aServiceUuid,
                                               BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, ConnectedDevicePropertiesRequest(aServiceUuid));
   return NS_OK;
 }
+
 nsresult
 BluetoothServiceChildProcess::GetPairedDevicePropertiesInternal(
                                      const nsTArray<nsString>& aDeviceAddresses,
                                      BluetoothReplyRunnable* aRunnable)
 {
   PairedDevicePropertiesRequest request;
   request.addresses().AppendElements(aDeviceAddresses);
 
   SendRequest(aRunnable, request);
   return NS_OK;
 }
 
 nsresult
+BluetoothServiceChildProcess::FetchUuidsInternal(
+    const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable, FetchUuidsRequest(nsString(aDeviceAddress)));
+  return NS_OK;
+}
+
+nsresult
 BluetoothServiceChildProcess::StopDiscoveryInternal(
                                               BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, StopDiscoveryRequest());
   return NS_OK;
 }
 
 nsresult
--- a/dom/bluetooth2/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth2/ipc/BluetoothServiceChildProcess.h
@@ -57,16 +57,20 @@ public:
                                     BluetoothReplyRunnable* aRunnable)
                                     MOZ_OVERRIDE;
 
   virtual nsresult
   GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid,
                                        BluetoothReplyRunnable* aRunnable)
                                        MOZ_OVERRIDE;
   virtual nsresult
+  FetchUuidsInternal(const nsAString& aDeviceAddress,
+                     BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual nsresult
   StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual nsresult
   StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
--- a/dom/bluetooth2/ipc/PBluetooth.ipdl
+++ b/dom/bluetooth2/ipc/PBluetooth.ipdl
@@ -91,16 +91,21 @@ struct PairedDevicePropertiesRequest
   nsString[] addresses;
 };
 
 struct ConnectedDevicePropertiesRequest
 {
   uint16_t serviceUuid;
 };
 
+struct FetchUuidsRequest
+{
+  nsString address;
+};
+
 struct ConnectRequest
 {
   nsString address;
   uint32_t cod;
   uint16_t serviceUuid;
 };
 
 struct DisconnectRequest
@@ -183,16 +188,17 @@ union Request
   PairRequest;
   UnpairRequest;
   SetPinCodeRequest;
   SetPasskeyRequest;
   ConfirmPairingConfirmationRequest;
   DenyPairingConfirmationRequest;
   ConnectedDevicePropertiesRequest;
   PairedDevicePropertiesRequest;
+  FetchUuidsRequest;
   ConnectRequest;
   DisconnectRequest;
   SendFileRequest;
   StopSendingFileRequest;
   ConfirmReceivingFileRequest;
   DenyReceivingFileRequest;
   ConnectScoRequest;
   DisconnectScoRequest;
--- a/dom/camera/CameraPreferences.cpp
+++ b/dom/camera/CameraPreferences.cpp
@@ -17,41 +17,71 @@ static StaticAutoPtr<Monitor> sPrefMonit
 
 StaticAutoPtr<nsCString> CameraPreferences::sPrefTestEnabled;
 StaticAutoPtr<nsCString> CameraPreferences::sPrefHardwareTest;
 StaticAutoPtr<nsCString> CameraPreferences::sPrefGonkParameters;
 
 nsresult CameraPreferences::sPrefCameraControlMethodErrorOverride = NS_OK;
 nsresult CameraPreferences::sPrefCameraControlAsyncErrorOverride = NS_OK;
 
+uint32_t CameraPreferences::sPrefCameraControlLowMemoryThresholdMB = 0;
+
+bool CameraPreferences::sPrefCameraParametersIsLowMemory = false;
+
+#ifdef CAMERAPREFERENCES_HAVE_SEPARATE_UINT32_AND_NSRESULT
 /* static */
 nsresult
 CameraPreferences::UpdatePref(const char* aPref, nsresult& aVal)
 {
   uint32_t val;
   nsresult rv = Preferences::GetUint(aPref, &val);
   if (NS_SUCCEEDED(rv)) {
     aVal = static_cast<nsresult>(val);
   }
   return rv;
 }
+#endif
+
+/* static */
+nsresult
+CameraPreferences::UpdatePref(const char* aPref, uint32_t& aVal)
+{
+  uint32_t val;
+  nsresult rv = Preferences::GetUint(aPref, &val);
+  if (NS_SUCCEEDED(rv)) {
+    aVal = val;
+  }
+  return rv;
+}
 
 /* static */
 nsresult
 CameraPreferences::UpdatePref(const char* aPref, nsACString& aVal)
 {
   nsCString val;
   nsresult rv = Preferences::GetCString(aPref, &val);
   if (NS_SUCCEEDED(rv)) {
     aVal = val;
   }
   return rv;
 }
 
 /* static */
+nsresult
+CameraPreferences::UpdatePref(const char* aPref, bool& aVal)
+{
+  bool val;
+  nsresult rv = Preferences::GetBool(aPref, &val);
+  if (NS_SUCCEEDED(rv)) {
+    aVal = val;
+  }
+  return rv;
+}
+
+/* static */
 CameraPreferences::Pref CameraPreferences::sPrefs[] = {
   {
     "camera.control.test.enabled",
     kPrefValueIsCString,
     { &sPrefTestEnabled }
   },
   {
     "camera.control.test.hardware",
@@ -62,24 +92,34 @@ CameraPreferences::Pref CameraPreference
   {
     "camera.control.test.hardware.gonk.parameters",
     kPrefValueIsCString,
     { &sPrefGonkParameters }
   },
 #endif
   {
     "camera.control.test.method.error",
-    kPrefValueIsNSResult,
+    kPrefValueIsNsResult,
     { &sPrefCameraControlMethodErrorOverride }
   },
   {
     "camera.control.test.async.error",
-    kPrefValueIsNSResult,
+    kPrefValueIsNsResult,
     { &sPrefCameraControlAsyncErrorOverride }
   },
+  {
+    "camera.control.test.is_low_memory",
+    kPrefValueIsBoolean,
+    { &sPrefCameraParametersIsLowMemory }
+  },
+  {
+    "camera.control.low_memory_thresholdMB",
+    kPrefValueIsUint32,
+    { &sPrefCameraControlLowMemoryThresholdMB }
+  },
 };
 
 /* static */
 uint32_t
 CameraPreferences::PrefToIndex(const char* aPref)
 {
   for (uint32_t i = 0; i < ArrayLength(sPrefs); ++i) {
     if (strcmp(aPref, sPrefs[i].mPref) == 0) {
@@ -99,36 +139,59 @@ CameraPreferences::PreferenceChanged(con
   if (i == kPrefNotFound) {
     DOM_CAMERA_LOGE("Preference '%s' is not tracked by CameraPreferences\n", aPref);
     return;
   }
 
   Pref& p = sPrefs[i];
   nsresult rv;
   switch (p.mValueType) {
-    case kPrefValueIsNSResult:
+    case kPrefValueIsNsResult:
+    #ifdef CAMERAPREFERENCES_HAVE_SEPARATE_UINT32_AND_NSRESULT
       {
         nsresult& v = *p.mValue.mAsNsResult;
         rv = UpdatePref(aPref, v);
         if (NS_SUCCEEDED(rv)) {
           DOM_CAMERA_LOGI("Preference '%s' has changed, 0x%x\n", aPref, v);
         }
       }
       break;
+    #endif
+
+    case kPrefValueIsUint32:
+      {
+        uint32_t& v = *p.mValue.mAsUint32;
+        rv = UpdatePref(aPref, v);
+        if (NS_SUCCEEDED(rv)) {
+          DOM_CAMERA_LOGI("Preference '%s' has changed, %u\n", aPref, v);
+        }
+      }
+      break;
 
     case kPrefValueIsCString:
       {
         nsCString& v = **p.mValue.mAsCString;
         rv = UpdatePref(aPref, v);
         if (NS_SUCCEEDED(rv)) {
           DOM_CAMERA_LOGI("Preference '%s' has changed, '%s'\n", aPref, v.get());
         }
       }
       break;
 
+    case kPrefValueIsBoolean:
+      {
+        bool& v = *p.mValue.mAsBoolean;
+        rv = UpdatePref(aPref, v);
+        if (NS_SUCCEEDED(rv)) {
+          DOM_CAMERA_LOGI("Preference '%s' has changed, %s\n",
+            aPref, v ? "true" : "false");
+        }
+      }
+      break;
+
     default:
       MOZ_ASSERT_UNREACHABLE("Unhandled preference value type!");
       return;
   }
 
   if (NS_FAILED(rv)) {
     DOM_CAMERA_LOGE("Failed to get pref '%s' (0x%x)\n", aPref, rv);
   }
@@ -206,35 +269,83 @@ CameraPreferences::GetPref(const char* a
     return false;
   }
 
   DOM_CAMERA_LOGI("Preference '%s', got '%s'\n", aPref, (*s)->get());
   aVal = **s;
   return true;
 }
 
+#ifdef CAMERAPREFERENCES_HAVE_SEPARATE_UINT32_AND_NSRESULT
 /* static */
 bool
 CameraPreferences::GetPref(const char* aPref, nsresult& aVal)
 {
   MOZ_ASSERT(sPrefMonitor, "sPrefMonitor missing in CameraPreferences::GetPref()");
   MonitorAutoLock mon(*sPrefMonitor);
 
   uint32_t i = PrefToIndex(aPref);
   if (i == kPrefNotFound || i >= ArrayLength(sPrefs)) {
     DOM_CAMERA_LOGW("Preference '%s' is not tracked by CameraPreferences\n", aPref);
     return false;
   }
-  if (sPrefs[i].mValueType != kPrefValueIsNSResult) {
+  if (sPrefs[i].mValueType != kPrefValueIsNsResult) {
     DOM_CAMERA_LOGW("Preference '%s' is not an nsresult type\n", aPref);
     return false;
   }
 
   nsresult v = *sPrefs[i].mValue.mAsNsResult;
   if (v == NS_OK) {
-    DOM_CAMERA_LOGI("Preference '%s' is not set\n", aPref);
+    DOM_CAMERA_LOGW("Preference '%s' is not set\n", aPref);
     return false;
   }
 
   DOM_CAMERA_LOGI("Preference '%s', got 0x%x\n", aPref, v);
   aVal = v;
   return true;
 }
+#endif
+
+/* static */
+bool
+CameraPreferences::GetPref(const char* aPref, uint32_t& aVal)
+{
+  MOZ_ASSERT(sPrefMonitor, "sPrefMonitor missing in CameraPreferences::GetPref()");
+  MonitorAutoLock mon(*sPrefMonitor);
+
+  uint32_t i = PrefToIndex(aPref);
+  if (i == kPrefNotFound || i >= ArrayLength(sPrefs)) {
+    DOM_CAMERA_LOGW("Preference '%s' is not tracked by CameraPreferences\n", aPref);
+    return false;
+  }
+  if (sPrefs[i].mValueType != kPrefValueIsUint32) {
+    DOM_CAMERA_LOGW("Preference '%s' is not a uint32_t type\n", aPref);
+    return false;
+  }
+
+  uint32_t v = *sPrefs[i].mValue.mAsUint32;
+  DOM_CAMERA_LOGI("Preference '%s', got %u\n", aPref, v);
+  aVal = v;
+  return true;
+}
+
+/* static */
+bool
+CameraPreferences::GetPref(const char* aPref, bool& aVal)
+{
+  MOZ_ASSERT(sPrefMonitor, "sPrefMonitor missing in CameraPreferences::GetPref()");
+  MonitorAutoLock mon(*sPrefMonitor);
+
+  uint32_t i = PrefToIndex(aPref);
+  if (i == kPrefNotFound || i >= ArrayLength(sPrefs)) {
+    DOM_CAMERA_LOGW("Preference '%s' is not tracked by CameraPreferences\n", aPref);
+    return false;
+  }
+  if (sPrefs[i].mValueType != kPrefValueIsBoolean) {
+    DOM_CAMERA_LOGW("Preference '%s' is not a boolean type\n", aPref);
+    return false;
+  }
+
+  bool v = *sPrefs[i].mValue.mAsBoolean;
+  DOM_CAMERA_LOGI("Preference '%s', got %s\n", aPref, v ? "true" : "false");
+  aVal = v;
+  return true;
+}
--- a/dom/camera/CameraPreferences.h
+++ b/dom/camera/CameraPreferences.h
@@ -3,64 +3,87 @@
  * 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 DOM_CAMERA_CAMERAPREFERENCES_H
 #define DOM_CAMERA_CAMERAPREFERENCES_H
 
 #include "nsString.h"
 
+#if defined(MOZ_HAVE_CXX11_STRONG_ENUMS) || defined(MOZ_HAVE_CXX11_ENUM_TYPE)
+// Older compilers that don't support strongly-typed enums
+// just typedef uint32_t to nsresult, which results in conflicting
+// overloaded members in CameraPreferences.
+#define CAMERAPREFERENCES_HAVE_SEPARATE_UINT32_AND_NSRESULT
+#endif
+
 namespace mozilla {
 
 template<class T> class StaticAutoPtr;
 
 class CameraPreferences
 {
 public:
   static bool Initialize();
   static void Shutdown();
 
   static bool GetPref(const char* aPref, nsACString& aVal);
+#ifdef CAMERAPREFERENCES_HAVE_SEPARATE_UINT32_AND_NSRESULT
   static bool GetPref(const char* aPref, nsresult& aVal);
+#endif
+  static bool GetPref(const char* aPref, uint32_t& aVal);
+  static bool GetPref(const char* aPref, bool& aVal);
 
 protected:
   static const uint32_t kPrefNotFound = UINT32_MAX;
   static uint32_t PrefToIndex(const char* aPref);
 
   static void PreferenceChanged(const char* aPref, void* aClosure);
+#ifdef CAMERAPREFERENCES_HAVE_SEPARATE_UINT32_AND_NSRESULT
   static nsresult UpdatePref(const char* aPref, nsresult& aVar);
+#endif
+  static nsresult UpdatePref(const char* aPref, uint32_t& aVar);
   static nsresult UpdatePref(const char* aPref, nsACString& aVar);
+  static nsresult UpdatePref(const char* aPref, bool& aVar);
 
   enum PrefValueType {
-    kPrefValueIsNSResult,
-    kPrefValueIsCString
+    kPrefValueIsNsResult,
+    kPrefValueIsUint32,
+    kPrefValueIsCString,
+    kPrefValueIsBoolean
   };
   struct Pref {
     const char* const           mPref;
     PrefValueType               mValueType;
     union {
       // The 'mAsVoid' member must be first and is required to allow 'mValue'
       // to be initialized with any pointer type, as not all of our platforms
       // support the use of designated initializers; in their absence, only
       // the first element of a union can be statically initialized, and
       // 'void*' lets us stuff any pointer type into it.
       void*                     mAsVoid;
       StaticAutoPtr<nsCString>* mAsCString;
       nsresult*                 mAsNsResult;
+      uint32_t*                 mAsUint32;
+      bool*                     mAsBoolean;
     } mValue;
   };
   static Pref sPrefs[];
 
   static StaticAutoPtr<nsCString> sPrefTestEnabled;
   static StaticAutoPtr<nsCString> sPrefHardwareTest;
   static StaticAutoPtr<nsCString> sPrefGonkParameters;
 
   static nsresult sPrefCameraControlMethodErrorOverride;
   static nsresult sPrefCameraControlAsyncErrorOverride;
 
+  static uint32_t sPrefCameraControlLowMemoryThresholdMB;
+
+  static bool sPrefCameraParametersIsLowMemory;
+
 private:
   // static class only
   CameraPreferences();
   ~CameraPreferences();
 };
 
 } // namespace mozilla
 
--- a/dom/camera/GonkCameraParameters.cpp
+++ b/dom/camera/GonkCameraParameters.cpp
@@ -11,22 +11,50 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "GonkCameraParameters.h"
 #include "camera/CameraParameters.h"
+#include "CameraPreferences.h"
 #include "ICameraControl.h"
 #include "CameraCommon.h"
+#include "mozilla/Hal.h"
 
 using namespace mozilla;
 using namespace android;
 
+/* static */ bool
+GonkCameraParameters::IsLowMemoryPlatform()
+{
+  bool testIsLowMem = false;
+  CameraPreferences::GetPref("camera.control.test.is_low_memory", testIsLowMem);
+  if (testIsLowMem) {
+    NS_WARNING("Forcing low-memory platform camera preferences");
+    return true;
+  }
+
+  uint32_t lowMemoryThresholdBytes = 0;
+  CameraPreferences::GetPref("camera.control.low_memory_thresholdMB",
+                             lowMemoryThresholdBytes);
+  lowMemoryThresholdBytes *= 1024 * 1024;
+  if (lowMemoryThresholdBytes) {
+    uint32_t totalMemoryBytes = hal::GetTotalSystemMemory();
+    if (totalMemoryBytes < lowMemoryThresholdBytes) {
+      DOM_CAMERA_LOGI("Low-memory platform with %d bytes of RAM (threshold: <%d bytes)\n",
+        totalMemoryBytes, lowMemoryThresholdBytes);
+      return true;
+    }
+  }
+
+  return false;
+}
+
 /* static */ const char*
 GonkCameraParameters::Parameters::GetTextKey(uint32_t aKey)
 {
   switch (aKey) {
     case CAMERA_PARAM_PREVIEWSIZE:
       return KEY_PREVIEW_SIZE;
     case CAMERA_PARAM_PREVIEWFORMAT:
       return KEY_PREVIEW_FORMAT;
@@ -241,44 +269,65 @@ GonkCameraParameters::Initialize()
   }
 
   // The return code from GetListAsArray() doesn't matter. If it fails,
   // the isoModes array will be empty, and the subsequent loop won't
   // execute.
   nsString s;
   nsTArray<nsCString> isoModes;
   GetListAsArray(CAMERA_PARAM_SUPPORTED_ISOMODES, isoModes);
-  for (uint32_t i = 0; i < isoModes.Length(); ++i) {
+  for (nsTArray<nsCString>::size_type i = 0; i < isoModes.Length(); ++i) {
     rv = MapIsoFromGonk(isoModes[i].get(), s);
     if (NS_FAILED(rv)) {
       DOM_CAMERA_LOGW("Unrecognized ISO mode value '%s'\n", isoModes[i].get());
       continue;
     }
     *mIsoModes.AppendElement() = s;
     mIsoModeMap.Put(s, new nsCString(isoModes[i]));
   }
 
+  GetListAsArray(CAMERA_PARAM_SUPPORTED_SCENEMODES, mSceneModes);
+  if (IsLowMemoryPlatform()) {
+    bool hdrRemoved = false;
+    while (mSceneModes.RemoveElement(NS_LITERAL_STRING("hdr"))) {
+      hdrRemoved = true;
+    }
+    if (hdrRemoved) {
+      DOM_CAMERA_LOGI("Disabling HDR support due to low memory\n");
+    }
+  }
+
   mInitialized = true;
   return NS_OK;
 }
 
 // Handle nsAStrings
 nsresult
 GonkCameraParameters::SetTranslated(uint32_t aKey, const nsAString& aValue)
 {
-  if (aKey == CAMERA_PARAM_ISOMODE) {
-    nsAutoCString v;
-    nsresult rv = MapIsoToGonk(aValue, v);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-    return SetImpl(aKey, v.get());
+  switch (aKey) {
+    case CAMERA_PARAM_ISOMODE:
+      {
+        nsAutoCString v;
+        nsresult rv = MapIsoToGonk(aValue, v);
+        if (NS_FAILED(rv)) {
+          return rv;
+        }
+        return SetImpl(aKey, v.get());
+      }
+
+    case CAMERA_PARAM_SCENEMODE:
+      if (mSceneModes.IndexOf(aValue) == nsTArray<nsString>::NoIndex) {
+        return NS_ERROR_INVALID_ARG;
+      }
+      // fallthrough
+
+    default:
+      return SetImpl(aKey, NS_ConvertUTF16toUTF8(aValue).get());
   }
-
-  return SetImpl(aKey, NS_ConvertUTF16toUTF8(aValue).get());
 }
 
 nsresult
 GonkCameraParameters::GetTranslated(uint32_t aKey, nsAString& aValue)
 {
   const char* val;
   nsresult rv = GetImpl(aKey, val);
   if (NS_FAILED(rv)) {
@@ -615,17 +664,17 @@ GonkCameraParameters::SetTranslated(uint
   }
 
   return SetImpl(aKey, aValue);
 }
 
 nsresult
 GonkCameraParameters::GetTranslated(uint32_t aKey, double& aValue)
 {
-  double val;
+  double val = 0.0; // initialize to keep the compiler happy [-Wmaybe-uninitialized]
   int index = 0;
   double focusDistance[3];
   const char* s;
   nsresult rv;
 
   switch (aKey) {
     case CAMERA_PARAM_ZOOM:
       rv = GetImpl(aKey, index);
@@ -835,22 +884,28 @@ GonkCameraParameters::GetListAsArray(uin
   }
 
   return NS_OK;
 }
 
 nsresult
 GonkCameraParameters::GetTranslated(uint32_t aKey, nsTArray<nsString>& aValues)
 {
-  if (aKey == CAMERA_PARAM_SUPPORTED_ISOMODES) {
-    aValues = mIsoModes;
-    return NS_OK;
+  switch (aKey) {
+    case CAMERA_PARAM_SUPPORTED_ISOMODES:
+      aValues = mIsoModes;
+      return NS_OK;
+
+    case CAMERA_PARAM_SUPPORTED_SCENEMODES:
+      aValues = mSceneModes;
+      return NS_OK;
+
+    default:
+      return GetListAsArray(aKey, aValues);
   }
-
-  return GetListAsArray(aKey, aValues);
 }
 
 nsresult
 GonkCameraParameters::GetTranslated(uint32_t aKey, nsTArray<double>& aValues)
 {
   if (aKey == CAMERA_PARAM_SUPPORTED_ZOOMRATIOS) {
     aValues.Clear();
     for (uint32_t i = 0; i < mZoomRatios.Length(); ++i) {
--- a/dom/camera/GonkCameraParameters.h
+++ b/dom/camera/GonkCameraParameters.h
@@ -96,16 +96,17 @@ protected:
   bool mInitialized;
 
   // Required internal properties
   double mExposureCompensationStep;
   int32_t mExposureCompensationMinIndex;
   int32_t mExposureCompensationMaxIndex;
   nsTArray<int> mZoomRatios;
   nsTArray<nsString> mIsoModes;
+  nsTArray<nsString> mSceneModes;
   nsClassHashtable<nsStringHashKey, nsCString> mIsoModeMap;
 
   // This subclass of android::CameraParameters just gives
   // all of the AOSP getters and setters the same signature.
   class Parameters : public android::CameraParameters
   {
   public:
     using android::CameraParameters::set;
@@ -219,13 +220,17 @@ protected:
   //  - NS_OK on success;
   //  - NS_ERROR_INVALID_ARG if the 'aIso' argument is not a valid form.
   nsresult MapIsoToGonk(const nsAString& aIso, nsACString& aIsoOut);
   nsresult MapIsoFromGonk(const char* aIso, nsAString& aIsoOut);
 
   // Call once to initialize local cached values used in translating other
   // arguments between Gecko and Gonk. Always returns NS_OK.
   nsresult Initialize();
+
+  // Returns true if we're a memory-constrained platform that requires
+  // certain features to be disabled; returns false otherwise.
+  static bool IsLowMemoryPlatform();
 };
 
 } // namespace mozilla
 
 #endif // DOM_CAMERA_GONKCAMERAPARAMETERS_H
--- a/dom/camera/test/camera_common.js
+++ b/dom/camera/test/camera_common.js
@@ -30,16 +30,17 @@ var CameraTest = (function() {
    *
    * This means (of course) that neither the key not the value tokens can
    * contain either equals signs or semicolons. The test shim doesn't enforce
    * this so that we can test getting junk from the camera library as well.
    */
   const PREF_TEST_ENABLED = "camera.control.test.enabled";
   const PREF_TEST_HARDWARE = "camera.control.test.hardware";
   const PREF_TEST_EXTRA_PARAMETERS = "camera.control.test.hardware.gonk.parameters";
+  const PREF_TEST_FAKE_LOW_MEMORY = "camera.control.test.is_low_memory";
   var oldTestEnabled;
   var oldTestHw;
   var testMode;
 
   function testHardwareSetFakeParameters(parameters, callback) {
     SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_EXTRA_PARAMETERS, parameters]]}, function() {
       var setParams = SpecialPowers.getCharPref(PREF_TEST_EXTRA_PARAMETERS);
       ise(setParams, parameters, "Extra test parameters '" + setParams + "'");
@@ -48,16 +49,30 @@ var CameraTest = (function() {
       }
     });
   }
 
   function testHardwareClearFakeParameters(callback) {
     SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_EXTRA_PARAMETERS]]}, callback);
   }
 
+  function testHardwareSetFakeLowMemoryPlatform(callback) {
+    SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_FAKE_LOW_MEMORY, true]]}, function() {
+      var setParams = SpecialPowers.getBoolPref(PREF_TEST_FAKE_LOW_MEMORY);
+      ise(setParams, true, "Fake low memory platform");
+      if (callback) {
+        callback(setParams);
+      }
+    });
+  }
+
+  function testHardwareClearFakeLowMemoryPlatform(callback) {
+    SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_FAKE_LOW_MEMORY]]}, callback);
+  }
+
   function testHardwareSet(test, callback) {
     SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_HARDWARE, test]]}, function() {
       var setTest = SpecialPowers.getCharPref(PREF_TEST_HARDWARE);
       ise(setTest, test, "Test subtype set to " + setTest);
       if (callback) {
         callback(setTest);
       }
     });
@@ -83,16 +98,18 @@ var CameraTest = (function() {
       if (setMode === "hardware") {
         try {
           oldTestHw = SpecialPowers.getCharPref(PREF_TEST_HARDWARE);
         } catch(e) { }
         testMode = {
           set: testHardwareSet,
           setFakeParameters: testHardwareSetFakeParameters,
           clearFakeParameters: testHardwareClearFakeParameters,
+          setFakeLowMemoryPlatform: testHardwareSetFakeLowMemoryPlatform,
+          clearFakeLowMemoryPlatform: testHardwareClearFakeLowMemoryPlatform,
           done: testHardwareDone
         };
         if (callback) {
           callback(testMode);
         }
       }
     });
   }
@@ -117,18 +134,26 @@ var CameraTest = (function() {
       var next = cleanUpTestEnabled;
       if (testMode) {
         testMode.done(next);
         testMode = null;
       } else {
         next();
       }
     }
+    function cleanUpLowMemoryPlatform() {
+      var next = cleanUpTest;
+      if (testMode) {
+        testMode.clearFakeLowMemoryPlatform(next);
+      } else {
+        next();
+      }
+    }
     function cleanUpExtraParameters() {
-      var next = cleanUpTest;
+      var next = cleanUpLowMemoryPlatform;
       if (testMode) {
         testMode.clearFakeParameters(next);
       } else {
         next();
       }
     }
 
     cleanUpExtraParameters();
--- a/dom/camera/test/test_camera_fake_parameters.html
+++ b/dom/camera/test/test_camera_fake_parameters.html
@@ -110,44 +110,93 @@ var tests = [
     },
     test: function testFakeZoomOutOfOrder(cam, cap) {
       ok(cap.zoomRatios.length == 1, "zoom ratios length = " + cap.zoomRatios.length);
       ok(cap.zoomRatios[0] == 1.0, "only supported zoom = " + cap.zoomRatios[0] + "x");
       next();
     }
   },
   {
+    key: "fake-high-memory-platform",
+    prep: function setupFakeHighMemoryPlatform(test) {
+      test.setFakeParameters("scene-mode-values=none,snow,beach,hdr,nothdr", function () {
+        run();
+      });
+    },
+    test: function testFakeHighMemoryPlatform(cam, cap) {
+      ok(cap.sceneModes.length == 5, "scene modes length = " + cap.zoomRatios.length);
+
+      // make sure expected values are present and can be set
+      [ "none", "snow", "beach", "hdr", "nothdr" ].forEach(function(mode) {
+        ok(cap.sceneModes.indexOf(mode) != -1, "Scene mode '" + mode + "' is present");
+        cam.sceneMode = mode;
+        ok(cam.sceneMode == mode, "Scene mode '" + cam.sceneMode + "' is set");
+      });
+
+      next();
+    }
+  },
+  {
+    key: "fake-low-memory-platform",
+    prep: function setupFakeLowMemoryPlatform(test) {
+      test.setFakeLowMemoryPlatform(function() {
+        test.setFakeParameters("scene-mode-values=none,hdr,snow,beach,hdr,nothdr", function () {
+          run();
+        });
+      });
+    },
+    test: function testFakeLowMemoryPlatform(cam, cap) {
+      ok(cap.sceneModes.length == 4, "scene modes length = " + cap.zoomRatios.length);
+
+      // make sure expected values are present and can be set
+      [ "none", "snow", "beach", "nothdr" ].forEach(function(mode) {
+        ok(cap.sceneModes.indexOf(mode) != -1, "Scene mode '" + mode + "' is present");
+        cam.sceneMode = mode;
+        ok(cam.sceneMode == mode, "Scene mode '" + cam.sceneMode + "' is set");
+      });
+
+      // make sure unsupported values have been removed, and can't be set
+      var sceneMode = cam.sceneMode;
+      [ "hdr" ].forEach(function(mode) {
+        ok(cap.sceneModes.indexOf(mode) == -1, "Scene mode '" + mode + "' is not present");
+        try {
+          cam.sceneMode = mode;
+        } catch(e) {
+        }
+        ok(cam.sceneMode != mode, "Scene mode '" + cam.sceneMode + "' is still set, '"
+          + mode + "' rejected");
+      });
+      ok(cam.sceneMode == sceneMode, "Scene mode '" + cam.sceneMode + "' is still set");
+
+      next();
+    }
+  },
+  {
     key: "fake-iso",
     prep: function setupFakeIso(test) {
       // we should recognize 'auto', 'hjr', and numeric modes; anything else
       // from the driver is ignored, which this test also verifies.
       test.setFakeParameters(
         "iso=auto;iso-values=auto,ISO_HJR,ISO100,foo,ISObar,ISO150moz,ISO200,400,ISO800,1600",
         function () {
         run();
       });
     },
     test: function testFakeIso(cam, cap) {
       ok(cap.isoModes.length == 7, "ISO modes length = " + cap.isoModes.length);
 
       // make sure we're not leaking any unexpected values formats
-      ok(cap.isoModes.indexOf("ISO_HJR") == -1, "ISO mode 'ISO_HJR' does not appear");
-      ok(cap.isoModes.indexOf("_HJR") == -1, "ISO mode '_HJR' does not appear");
-      ok(cap.isoModes.indexOf("HJR") == -1, "ISO mode 'HJR' does not appear");
-      ok(cap.isoModes.indexOf("ISO100") == -1, "ISO mode 'ISO100' does not appear");
-      ok(cap.isoModes.indexOf("ISO200") == -1, "ISO mode 'ISO200' does not appear");
-      ok(cap.isoModes.indexOf("ISO800") == -1, "ISO mode 'ISO800' does not appear");
+      [ "ISO_HJR", "_HJR", "HJR", "ISO100", "ISO200", "ISO800" ].forEach(function(iso) {
+        ok(cap.isoModes.indexOf(iso) == -1, "ISO mode '" + iso + "' does not appear");
+      });
 
       // make sure any weird values are dropped entirely
-      ok(cap.isoModes.indexOf("foo") == -1, "Unknown ISO mode 'foo' is ignored");
-      ok(cap.isoModes.indexOf("ISObar") == -1, "Unknown ISO mode 'ISObar' is ignored");
-      ok(cap.isoModes.indexOf("bar") == -1, "Unknown ISO mode 'bar' is ignored");
-      ok(cap.isoModes.indexOf("ISO150moz") == -1, "Unknown ISO mode 'ISO150moz' is ignored");
-      ok(cap.isoModes.indexOf("150moz") == -1, "Unknown ISO mode '150moz' is ignored");
-      ok(cap.isoModes.indexOf("150") == -1, "Unknown ISO mode '150' is ignored");
+      [ "foo", "ISObar", "bar", "ISO150moz", "150moz", "150" ].forEach(function(iso) {
+        ok(cap.isoModes.indexOf(iso) == -1, "Unknown ISO mode '" + iso + "' is ignored");
+      });
 
       // make sure expected values are present
       [ "auto", "hjr", "100", "200", "400", "800", "1600" ].forEach(function(iso) {
         ok(cap.isoModes.indexOf(iso) != -1, "ISO mode '" + iso + "' is present");
       });
 
       // test setters/getters for individual ISO modes
       cap.isoModes.forEach(function(iso, index) {
--- a/dom/webidl/BluetoothDevice2.webidl
+++ b/dom/webidl/BluetoothDevice2.webidl
@@ -1,22 +1,45 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-interface BluetoothDevice : EventTarget {
-  readonly attribute DOMString      address;
-  readonly attribute DOMString      name;
-  readonly attribute DOMString      icon;
-  readonly attribute boolean        connected;
-  readonly attribute boolean        paired;
-  readonly attribute unsigned long  class;
+/*
+ * Set of attributes that might be changed and reported by attributechanged
+ * event.
+ * Address is not included since it should not be changed once BluetoothDevice
+ * is created.
+ */
+enum BluetoothDeviceAttribute
+{
+  "unknown",
+  "cod",
+  "name",
+  "paired",
+  "uuids"
+};
 
-  // array of type DOMString[]
-  [Throws]
-  readonly attribute any            uuids;
+[CheckPermissions="bluetooth"]
+interface BluetoothDevice : EventTarget
+{
+  readonly attribute DOMString              address;
+  readonly attribute BluetoothClassOfDevice cod;
+  readonly attribute DOMString              name;
+  readonly attribute boolean                paired;
+
+  [Cached, Pure]
+  readonly attribute sequence<DOMString>    uuids;
 
-  // array of type DOMString[]
-  [Throws]
-  readonly attribute any            services;
+           attribute EventHandler           onattributechanged;
+
+  /**
+   * Fetch the up-to-date UUID list of each bluetooth service that the device
+   * provides and refresh the cache value of attribute uuids if it is updated.
+   *
+   * If the operation succeeds, the promise will be resolved with up-to-date
+   * UUID list which is identical to attribute uuids.
+   */
+  // Promise<sequence<DOMString>>
+  [NewObject, Throws]
+  Promise                                   fetchUuids();
 };
--- a/dom/wifi/WifiCommand.jsm
+++ b/dom/wifi/WifiCommand.jsm
@@ -235,36 +235,41 @@ this.WifiCommand = function(aControlMess
     doStringCommand("DRIVER LINKSPEED", function(reply) {
       if (reply) {
         reply = reply.split(" ")[1] | 0; // Format: LinkSpeed XX
       }
       callback(reply);
     });
   };
 
+  let infoKeys = [{regexp: /RSSI=/i,      prop: 'rssi'},
+                  {regexp: /LINKSPEED=/i, prop: 'linkspeed'}];
+
   command.getConnectionInfoICS = function (callback) {
     doStringCommand("SIGNAL_POLL", function(reply) {
       if (!reply) {
         callback(null);
         return;
       }
 
+      // Find any values matching |infoKeys|. This gets executed frequently
+      // enough that we want to avoid creating intermediate strings as much as
+      // possible.
       let rval = {};
-      var lines = reply.split("\n");
-      for (let i = 0; i < lines.length; ++i) {
-        let [key, value] = lines[i].split("=");
-        switch (key.toUpperCase()) {
-          case "RSSI":
-            rval.rssi = value | 0;
-            break;
-          case "LINKSPEED":
-            rval.linkspeed = value | 0;
-            break;
-          default:
-            // Ignore.
+      for (let i = 0; i < infoKeys.length; i++) {
+        let re = infoKeys[i].regexp;
+        let iKeyStart = reply.search(re);
+        if (iKeyStart !== -1) {
+          let prop = infoKeys[i].prop;
+          let iValueStart = reply.indexOf('=', iKeyStart) + 1;
+          let iNewlineAfterValue = reply.indexOf('\n', iValueStart);
+          let iValueEnd = iNewlineAfterValue !== -1
+                        ? iNewlineAfterValue
+                        : reply.length;
+          rval[prop] = reply.substring(iValueStart, iValueEnd) | 0;
         }
       }
 
       callback(rval);
     });
   };
 
   command.getMacAddress = function (callback) {
--- a/mobile/android/base/tabspanel/TabsPanel.java
+++ b/mobile/android/base/tabspanel/TabsPanel.java
@@ -174,16 +174,20 @@ public class TabsPanel extends LinearLay
             @Override
             public void onClick(View view) {
                 showMenu();
             }
         });
     }
 
     public void showMenu() {
+        if (mCurrentPanel == Panel.REMOTE_TABS) {
+            return;
+        }
+
         final Menu menu = mPopupMenu.getMenu();
 
         // Each panel has a "+" shortcut button, so don't show it for that panel.
         menu.findItem(R.id.new_tab).setVisible(mCurrentPanel != Panel.NORMAL_TABS);
         menu.findItem(R.id.new_private_tab).setVisible(mCurrentPanel != Panel.PRIVATE_TABS);
 
         // Only show "Clear * tabs" for current panel.
         menu.findItem(R.id.close_all_tabs).setVisible(mCurrentPanel == Panel.NORMAL_TABS);
--- a/mobile/android/base/tests/testDebuggerServer.js
+++ b/mobile/android/base/tests/testDebuggerServer.js
@@ -17,14 +17,14 @@ add_test(function() {
   // Enable the debugger via the pref it listens for
   do_register_cleanup(function() {
     Services.prefs.clearUserPref(DEBUGGER_REMOTE_ENABLED);
   });
   Services.prefs.setBoolPref(DEBUGGER_REMOTE_ENABLED, true);
 
   let DebuggerServer = window.DebuggerServer;
   do_check_true(DebuggerServer.initialized);
-  do_check_true(!!DebuggerServer._listener);
+  do_check_eq(DebuggerServer.listeningSockets, 1);
 
   run_next_test();
 });
 
 run_next_test();
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1518,18 +1518,19 @@ var BrowserApp = {
         break;
       }
 
       case "Tab:Load": {
         let data = JSON.parse(aData);
         let url = data.url;
         let flags;
 
-        if (/^[0-9]+$/.test(url)) {
-          // If the query is a number, force a search (see bug 993705; workaround for bug 693808).
+        if (!data.engine && /^[0-9]+$/.test(url)) {
+          // If the query is a number and we're not using a search engine,
+          // force a search (see bug 993705; workaround for bug 693808).
           url = URIFixup.keywordToURI(url).spec;
         } else {
           flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
                    Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
         }
 
         // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from
         // inheriting the currently loaded document's principal.
@@ -3158,16 +3159,17 @@ Tab.prototype = {
                 Ci.nsIWebProgress.NOTIFY_LOCATION |
                 Ci.nsIWebProgress.NOTIFY_SECURITY;
     this.browser.addProgressListener(this, flags);
     this.browser.sessionHistory.addSHistoryListener(this);
 
     this.browser.addEventListener("DOMContentLoaded", this, true);
     this.browser.addEventListener("DOMFormHasPassword", this, true);
     this.browser.addEventListener("DOMLinkAdded", this, true);
+    this.browser.addEventListener("DOMLinkChanged", this, true);
     this.browser.addEventListener("DOMTitleChanged", this, true);
     this.browser.addEventListener("DOMWindowClose", this, true);
     this.browser.addEventListener("DOMWillOpenModalDialog", this, true);
     this.browser.addEventListener("DOMAutoComplete", this, true);
     this.browser.addEventListener("blur", this, true);
     this.browser.addEventListener("scroll", this, true);
     this.browser.addEventListener("MozScrolledAreaChanged", this, true);
     this.browser.addEventListener("pageshow", this, true);
@@ -3330,16 +3332,17 @@ Tab.prototype = {
     this.browser.contentWindow.controllers.removeController(this.overscrollController);
 
     this.browser.removeProgressListener(this);
     this.browser.sessionHistory.removeSHistoryListener(this);
 
     this.browser.removeEventListener("DOMContentLoaded", this, true);
     this.browser.removeEventListener("DOMFormHasPassword", this, true);
     this.browser.removeEventListener("DOMLinkAdded", this, true);
+    this.browser.removeEventListener("DOMLinkChanged", this, true);
     this.browser.removeEventListener("DOMTitleChanged", this, true);
     this.browser.removeEventListener("DOMWindowClose", this, true);
     this.browser.removeEventListener("DOMWillOpenModalDialog", this, true);
     this.browser.removeEventListener("DOMAutoComplete", this, true);
     this.browser.removeEventListener("blur", this, true);
     this.browser.removeEventListener("scroll", this, true);
     this.browser.removeEventListener("MozScrolledAreaChanged", this, true);
     this.browser.removeEventListener("pageshow", this, true);
@@ -3743,17 +3746,18 @@ Tab.prototype = {
         break;
       }
 
       case "DOMFormHasPassword": {
         LoginManagerContent.onFormPassword(aEvent);
         break;
       }
 
-      case "DOMLinkAdded": {
+      case "DOMLinkAdded":
+      case "DOMLinkChanged": {
         let target = aEvent.originalTarget;
         if (!target.href || target.disabled)
           return;
 
         // Ignore on frames and other documents
         if (target.ownerDocument != this.browser.contentDocument)
           return;
 
@@ -3792,17 +3796,17 @@ Tab.prototype = {
 
           let json = {
             type: "Link:Favicon",
             tabID: this.id,
             href: resolveGeckoURI(target.href),
             size: maxSize
           };
           sendMessageToJava(json);
-        } else if (list.indexOf("[alternate]") != -1) {
+        } else if (list.indexOf("[alternate]") != -1 && aEvent.type == "DOMLinkAdded") {
           let type = target.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
           let isFeed = (type == "application/rss+xml" || type == "application/atom+xml");
 
           if (!isFeed)
             return;
 
           try {
             // urlSecurityCeck will throw if things are not OK
@@ -3813,17 +3817,17 @@ Tab.prototype = {
             this.browser.feeds.push({ href: target.href, title: target.title, type: type });
 
             let json = {
               type: "Link:Feed",
               tabID: this.id
             };
             sendMessageToJava(json);
           } catch (e) {}
-        } else if (list.indexOf("[search]" != -1)) {
+        } else if (list.indexOf("[search]" != -1) && aEvent.type == "DOMLinkAdded") {
           let type = target.type && target.type.toLowerCase();
 
           // Replace all starting or trailing spaces or spaces before "*;" globally w/ "".
           type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
 
           // Check that type matches opensearch.
           let isOpenSearch = (type == "application/opensearchdescription+xml");
           if (isOpenSearch && target.title && /^(?:https?|ftp):/i.test(target.href)) {
@@ -7210,17 +7214,17 @@ var RemoteDebugger = {
       DebuggerServer.openListener(port);
       dump("Remote debugger listening on port " + port);
     } catch(e) {
       dump("Remote debugger didn't start: " + e);
     }
   },
 
   _stop: function rd_start() {
-    DebuggerServer.closeListener();
+    DebuggerServer.closeAllListeners();
     dump("Remote debugger stopped");
   }
 };
 
 var Telemetry = {
   addData: function addData(aHistogramId, aValue) {
     let histogram = Services.telemetry.getHistogramById(aHistogramId);
     histogram.add(aValue);
--- a/mobile/android/search/java/org/mozilla/search/Constants.java
+++ b/mobile/android/search/java/org/mozilla/search/Constants.java
@@ -10,14 +10,15 @@
 
 package org.mozilla.search;
 
 /**
  * Key should not be stored here. For more info on storing keys, see
  * https://github.com/ericedens/FirefoxSearch/issues/3
  */
 public class Constants {
-    public static final String AUTO_COMPLETE_FRAGMENT = "org.mozilla.search.AUTO_COMPLETE_FRAGMENT";
-    public static final String CARD_STREAM_FRAGMENT = "org.mozilla.search.CARD_STREAM_FRAGMENT";
-    public static final String GECKO_VIEW_FRAGMENT = "org.mozilla.search.GECKO_VIEW_FRAGMENT";
+
+    public static final String POSTSEARCH_FRAGMENT = "org.mozilla.search.POSTSEARCH_FRAGMENT";
+    public static final String PRESEARCH_FRAGMENT = "org.mozilla.search.PRESEARCH_FRAGMENT";
+    public static final String SEARCH_FRAGMENT = "org.mozilla.search.SEARCH_FRAGMENT";
 
     public static final String AUTOCOMPLETE_ROW_LIMIT = "5";
 }
--- a/mobile/android/search/java/org/mozilla/search/MainActivity.java
+++ b/mobile/android/search/java/org/mozilla/search/MainActivity.java
@@ -2,102 +2,69 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.search;
 
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentTransaction;
+import android.view.View;
 
 import org.mozilla.search.autocomplete.AcceptsSearchQuery;
-import org.mozilla.search.autocomplete.AutoCompleteFragment;
-import org.mozilla.search.stream.CardStreamFragment;
-
 
 /**
  * The main entrance for the Android search intent.
  * <p/>
  * State management is delegated to child fragments. Fragments communicate
  * with each other by passing messages through this activity. The only message passing right
  * now, the only message passing occurs when a user wants to submit a search query. That
  * passes through the onSearch method here.
  */
-public class MainActivity extends FragmentActivity implements AcceptsSearchQuery,
-        FragmentManager.OnBackStackChangedListener {
+public class MainActivity extends FragmentActivity implements AcceptsSearchQuery {
 
-    private DetailActivity detailActivity;
+    enum State {
+        START,
+        PRESEARCH,
+        POSTSEARCH
+    }
+
+    private State state = State.START;
 
     @Override
     protected void onCreate(Bundle stateBundle) {
         super.onCreate(stateBundle);
-
-        // Sets the content view for the Activity
         setContentView(R.layout.search_activity_main);
+        startPresearch();
+    }
 
-        // Gets an instance of the support library FragmentManager
-        FragmentManager localFragmentManager = getSupportFragmentManager();
-
-        // If the incoming state of the Activity is null, sets the initial view to be thumbnails
-        if (null == stateBundle) {
+    @Override
+    public void onSearch(String s) {
+        startPostsearch();
+        ((PostSearchFragment) getSupportFragmentManager().findFragmentById(R.id.gecko))
+                .setUrl("https://search.yahoo.com/search?p=" + Uri.encode(s));
+    }
 
-            // Starts a Fragment transaction to track the stack
-            FragmentTransaction localFragmentTransaction = localFragmentManager.beginTransaction();
-
-            localFragmentTransaction.add(R.id.header_fragments, new AutoCompleteFragment(),
-                    Constants.AUTO_COMPLETE_FRAGMENT);
+    private void startPresearch() {
+        if (state != State.PRESEARCH) {
+            state = State.PRESEARCH;
+            findViewById(R.id.gecko).setVisibility(View.INVISIBLE);
+            findViewById(R.id.presearch).setVisibility(View.VISIBLE);
+        }
+    }
 
-            localFragmentTransaction.add(R.id.presearch_fragments, new CardStreamFragment(),
-                    Constants.CARD_STREAM_FRAGMENT);
-
-            // Commits this transaction to display the Fragment
-            localFragmentTransaction.commit();
-
-            // The incoming state of the Activity isn't null.
+    private void startPostsearch() {
+        if (state != State.POSTSEARCH) {
+            state = State.POSTSEARCH;
+            findViewById(R.id.presearch).setVisibility(View.INVISIBLE);
+            findViewById(R.id.gecko).setVisibility(View.VISIBLE);
         }
     }
 
     @Override
-    protected void onStart() {
-        super.onStart();
-
-
-        if (null == detailActivity) {
-            detailActivity = new DetailActivity();
-        }
-
-        if (null == getSupportFragmentManager().findFragmentByTag(Constants.GECKO_VIEW_FRAGMENT)) {
-            FragmentTransaction txn = getSupportFragmentManager().beginTransaction();
-            txn.add(R.id.gecko_fragments, detailActivity, Constants.GECKO_VIEW_FRAGMENT);
-            txn.hide(detailActivity);
-
-            txn.commit();
+    public void onBackPressed() {
+        if (state == State.POSTSEARCH) {
+            startPresearch();
+        } else {
+            super.onBackPressed();
         }
     }
-
-    @Override
-    public void onSearch(String s) {
-        FragmentManager localFragmentManager = getSupportFragmentManager();
-        FragmentTransaction localFragmentTransaction = localFragmentManager.beginTransaction();
-
-        localFragmentTransaction
-                .hide(localFragmentManager.findFragmentByTag(Constants.CARD_STREAM_FRAGMENT))
-                .addToBackStack(null);
-
-        localFragmentTransaction
-                .show(localFragmentManager.findFragmentByTag(Constants.GECKO_VIEW_FRAGMENT))
-                .addToBackStack(null);
-
-        localFragmentTransaction.commit();
-
-
-        ((DetailActivity) getSupportFragmentManager()
-                .findFragmentByTag(Constants.GECKO_VIEW_FRAGMENT))
-                .setUrl("https://search.yahoo.com/search?p=" + Uri.encode(s));
-    }
-
-    @Override
-    public void onBackStackChanged() {
-
-    }
 }
rename from mobile/android/search/java/org/mozilla/search/DetailActivity.java
rename to mobile/android/search/java/org/mozilla/search/PostSearchFragment.java
--- a/mobile/android/search/java/org/mozilla/search/DetailActivity.java
+++ b/mobile/android/search/java/org/mozilla/search/PostSearchFragment.java
@@ -13,25 +13,25 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
 import org.mozilla.gecko.GeckoView;
 import org.mozilla.gecko.GeckoViewChrome;
 import org.mozilla.gecko.GeckoViewContent;
 import org.mozilla.gecko.PrefsHelper;
 
-public class DetailActivity extends Fragment {
+public class PostSearchFragment extends Fragment {
 
-    private static final String LOGTAG = "DetailActivity";
+    private static final String LOGTAG = "PostSearchFragment";
     private GeckoView geckoView;
 
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
+                             Bundle savedInstanceState) {
         View mainView = inflater.inflate(R.layout.search_activity_detail, container, false);
 
 
         geckoView = (GeckoView) mainView.findViewById(R.id.gecko_view);
 
         geckoView.setChromeDelegate(new MyGeckoViewChrome());
         geckoView.setContentDelegate(new SearchGeckoView());
 
rename from mobile/android/search/java/org/mozilla/search/stream/CardStreamFragment.java
rename to mobile/android/search/java/org/mozilla/search/PreSearchFragment.java
--- a/mobile/android/search/java/org/mozilla/search/stream/CardStreamFragment.java
+++ b/mobile/android/search/java/org/mozilla/search/PreSearchFragment.java
@@ -1,36 +1,36 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-package org.mozilla.search.stream;
+package org.mozilla.search;
 
 import android.os.Bundle;
 import android.support.v4.app.ListFragment;
 import android.view.View;
 import android.widget.ArrayAdapter;
 
-import org.mozilla.search.R;
+import org.mozilla.search.stream.PreloadAgent;
 
 
 /**
  * This fragment is responsible for managing the card stream. Right now
  * we only use this during pre-search, but we could also use it
  * during post-search at some point.
  */
-public class CardStreamFragment extends ListFragment {
+public class PreSearchFragment extends ListFragment {
 
     private ArrayAdapter<PreloadAgent.TmpItem> adapter;
 
     /**
      * Mandatory empty constructor for the fragment manager to instantiate the
      * fragment (e.g. upon screen orientation changes).
      */
-    public CardStreamFragment() {
+    public PreSearchFragment() {
     }
 
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         getListView().setDivider(null);
     }
 
rename from mobile/android/search/java/org/mozilla/search/autocomplete/AutoCompleteFragment.java
rename to mobile/android/search/java/org/mozilla/search/autocomplete/SearchFragment.java
--- a/mobile/android/search/java/org/mozilla/search/autocomplete/AutoCompleteFragment.java
+++ b/mobile/android/search/java/org/mozilla/search/autocomplete/SearchFragment.java
@@ -28,40 +28,40 @@ import org.mozilla.search.R;
 
 /**
  * A fragment to handle autocomplete. Its interface with the outside
  * world should be very very limited.
  * <p/>
  * TODO: Add clear button to search input
  * TODO: Add more search providers (other than the dictionary)
  */
-public class AutoCompleteFragment extends Fragment implements AdapterView.OnItemClickListener,
+public class SearchFragment extends Fragment implements AdapterView.OnItemClickListener,
         TextView.OnEditorActionListener, AcceptsJumpTaps {
 
     private View mainView;
     private FrameLayout backdropFrame;
     private EditText searchBar;
     private ListView suggestionDropdown;
     private InputMethodManager inputMethodManager;
     private AutoCompleteAdapter autoCompleteAdapter;
     private AutoCompleteAgentManager autoCompleteAgentManager;
     private State state;
 
     private enum State {
         WAITING,  // The user is doing something else in the app.
         RUNNING   // The user is in search mode.
     }
 
-    public AutoCompleteFragment() {
+    public SearchFragment() {
         // Required empty public constructor
     }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
+                             Bundle savedInstanceState) {
 
 
         mainView = inflater.inflate(R.layout.search_auto_complete, container, false);
         backdropFrame = (FrameLayout) mainView.findViewById(R.id.auto_complete_backdrop);
         searchBar = (EditText) mainView.findViewById(R.id.auto_complete_search_bar);
         suggestionDropdown = (ListView) mainView.findViewById(R.id.auto_complete_dropdown);
 
         inputMethodManager =
--- a/mobile/android/search/java/org/mozilla/search/stream/PreloadAgent.java
+++ b/mobile/android/search/java/org/mozilla/search/stream/PreloadAgent.java
@@ -7,20 +7,20 @@ package org.mozilla.search.stream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 /**
  * A temporary agent for loading cards into the pre-load card stream.
  * <p/>
- * When we have more agents, we'll want to put an agent manager between the CardStreamFragment
+ * When we have more agents, we'll want to put an agent manager between the PreSearchFragment
  * and the set of all agents. See autocomplete.AutoCompleteFragmentManager.
  */
-class PreloadAgent {
+public class PreloadAgent {
 
     public static final List<TmpItem> ITEMS = new ArrayList<TmpItem>();
 
     private static final Map<String, TmpItem> ITEM_MAP = new HashMap<String, TmpItem>();
 
     static {
         addItem(new TmpItem("1", "Pre-load item1"));
         addItem(new TmpItem("2", "Pre-load item2"));
--- a/mobile/android/search/res/layout/search_activity_detail.xml
+++ b/mobile/android/search/res/layout/search_activity_detail.xml
@@ -5,16 +5,16 @@
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                 xmlns:tools="http://schemas.android.com/tools"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:paddingBottom="@dimen/activity_vertical_margin"
                 android:paddingLeft="@dimen/activity_horizontal_margin"
                 android:paddingRight="@dimen/activity_horizontal_margin"
                 android:paddingTop="@dimen/activity_vertical_margin"
-                tools:context="org.mozilla.search.DetailActivity">
+                tools:context="org.mozilla.search.PostSearchFragment">
 
     <org.mozilla.gecko.GeckoView
         android:id="@+id/gecko_view"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"/>
 
 </RelativeLayout>
--- a/mobile/android/search/res/layout/search_activity_main.xml
+++ b/mobile/android/search/res/layout/search_activity_main.xml
@@ -5,31 +5,30 @@
 <merge
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
     tools:context=".MainActivity">
 
-
-    <FrameLayout
-        android:id="@+id/gecko_fragments"
+    <fragment
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_marginTop="45dp"
+        android:name="org.mozilla.search.PostSearchFragment"
+        android:id="@+id/gecko"
         />
 
-
-    <FrameLayout
-        android:id="@+id/presearch_fragments"
+    <fragment
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:name="org.mozilla.search.PreSearchFragment"
+        android:id="@+id/presearch"
         />
 
-    <FrameLayout
-
-        android:id="@+id/header_fragments"
+    <fragment
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:name="org.mozilla.search.autocomplete.SearchFragment"
+        android:id="@+id/header_fragments"
         />
 
-</merge>
\ No newline at end of file
+</merge>
--- a/mobile/android/search/search_activity_sources.mozbuild
+++ b/mobile/android/search/search_activity_sources.mozbuild
@@ -4,18 +4,18 @@
 # 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/.
 
 search_activity_sources = [
     'java/org/mozilla/search/autocomplete/AcceptsJumpTaps.java',
     'java/org/mozilla/search/autocomplete/AcceptsSearchQuery.java',
     'java/org/mozilla/search/autocomplete/AutoCompleteAdapter.java',
     'java/org/mozilla/search/autocomplete/AutoCompleteAgentManager.java',
-    'java/org/mozilla/search/autocomplete/AutoCompleteFragment.java',
     'java/org/mozilla/search/autocomplete/AutoCompleteModel.java',
     'java/org/mozilla/search/autocomplete/AutoCompleteRowView.java',
     'java/org/mozilla/search/autocomplete/AutoCompleteWordListAgent.java',
+    'java/org/mozilla/search/autocomplete/SearchFragment.java',
     'java/org/mozilla/search/Constants.java',
-    'java/org/mozilla/search/DetailActivity.java',
     'java/org/mozilla/search/MainActivity.java',
-    'java/org/mozilla/search/stream/CardStreamFragment.java',
+    'java/org/mozilla/search/PostSearchFragment.java',
+    'java/org/mozilla/search/PreSearchFragment.java',
     'java/org/mozilla/search/stream/PreloadAgent.java',
 ]
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -4268,8 +4268,16 @@ pref("image.mozsamplesize.enabled", fals
 // play nicely with Firefox OS apps yet.
 #ifndef MOZ_WIDGET_GONK
 pref("beacon.enabled", true);
 #endif
 
 // Camera prefs
 pref("camera.control.autofocus_moving_callback.enabled", true);
 pref("camera.control.face_detection.enabled", true);
+#ifdef MOZ_WIDGET_GONK
+// Empirically, this is the value returned by hal::GetTotalSystemMemory()
+// when Flame's memory is limited to 512MiB. If the camera stack determines
+// it is running on a low memory platform, features that can be reliably
+// supported will be disabled. This threshold can be adjusted to suit other
+// platforms; and set to 0 to disable the low-memory check altogether.
+pref("camera.control.low_memory_thresholdMB", 404);
+#endif
--- a/netwerk/dns/nsIDNSListener.idl
+++ b/netwerk/dns/nsIDNSListener.idl
@@ -5,17 +5,17 @@
 #include "nsISupports.idl"
 
 interface nsICancelable;
 interface nsIDNSRecord;
 
 /**
  * nsIDNSListener
  */
-[scriptable, uuid(41466a9f-f027-487d-a96c-af39e629b8d2)]
+[scriptable, function, uuid(27d49bfe-280c-49e0-bbaa-f6200c232c3d)]
 interface nsIDNSListener : nsISupports
 {
     /**
      * called when an asynchronous host lookup completes.
      *
      * @param aRequest
      *        the value returned from asyncResolve.
      * @param aRecord
--- a/security/manager/boot/src/StaticHPKPins.h
+++ b/security/manager/boot/src/StaticHPKPins.h
@@ -1062,9 +1062,9 @@ static const TransportSecurityPreload kP
   { "youtube.com", true, false, false, -1, &kPinset_google_root_pems },
   { "ytimg.com", true, false, false, -1, &kPinset_google_root_pems },
 };
 
 // Pinning Preload List Length = 325;
 
 static const int32_t kUnknownId = -1;
 
-static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1413551502825000);
+static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1413626795593000);
--- a/security/manager/boot/src/nsSTSPreloadList.errors
+++ b/security/manager/boot/src/nsSTSPreloadList.errors
@@ -101,16 +101,17 @@ saturngames.co.uk: did not receive HSTS 
 script.google.com: did not receive HSTS header (error ignored - included regardless)
 security.google.com: did not receive HSTS header (error ignored - included regardless)
 semenkovich.com: did not receive HSTS header
 serverdensity.io: did not receive HSTS header
 shops.neonisi.com: could not connect to host
 silentcircle.com: did not receive HSTS header
 silentcircle.org: could not connect to host
 simon.butcher.name: max-age too low: 2629743
+simple.com: did not receive HSTS header
 sites.google.com: did not receive HSTS header (error ignored - included regardless)
 sol.io: could not connect to host
 souyar.de: could not connect to host
 souyar.net: could not connect to host
 souyar.us: could not connect to host
 spreadsheets.google.com: did not receive HSTS header (error ignored - included regardless)
 square.com: did not receive HSTS header
 ssl.google-analytics.com: did not receive HSTS header (error ignored - included regardless)
@@ -119,17 +120,16 @@ stocktrade.de: could not connect to host
 sunshinepress.org: could not connect to host
 surfeasy.com: did not receive HSTS header
 talk.google.com: did not receive HSTS header (error ignored - included regardless)
 talkgadget.google.com: did not receive HSTS header (error ignored - included regardless)
 translate.googleapis.com: did not receive HSTS header (error ignored - included regardless)
 uprotect.it: could not connect to host
 wallet.google.com: did not receive HSTS header (error ignored - included regardless)
 webmail.mayfirst.org: did not receive HSTS header
-wf-bigsky-master.appspot.com: did not receive HSTS header (error ignored - included regardless)
 whonix.org: did not receive HSTS header
 www.airbnb.com: could not connect to host
 www.calyxinstitute.org: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 124"  data: no]
 www.cueup.com: did not receive HSTS header
 www.developer.mydigipass.com: could not connect to host
 www.dropbox.com: max-age too low: 2592000
 www.elanex.biz: did not receive HSTS header
 www.gmail.com: did not receive HSTS header (error ignored - included regardless)
--- a/security/manager/boot/src/nsSTSPreloadList.inc
+++ b/security/manager/boot/src/nsSTSPreloadList.inc
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*****************************************************************************/
 /* This is an automatically generated file. If you're not                    */
 /* nsSiteSecurityService.cpp, you shouldn't be #including it.     */
 /*****************************************************************************/
 
 #include <stdint.h>
-const PRTime gPreloadListExpirationTime = INT64_C(1415970699881000);
+const PRTime gPreloadListExpirationTime = INT64_C(1416045990752000);
 
 class nsSTSPreload
 {
   public:
     const char *mHost;
     const bool mIncludeSubdomains;
 };
 
@@ -201,17 +201,16 @@ static const nsSTSPreload kSTSPreloadLis
   { "sakaki.anime.my", true },
   { "sandbox.mydigipass.com", false },
   { "script.google.com", true },
   { "security.google.com", true },
   { "securityheaders.com", true },
   { "seifried.org", true },
   { "shodan.io", true },
   { "simbolo.co.uk", false },
-  { "simple.com", false },
   { "sites.google.com", true },
   { "skydrive.live.com", false },
   { "spreadsheets.google.com", true },
   { "squareup.com", false },
   { "ssl.google-analytics.com", true },
   { "stage.wepay.com", false },
   { "static.wepay.com", false },
   { "stocktrade.de", false },
--- a/testing/mochitest/b2g_start_script.js
+++ b/testing/mochitest/b2g_start_script.js
@@ -4,27 +4,35 @@
 
 let outOfProcess = __marionetteParams[0]
 let mochitestUrl = __marionetteParams[1]
 let onDevice = __marionetteParams[2]
 let wifiSettings = __marionetteParams[3]
 let prefs = Components.classes["@mozilla.org/preferences-service;1"].
                             getService(Components.interfaces.nsIPrefBranch)
 let settings = window.navigator.mozSettings;
+let cm = Components.classes["@mozilla.org/categorymanager;1"].
+                    getService(Components.interfaces.nsICategoryManager);
 
 if (wifiSettings)
   wifiSettings = JSON.parse(wifiSettings);
 
 const CHILD_SCRIPT = "chrome://specialpowers/content/specialpowers.js";
 const CHILD_SCRIPT_API = "chrome://specialpowers/content/specialpowersAPI.js";
 const CHILD_LOGGER_SCRIPT = "chrome://specialpowers/content/MozillaLogger.js";
 
 let homescreen = document.getElementById('systemapp');
 let container = homescreen.contentWindow.document.getElementById('test-container');
 
+// Disable udpate timers which cause failure in b2g permisson prompt tests.
+if (cm) {
+  cm.deleteCategoryEntry("update-timer", "WebappsUpdateTimer", false);
+  cm.deleteCategoryEntry("update-timer", "nsUpdateService", false);
+}
+
 function openWindow(aEvent) {
   var popupIframe = aEvent.detail.frameElement;
   popupIframe.style = 'position: absolute; left: 0; top: 0px; background: white;';
 
   // This is to size the iframe to what is requested in the window.open call,
   // e.g. window.open("", "", "width=600,height=600");
   if (aEvent.detail.features.indexOf('width') != -1) {
     let width = aEvent.detail.features.substr(aEvent.detail.features.indexOf('width')+6);
--- a/toolkit/devtools/gcli/commands/csscoverage.js
+++ b/toolkit/devtools/gcli/commands/csscoverage.js
@@ -25,23 +25,31 @@ exports.items = [
     name: "csscoverage",
     hidden: true,
     description: l10n.lookup("csscoverageDesc"),
   },
   {
     name: "csscoverage start",
     hidden: true,
     description: l10n.lookup("csscoverageStartDesc2"),
+    params: [
+      {
+        name: "noreload",
+        type: "boolean",
+        description: l10n.lookup("csscoverageStartNoReloadDesc"),
+        manual: l10n.lookup("csscoverageStartNoReloadManual")
+      }
+    ],
     exec: function*(args, context) {
       let usage = yield csscoverage.getUsage(context.environment.target);
       if (usage == null) {
         throw new Error(l10n.lookup("csscoverageNoRemoteError"));
       }
       yield usage.start(context.environment.chromeWindow,
-                        context.environment.target);
+                        context.environment.target, args.noreload);
     }
   },
   {
     name: "csscoverage stop",
     hidden: true,
     description: l10n.lookup("csscoverageStopDesc2"),
     exec: function*(args, context) {
       let target = context.environment.target;
--- a/toolkit/devtools/server/actors/csscoverage.js
+++ b/toolkit/devtools/server/actors/csscoverage.js
@@ -103,22 +103,27 @@ let CSSUsageActor = protocol.ActorClass(
     delete this._onTabLoad;
     delete this._onChange;
 
     protocol.Actor.prototype.destroy.call(this);
   },
 
   /**
    * Begin recording usage data
+   * @param noreload It's best if we start by reloading the current page
+   * because that starts the test at a known point, but there could be reasons
+   * why we don't want to do that (e.g. the page contains state that will be
+   * lost across a reload)
    */
-  start: method(function() {
+  start: method(function(noreload) {
     if (this._running) {
       throw new Error(l10n.lookup("csscoverageRunningError"));
     }
 
+    this._isOneShot = false;
     this._visitedPages = new Set();
     this._knownRules = new Map();
     this._running = true;
     this._tooManyUnused = false;
 
     this._progressListener = {
       QueryInterface: XPCOMUtils.generateQI([ Ci.nsIWebProgressListener,
                                               Ci.nsISupportsWeakReference ]),
@@ -138,20 +143,28 @@ let CSSUsageActor = protocol.ActorClass(
       onStatusChange: () => {},
       destroy: () => {}
     };
 
     this._progress = this._tabActor.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                                             .getInterface(Ci.nsIWebProgress);
     this._progress.addProgressListener(this._progressListener, this._notifyOn);
 
-    this._populateKnownRules(this._tabActor.window.document);
-    this._updateUsage(this._tabActor.window.document, false);
+    if (noreload) {
+      // If we're not starting by reloading the page, then pretend that onload
+      // has just happened.
+      this._onTabLoad(this._tabActor.window.document);
+    }
+    else {
+      this._tabActor.window.location.reload();
+    }
 
     events.emit(this, "state-change", { isRunning: true });
+  }, {
+    request: { url: Arg(0, "boolean") }
   }),
 
   /**
    * Cease recording usage data
    */
   stop: method(function() {
     if (!this._running) {
       throw new Error(l10n.lookup("csscoverageNotRunningError"));
@@ -175,16 +188,17 @@ let CSSUsageActor = protocol.ActorClass(
    * Running start() quickly followed by stop() does a bunch of unnecessary
    * work, so this cuts all that out
    */
   oneshot: method(function() {
     if (this._running) {
       throw new Error(l10n.lookup("csscoverageRunningError"));
     }
 
+    this._isOneShot = true;
     this._visitedPages = new Set();
     this._knownRules = new Map();
 
     this._populateKnownRules(this._tabActor.window.document);
     this._updateUsage(this._tabActor.window.document, false);
   }),
 
   /**
@@ -387,16 +401,20 @@ let CSSUsageActor = protocol.ActorClass(
     if (this._running) {
       throw new Error(l10n.lookup("csscoverageRunningError"));
     }
 
     if (this._visitedPages == null) {
       throw new Error(l10n.lookup("csscoverageNotRunError"));
     }
 
+    if (this._isOneShot) {
+      throw new Error(l10n.lookup("csscoverageOneShotReportError"));
+    }
+
     // Helper function to create a JSONable data structure representing a rule
     const ruleToRuleReport = function(rule, ruleData) {
       return {
         url: rule.url,
         shortUrl: rule.url.split("/").slice(-1)[0],
         start: { line: rule.line, column: rule.column },
         selectorText: ruleData.selectorText,
         formattedCssText: CssLogic.prettifyCSS(ruleData.cssText)
@@ -772,21 +790,21 @@ const CSSUsageFront = protocol.FrontClas
       gDevTools.showToolbox(target, "styleeditor");
       target = undefined;
     }
   }),
 
   /**
    * Server-side start is above. Client-side start adds a notification box
    */
-  start: custom(function(newChromeWindow, newTarget) {
+  start: custom(function(newChromeWindow, newTarget, noreload=false) {
     target = newTarget;
     chromeWindow = newChromeWindow;
 
-    return this._start();
+    return this._start(noreload);
   }, {
     impl: "_start"
   }),
 
   /**
    * Server-side start is above. Client-side start adds a notification box
    */
   toggle: custom(function(newChromeWindow, newTarget) {
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -157,21 +157,19 @@ function ModuleAPI() {
     }
   }
 };
 
 /***
  * Public API
  */
 var DebuggerServer = {
-  _listener: null,
+  _listeners: [],
   _initialized: false,
   _transportInitialized: false,
-  // Number of currently open TCP connections.
-  _socketConnections: 0,
   // Map of global actor names to actor constructors provided by extensions.
   globalActorFactories: {},
   // Map of tab actor names to actor constructors provided by extensions.
   tabActorFactories: {},
 
   LONG_STRING_LENGTH: 10000,
   LONG_STRING_INITIAL_LENGTH: 1000,
   LONG_STRING_READ_LENGTH: 65 * 1024,
@@ -207,17 +205,17 @@ var DebuggerServer = {
                 prompt.BUTTON_POS_2 * prompt.BUTTON_TITLE_IS_STRING +
                 prompt.BUTTON_POS_1_DEFAULT;
     let result = prompt.confirmEx(null, title, msg, flags, null, null,
                                   disableButton, null, { value: false });
     if (result == 0) {
       return true;
     }
     if (result == 2) {
-      DebuggerServer.closeListener(true);
+      DebuggerServer.closeAllListeners();
       Services.prefs.setBoolPref("devtools.debugger.remote-enabled", false);
     }
     return false;
   },
 
   /**
    * Initialize the debugger server.
    *
@@ -277,17 +275,17 @@ var DebuggerServer = {
     }
 
     for (let id of Object.getOwnPropertyNames(gRegisteredModules)) {
       let mod = gRegisteredModules[id];
       mod.module.unregister(mod.api);
     }
     gRegisteredModules = {};
 
-    this.closeListener();
+    this.closeAllListeners();
     this.globalActorFactories = {};
     this.tabActorFactories = {};
     this._allowConnection = null;
     this._transportInitialized = false;
     this._initialized = false;
 
     dumpn("Debugger server is shut down.");
   },
@@ -431,81 +429,72 @@ var DebuggerServer = {
     // Pass to all connections
     for (let connID of Object.getOwnPropertyNames(this._connections)) {
       promises.push(this._connections[connID].setAddonOptions(aId, aOptions));
     }
 
     return all(promises);
   },
 
+  get listeningSockets() {
+    return this._listeners.length;
+  },
+
+  // TODO: Bug 1033079: Remove after cleaning up Gaia test:
+  // https://github.com/mozilla-b2g/gaia/blob/1ba15ce1ae7254badd25fd276556c1b4f36c0a45/tests/integration/devtools/server_test.js#L31
+  get _listener() {
+    return this.listeningSockets;
+  },
+
   /**
    * Listens on the given port or socket file for remote debugger connections.
    *
-   * @param aPortOrPath int, string
+   * @param portOrPath int, string
    *        If given an integer, the port to listen on.
    *        Otherwise, the path to the unix socket domain file to listen on.
+   * @return SocketListener
+   *         A SocketListener instance that is already opened is returned.  This
+   *         single listener can be closed at any later time by calling |close|
+   *         on the SocketListener.  If a SocketListener could not be opened, an
+   *         error is thrown.  If remote connections are disabled, undefined is
+   *         returned.
    */
-  openListener: function DS_openListener(aPortOrPath) {
+  openListener: function(portOrPath) {
     if (!Services.prefs.getBoolPref("devtools.debugger.remote-enabled")) {
-      return false;
+      return;
     }
     this._checkInit();
 
-    // Return early if the server is already listening.
-    if (this._listener) {
-      return true;
-    }
-
-    let flags = Ci.nsIServerSocket.KeepWhenOffline;
-    // A preference setting can force binding on the loopback interface.
-    if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
-      flags |= Ci.nsIServerSocket.LoopbackOnly;
-    }
-
-    try {
-      let backlog = 4;
-      let socket;
-      let port = Number(aPortOrPath);
-      if (port) {
-        socket = new ServerSocket(port, flags, backlog);
-      } else {
-        let file = nsFile(aPortOrPath);
-        if (file.exists())
-          file.remove(false);
-        socket = new UnixDomainServerSocket(file, parseInt("666", 8), backlog);
-      }
-      socket.asyncListen(this);
-      this._listener = socket;
-    } catch (e) {
-      dumpn("Could not start debugging listener on '" + aPortOrPath + "': " + e);
-      throw Cr.NS_ERROR_NOT_AVAILABLE;
-    }
-    this._socketConnections++;
-
-    return true;
+    let listener = new SocketListener(this);
+    listener.open(portOrPath);
+    this._listeners.push(listener);
+    return listener;
   },
 
   /**
-   * Close a previously-opened TCP listener.
+   * Remove a SocketListener instance from the server's set of active
+   * SocketListeners.  This is called by a SocketListener after it is closed.
+   */
+  _removeListener: function(listener) {
+    this._listeners = this._listeners.filter(l => l !== listener);
+  },
+
+  /**
+   * Closes and forgets all previously opened listeners.
    *
-   * @param aForce boolean [optional]
-   *        If set to true, then the socket will be closed, regardless of the
-   *        number of open connections.
+   * @return boolean
+   *         Whether any listeners were actually closed.
    */
-  closeListener: function DS_closeListener(aForce) {
-    if (!this._listener || this._socketConnections == 0) {
+  closeAllListeners: function() {
+    if (!this.listeningSockets) {
       return false;
     }
 
-    // Only close the listener when the last connection is closed, or if the
-    // aForce flag is passed.
-    if (--this._socketConnections == 0 || aForce) {
-      this._listener.close();
-      this._listener = null;
-      this._socketConnections = 0;
+    for (let listener of this._listeners) {
+      listener.close();
     }
 
     return true;
   },
 
   /**
    * Creates a new connection to the local debugger speaking over a fake
    * transport. This connection results in straightforward calls to the onPacket
@@ -660,35 +649,16 @@ var DebuggerServer = {
       Services.obs.removeObserver(onMessageManagerDisconnect, "message-manager-disconnect");
     });
 
     mm.sendAsyncMessage("debug:connect", { prefix: prefix });
 
     return deferred.promise;
   },
 
-  // nsIServerSocketListener implementation
-
-  onSocketAccepted:
-  DevToolsUtils.makeInfallible(function DS_onSocketAccepted(aSocket, aTransport) {
-    if (Services.prefs.getBoolPref("devtools.debugger.prompt-connection") && !this._allowConnection()) {
-      return;
-    }
-    dumpn("New debugging connection on " + aTransport.host + ":" + aTransport.port);
-
-    let input = aTransport.openInputStream(0, 0, 0);
-    let output = aTransport.openOutputStream(0, 0, 0);
-    let transport = new DebuggerTransport(input, output);
-    DebuggerServer._onConnection(transport);
-  }, "DebuggerServer.onSocketAccepted"),
-
-  onStopListening: function DS_onStopListening(aSocket, status) {
-    dumpn("onStopListening, status: " + status);
-  },
-
   /**
    * Raises an exception if the server has not been properly initialized.
    */
   _checkInit: function DS_checkInit() {
     if (!this._transportInitialized) {
       throw "DebuggerServer has not been initialized.";
     }
 
@@ -855,16 +825,104 @@ includes.forEach(name => {
 });
 
 // Export ActorPool for requirers of main.js
 if (this.exports) {
   exports.ActorPool = ActorPool;
 }
 
 /**
+ * Creates a new socket listener for remote connections to a given
+ * DebuggerServer.  This helps contain and organize the parts of the server that
+ * may differ or are particular to one given listener mechanism vs. another.
+ */
+function SocketListener(server) {
+  this._server = server;
+}
+
+SocketListener.prototype = {
+
+  /**
+   * Listens on the given port or socket file for remote debugger connections.
+   *
+   * @param portOrPath int, string
+   *        If given an integer, the port to listen on.
+   *        Otherwise, the path to the unix socket domain file to listen on.
+   */
+  open: function(portOrPath) {
+    let flags = Ci.nsIServerSocket.KeepWhenOffline;
+    // A preference setting can force binding on the loopback interface.
+    if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
+      flags |= Ci.nsIServerSocket.LoopbackOnly;
+    }
+
+    try {
+      let backlog = 4;
+      let port = Number(portOrPath);
+      if (port) {
+        this._socket = new ServerSocket(port, flags, backlog);
+      } else {
+        let file = nsFile(portOrPath);
+        if (file.exists())
+          file.remove(false);
+        this._socket = new UnixDomainServerSocket(file, parseInt("666", 8),
+                                                  backlog);
+      }
+      this._socket.asyncListen(this);
+    } catch (e) {
+      dumpn("Could not start debugging listener on '" + portOrPath + "': " + e);
+      throw Cr.NS_ERROR_NOT_AVAILABLE;
+    }
+  },
+
+  /**
+   * Closes the SocketListener.  Notifies the server to remove the listener from
+   * the set of active SocketListeners.
+   */
+  close: function() {
+    this._socket.close();
+    this._server._removeListener(this);
+    this._server = null;
+  },
+
+  /**
+   * Gets the port that a TCP socket listener is listening on, or null if this
+   * is not a TCP socket (so there is no port).
+   */
+  get port() {
+    if (!this._socket) {
+      return null;
+    }
+    return this._socket.port;
+  },
+
+  // nsIServerSocketListener implementation
+
+  onSocketAccepted:
+  DevToolsUtils.makeInfallible(function(aSocket, aTransport) {
+    if (Services.prefs.getBoolPref("devtools.debugger.prompt-connection") &&
+        !this._server._allowConnection()) {
+      return;
+    }
+    dumpn("New debugging connection on " +
+          aTransport.host + ":" + aTransport.port);
+
+    let input = aTransport.openInputStream(0, 0, 0);
+    let output = aTransport.openOutputStream(0, 0, 0);
+    let transport = new DebuggerTransport(input, output);
+    this._server._onConnection(transport);
+  }, "SocketListener.onSocketAccepted"),
+
+  onStopListening: function(aSocket, status) {
+    dumpn("onStopListening, status: " + status);
+  }
+
+};
+
+/**
  * Creates a DebuggerServerConnection.
  *
  * Represents a connection to this debugging global from a client.
  * Manages a set of actors and actor pools, allocates actor ids, and
  * handles incoming requests.
  *
  * @param aPrefix string
  *        All actor IDs created by this connection should be prefixed
--- a/toolkit/devtools/server/protocol.js
+++ b/toolkit/devtools/server/protocol.js
@@ -1102,17 +1102,19 @@ let Front = Class({
       let msg = "Unexpected packet " + this.actorID + ", " + JSON.stringify(packet);
       let err = Error(msg);
       console.error(err);
       throw err;
     }
 
     let deferred = this._requests.shift();
     if (packet.error) {
-      deferred.reject(packet.error);
+      let message = (packet.error == "unknownError" && packet.message) ?
+                    packet.message : packet.error;
+      deferred.reject(message);
     } else {
       deferred.resolve(packet);
     }
   }
 });
 exports.Front = Front;
 
 /**
--- a/toolkit/devtools/server/tests/unit/test_dbgglobal.js
+++ b/toolkit/devtools/server/tests/unit/test_dbgglobal.js
@@ -6,35 +6,35 @@ Cu.import("resource://gre/modules/devtoo
 
 function run_test()
 {
   // Should get an exception if we try to interact with DebuggerServer
   // before we initialize it...
   check_except(function() {
     DebuggerServer.openListener(-1);
   });
-  check_except(DebuggerServer.closeListener);
+  check_except(DebuggerServer.closeAllListeners);
   check_except(DebuggerServer.connectPipe);
 
   // Allow incoming connections.
   DebuggerServer.init(function () { return true; });
 
   // These should still fail because we haven't added a createRootActor
   // implementation yet.
   check_except(function() {
     DebuggerServer.openListener(-1);
   });
-  check_except(DebuggerServer.closeListener);
+  check_except(DebuggerServer.closeAllListeners);
   check_except(DebuggerServer.connectPipe);
 
   DebuggerServer.registerModule("xpcshell-test/testactors");
 
   // Now they should work.
   DebuggerServer.openListener(-1);
-  DebuggerServer.closeListener();
+  DebuggerServer.closeAllListeners();
 
   // Make sure we got the test's root actor all set up.
   let client1 = DebuggerServer.connectPipe();
   client1.hooks = {
     onPacket: function(aPacket1) {
       do_check_eq(aPacket1.from, "root");
       do_check_eq(aPacket1.applicationType, "xpcshell-tests");
 
--- a/toolkit/devtools/transport/tests/unit/head_dbg.js
+++ b/toolkit/devtools/transport/tests/unit/head_dbg.js
@@ -245,34 +245,23 @@ function writeTestTempFile(aFileName, aC
       let numWritten = stream.write(aContent, aContent.length);
       aContent = aContent.slice(numWritten);
     } while (aContent.length > 0);
   } finally {
     stream.close();
   }
 }
 
-function try_open_listener() {
-  if (DebuggerServer._listener) {
-    return DebuggerServer._listener.port;
-  }
-  try {
-    // Pick a random one between 2000 and 65000.
-    let port = Math.floor(Math.random() * (65000 - 2000 + 1)) + 2000;
-    do_check_true(DebuggerServer.openListener(port));
-    return port;
-  } catch (e) {
-    return try_open_listener();
-  }
-}
-
 /*** Transport Factories ***/
 
 function socket_transport() {
-  let port = try_open_listener();
+  if (!DebuggerServer.listeningSockets) {
+    DebuggerServer.openListener(-1);
+  }
+  let port = DebuggerServer._listeners[0].port;
   do_print("Debugger server port is " + port);
   return debuggerSocketConnect("127.0.0.1", port);
 }
 
 function local_transport() {
   return DebuggerServer.connectPipe();
 }
 
--- a/toolkit/devtools/transport/tests/unit/test_dbgsocket.js
+++ b/toolkit/devtools/transport/tests/unit/test_dbgsocket.js
@@ -1,49 +1,43 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
 
-let port = 2929;
+let gPort;
+let gExtraListener;
 
 function run_test()
 {
   do_print("Starting test at " + new Date().toTimeString());
   initTestDebuggerServer();
 
   add_test(test_socket_conn);
   add_test(test_socket_shutdown);
   add_test(test_pipe_conn);
 
   run_next_test();
 }
 
-function really_long() {
-  let ret = "0123456789";
-  for (let i = 0; i < 18; i++) {
-    ret += ret;
-  }
-  return ret;
-}
-
 function test_socket_conn()
 {
-  do_check_eq(DebuggerServer._socketConnections, 0);
-  try_open_listener();
-  do_print("Debugger server port is " + port);
-  do_check_eq(DebuggerServer._socketConnections, 1);
-  // Make sure opening the listener twice does nothing.
-  do_check_true(DebuggerServer.openListener(port));
-  do_check_eq(DebuggerServer._socketConnections, 1);
+  do_check_eq(DebuggerServer.listeningSockets, 0);
+  do_check_true(DebuggerServer.openListener(-1));
+  do_check_eq(DebuggerServer.listeningSockets, 1);
+  gPort = DebuggerServer._listeners[0].port;
+  do_print("Debugger server port is " + gPort);
+  // Open a second, separate listener
+  gExtraListener = DebuggerServer.openListener(-1);
+  do_check_eq(DebuggerServer.listeningSockets, 2);
 
   do_print("Starting long and unicode tests at " + new Date().toTimeString());
   let unicodeString = "(╯°□°)╯︵ ┻━┻";
-  let transport = debuggerSocketConnect("127.0.0.1", port);
+  let transport = debuggerSocketConnect("127.0.0.1", gPort);
   transport.hooks = {
     onPacket: function(aPacket) {
       this.onPacket = function(aPacket) {
         do_check_eq(aPacket.unicode, unicodeString);
         transport.close();
       }
       // Verify that things work correctly when bigger than the output
       // transport buffers and when transporting unicode...
@@ -57,25 +51,27 @@ function test_socket_conn()
       run_next_test();
     },
   };
   transport.ready();
 }
 
 function test_socket_shutdown()
 {
-  do_check_eq(DebuggerServer._socketConnections, 1);
-  do_check_true(DebuggerServer.closeListener());
-  do_check_eq(DebuggerServer._socketConnections, 0);
+  do_check_eq(DebuggerServer.listeningSockets, 2);
+  gExtraListener.close();
+  do_check_eq(DebuggerServer.listeningSockets, 1);
+  do_check_true(DebuggerServer.closeAllListeners());
+  do_check_eq(DebuggerServer.listeningSockets, 0);
   // Make sure closing the listener twice does nothing.
-  do_check_false(DebuggerServer.closeListener());
-  do_check_eq(DebuggerServer._socketConnections, 0);
+  do_check_false(DebuggerServer.closeAllListeners());
+  do_check_eq(DebuggerServer.listeningSockets, 0);
 
   do_print("Connecting to a server socket at " + new Date().toTimeString());
-  let transport = debuggerSocketConnect("127.0.0.1", port);
+  let transport = debuggerSocketConnect("127.0.0.1", gPort);
   transport.hooks = {
     onPacket: function(aPacket) {
       // Shouldn't reach this, should never connect.
       do_check_true(false);
     },
 
     onClosed: function(aStatus) {
       do_print("test_socket_shutdown onClosed called at " + new Date().toTimeString());
@@ -101,19 +97,8 @@ function test_pipe_conn()
     },
     onClosed: function(aStatus) {
       run_next_test();
     }
   };
 
   transport.ready();
 }
-
-function try_open_listener()
-{
-  try {
-    do_check_true(DebuggerServer.openListener(port));
-  } catch (e) {
-    // In case the port is unavailable, pick a random one between 2000 and 65000.
-    port = Math.floor(Math.random() * (65000 - 2000 + 1)) + 2000;
-    try_open_listener();
-  }
-}
--- a/toolkit/devtools/transport/tests/unit/test_dbgsocket_connection_drop.js
+++ b/toolkit/devtools/transport/tests/unit/test_dbgsocket_connection_drop.js
@@ -8,18 +8,16 @@
  * framed packet, i.e. when the length header is invalid.
  */
 
 Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
 
 const { RawPacket } = devtools.require("devtools/toolkit/transport/packets");
 
-let port = 2929;
-
 function run_test() {
   do_print("Starting test at " + new Date().toTimeString());
   initTestDebuggerServer();
 
   add_test(test_socket_conn_drops_after_invalid_header);
   add_test(test_socket_conn_drops_after_invalid_header_2);
   add_test(test_socket_conn_drops_after_too_large_length);
   add_test(test_socket_conn_drops_after_too_long_header);
@@ -44,39 +42,29 @@ function test_socket_conn_drops_after_to
   let rawPacket = '4305724038957487634549823475894325';
   for (let i = 0; i < 8; i++) {
     rawPacket += rawPacket;
   }
   return test_helper(rawPacket + ':');
 }
 
 function test_helper(payload) {
-  try_open_listener();
+  let listener = DebuggerServer.openListener(-1);
 
-  let transport = debuggerSocketConnect("127.0.0.1", port);
+  let transport = debuggerSocketConnect("127.0.0.1", listener.port);
   transport.hooks = {
     onPacket: function(aPacket) {
       this.onPacket = function(aPacket) {
         do_throw(new Error("This connection should be dropped."));
         transport.close();
-      }
+      };
 
       // Inject the payload directly into the stream.
       transport._outgoing.push(new RawPacket(transport, payload));
       transport._flushOutgoing();
     },
     onClosed: function(aStatus) {
       do_check_true(true);
       run_next_test();
     },
   };
   transport.ready();
 }
-
-function try_open_listener() {
-  try {
-    do_check_true(DebuggerServer.openListener(port));
-  } catch (e) {
-    // In case the port is unavailable, pick a random one between 2000 and 65000.
-    port = Math.floor(Math.random() * (65000 - 2000 + 1)) + 2000;
-    try_open_listener();
-  }
-}
--- a/toolkit/locales/en-US/chrome/global/devtools/csscoverage.properties
+++ b/toolkit/locales/en-US/chrome/global/devtools/csscoverage.properties
@@ -9,21 +9,24 @@
 # csscoverageStopDesc2, csscoverageOneShotDesc2, csscoverageToggleDesc2,
 # csscoverageReportDesc2): Short descriptions of the csscoverage commands
 csscoverageDesc=Control CSS coverage analysis
 csscoverageStartDesc2=Begin collecting CSS coverage data
 csscoverageStopDesc2=Stop collecting CSS coverage data
 csscoverageOneShotDesc2=Collect instantaneous CSS coverage data
 csscoverageToggleDesc2=Toggle collecting CSS coverage data
 csscoverageReportDesc2=Show CSS coverage report
+csscoverageStartNoReloadDesc=Don't start with a page reload
+csscoverageStartNoReloadManual=It's best if we start by reloading the current page because that starts the test at a known point, but there could be reasons why we don't want to do that (e.g. the page contains state that will be lost across a reload)
 
 # LOCALIZATION NOTE (csscoverageRunningReply, csscoverageDoneReply): Text that
 # describes the current state of the css coverage system
 csscoverageRunningReply=Running CSS coverage analysis
 csscoverageDoneReply=CSS Coverage analysis completed
 
 # LOCALIZATION NOTE (csscoverageRunningError, csscoverageNotRunningError,
 # csscoverageNotRunError): Error message that describe things that can go wrong
 # with the css coverage system
 csscoverageRunningError=CSS coverage analysis already running
 csscoverageNotRunningError=CSS coverage analysis not running
 csscoverageNotRunError=CSS coverage analysis has not been run
 csscoverageNoRemoteError=Target does not support CSS Coverage
+csscoverageOneShotReportError=CSS coverage report is not available for 'oneshot' data. Please use start/stop.
--- a/tools/docs/conf.py
+++ b/tools/docs/conf.py
@@ -14,16 +14,17 @@ mozilla_dir = os.environ['MOZILLA_DIR']
 
 import mdn_theme
 
 extensions = [
     'sphinx.ext.autodoc',
     'sphinx.ext.graphviz',
     'sphinx.ext.todo',
     'mozbuild.sphinx',
+    'javasphinx',
 ]
 
 templates_path = ['_templates']
 source_suffix = '.rst'
 master_doc = 'index'
 project = u'Mozilla Source Tree Docs'
 year = datetime.now().year