Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 16 Feb 2015 16:14:51 +0100
changeset 229352 12189d1c13af8dea7ebfe91391dee8b1186aeaca
parent 229351 c4141f98a5df598af3d22f81682de7cdf23a8564 (current diff)
parent 229283 09f4968d5f429e48e8d53a8a21408cb9674e996c (diff)
child 229353 3341a0bc3296f3c6096eadf378da2db912a58c7e
push id11352
push userryanvm@gmail.com
push dateTue, 17 Feb 2015 19:29:37 +0000
treeherderfx-team@b6c56fab513d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone38.0a1
Merge mozilla-central to mozilla-inbound
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1109,13 +1109,15 @@ pref("dom.requestSync.enabled", true);
 
 // Only enable for kit kat and above devices
 // kit kat == 19, L = 21, 20 is kit-kat for wearables
 // 15 is for the ICS emulators which will fallback to software vsync
 #if ANDROID_VERSION == 19 || ANDROID_VERSION == 21 || ANDROID_VERSION == 15
 pref("gfx.vsync.hw-vsync.enabled", true);
 pref("gfx.vsync.compositor", true);
 pref("gfx.touch.resample", true);
+pref("gfx.vsync.refreshdriver", true);
 #else
 pref("gfx.vsync.hw-vsync.enabled", false);
 pref("gfx.vsync.compositor", false);
 pref("gfx.touch.resample", false);
+pref("gfx.vsync.refreshdriver", false);
 #endif
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cdaa0a4ac28c781709df8c318ed079e9e475503a">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
--- 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="6295c4eb38de793159368aa7f745ef3faf7208aa">
     <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="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eb1795a9002eb142ac58c8d68f8f4ba094af07ca"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="2c31ac3a31a340b40ecd9c291df9b9613d3afa72"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
   <!-- 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="ac778ae59be38aea284a04c89640b1a11c26a5de">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
   <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="cdaa0a4ac28c781709df8c318ed079e9e475503a">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
--- 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="6295c4eb38de793159368aa7f745ef3faf7208aa">
     <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="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eb1795a9002eb142ac58c8d68f8f4ba094af07ca"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="2c31ac3a31a340b40ecd9c291df9b9613d3afa72"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cdaa0a4ac28c781709df8c318ed079e9e475503a">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
--- 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="ac778ae59be38aea284a04c89640b1a11c26a5de">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
   <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": "f0b93e0668ef9565bd6f050b15b4f794d59feb65", 
+        "git_revision": "ae02fbdeae77b2002cebe33c61aedeee4b9439fd", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "d170342f8e1837435749392fc2bded3b9fee01d4", 
+    "revision": "62d026a98ea42f2b93de000e8d0d4f1254f86730", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="ac778ae59be38aea284a04c89640b1a11c26a5de">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="7f2ee9f4cb926684883fc2a2e407045fd9db2199">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f0b93e0668ef9565bd6f050b15b4f794d59feb65"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="ae02fbdeae77b2002cebe33c61aedeee4b9439fd"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d0d11d190ccc50d7d66009bcc896ad4b42d3f0d"/>
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1423263875000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1423697587000">
   <emItems>
       <emItem  blockID="i58" id="webmaster@buzzzzvideos.info">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i71" id="youtube@2youtube.com">
@@ -216,16 +216,36 @@
               </prefs>
     </emItem>
       <emItem  blockID="i93" id="{68b8676b-99a5-46d1-b390-22411d8bcd61}">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i858" id="fftoolbar2014@etech.com">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                    </versionRange>
+                    <prefs>
+                  <pref>browser.startup.homepage</pref>
+                  <pref>browser.search.defaultenginename</pref>
+              </prefs>
+    </emItem>
+      <emItem  blockID="i854" id="/^(7tG@zEb\.net|ru@gfK0J\.edu)$/">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
+      <emItem  blockID="i850" id="P2@D.edu">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i554" id="lightningnewtab@gmail.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i348" id="{13c9f1f9-2322-4d5c-81df-6d4bf8476ba4}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
@@ -264,16 +284,22 @@
               </prefs>
     </emItem>
       <emItem  blockID="i620" id="{21EAF666-26B3-4A3C-ABD0-CA2F5A326744}">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i856" id="/^({94d62e35-4b43-494c-bf52-ba5935df36ef}|firefox@advanceelite\.com|{bb7b7a60-f574-47c2-8a0b-4c56f2da9802})$/">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i482" id="brasilescapeeight@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i17" id="{3252b9ae-c69a-4eaf-9502-dc9c1f6c009e}">
                         <versionRange  minVersion="2.2" maxVersion="2.2">
@@ -448,16 +474,22 @@
               </prefs>
     </emItem>
       <emItem  blockID="i744" id="{84a93d51-b7a9-431e-8ff8-d60e5d7f5df1}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i848" id="bcVX5@nQm9l.org">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i431" id="chinaescapeone@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i806" id="{d9284e50-81fc-11da-a72b-0800200c9a66}">
                         <versionRange  minVersion="0" maxVersion="7.7.34" severity="1">
@@ -1540,16 +1572,22 @@
               </prefs>
     </emItem>
       <emItem  blockID="i726" id="{d87d56b2-1379-49f4-b081-af2850c79d8e}">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i852" id="6lIy@T.edu">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i505" id="extacylife@a.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i86" id="{45147e67-4020-47e2-8f7a-55464fb535aa}">
                         <versionRange  minVersion="0" maxVersion="*">
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -410,8 +410,15 @@
                 accesskey="&bidiSwitchPageDirectionItem.accesskey;"
                 oncommand="gContextMenu.switchPageDirection();"/>
       <menuseparator id="inspect-separator" hidden="true"/>
       <menuitem id="context-inspect"
                 hidden="true"
                 label="&inspectContextMenu.label;"
                 accesskey="&inspectContextMenu.accesskey;"
                 oncommand="gContextMenu.inspectNode();"/>
+      <menuseparator id="context-media-eme-separator" hidden="true"/>
+      <menuitem id="context-media-eme-learnmore"
+                class="menuitem-iconic"
+                hidden="true"
+                label="&emeLearnMoreContextMenu.label;"
+                accesskey="&emeLearnMoreContextMenu.accesskey;"
+                onclick="gContextMenu.drmLearnMore(event);"/>
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -473,16 +473,18 @@ nsContextMenu.prototype = {
     this.showItem("context-media-unmute", onMedia && this.target.muted);
     this.showItem("context-media-playbackrate", onMedia);
     this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
     this.showItem("context-media-hidecontrols", onMedia && this.target.controls);
     this.showItem("context-video-fullscreen", this.onVideo && this.target.ownerDocument.mozFullScreenElement == null);
     var statsShowing = this.onVideo && this.target.mozMediaStatisticsShowing;
     this.showItem("context-video-showstats", this.onVideo && this.target.controls && !statsShowing);
     this.showItem("context-video-hidestats", this.onVideo && this.target.controls && statsShowing);
+    this.showItem("context-media-eme-learnmore", this.onDRMMedia);
+    this.showItem("context-media-eme-separator", this.onDRMMedia);
 
     // Disable them when there isn't a valid media source loaded.
     if (onMedia) {
       this.setItemAttr("context-media-playbackrate-050x", "checked", this.target.playbackRate == 0.5);
       this.setItemAttr("context-media-playbackrate-100x", "checked", this.target.playbackRate == 1.0);
       this.setItemAttr("context-media-playbackrate-150x", "checked", this.target.playbackRate == 1.5);
       this.setItemAttr("context-media-playbackrate-200x", "checked", this.target.playbackRate == 2.0);
       var hasError = this.target.error != null ||
@@ -494,17 +496,17 @@ nsContextMenu.prototype = {
       this.setItemAttr("context-media-playbackrate", "disabled", hasError);
       this.setItemAttr("context-media-playbackrate-050x", "disabled", hasError);
       this.setItemAttr("context-media-playbackrate-100x", "disabled", hasError);
       this.setItemAttr("context-media-playbackrate-150x", "disabled", hasError);
       this.setItemAttr("context-media-playbackrate-200x", "disabled", hasError);
       this.setItemAttr("context-media-showcontrols", "disabled", hasError);
       this.setItemAttr("context-media-hidecontrols", "disabled", hasError);
       if (this.onVideo) {
-        let canSaveSnapshot = this.target.readyState >= this.target.HAVE_CURRENT_DATA;
+        let canSaveSnapshot = !this.onDRMMedia && this.target.readyState >= this.target.HAVE_CURRENT_DATA;
         this.setItemAttr("context-video-saveimage",  "disabled", !canSaveSnapshot);
         this.setItemAttr("context-video-fullscreen", "disabled", hasError);
         this.setItemAttr("context-video-showstats", "disabled", hasError);
         this.setItemAttr("context-video-hidestats", "disabled", hasError);
       }
     }
     this.showItem("context-media-sep-commands",  onMedia);
   },
@@ -557,16 +559,17 @@ nsContextMenu.prototype = {
     // Initialize contextual info.
     this.onImage           = false;
     this.onLoadedImage     = false;
     this.onCompletedImage  = false;
     this.imageDescURL      = "";
     this.onCanvas          = false;
     this.onVideo           = false;
     this.onAudio           = false;
+    this.onDRMMedia        = false;
     this.onTextInput       = false;
     this.onNumeric         = false;
     this.onKeywordField    = false;
     this.mediaURL          = "";
     this.onLink            = false;
     this.onMailtoLink      = false;
     this.onSaveableLink    = false;
     this.link              = null;
@@ -635,32 +638,38 @@ nsContextMenu.prototype = {
       else if (this.target instanceof HTMLCanvasElement) {
         this.onCanvas = true;
       }
       else if (this.target instanceof HTMLVideoElement) {
         let mediaURL = this.target.currentSrc || this.target.src;
         if (this.isMediaURLReusable(mediaURL)) {
           this.mediaURL = mediaURL;
         }
+        if (this.target.isEncrypted) {
+          this.onDRMMedia = true;
+        }
         // Firefox always creates a HTMLVideoElement when loading an ogg file
         // directly. If the media is actually audio, be smarter and provide a
         // context menu with audio operations.
         if (this.target.readyState >= this.target.HAVE_METADATA &&
             (this.target.videoWidth == 0 || this.target.videoHeight == 0)) {
           this.onAudio = true;
         } else {
           this.onVideo = true;
         }
       }
       else if (this.target instanceof HTMLAudioElement) {
         this.onAudio = true;
         let mediaURL = this.target.currentSrc || this.target.src;
         if (this.isMediaURLReusable(mediaURL)) {
           this.mediaURL = mediaURL;
         }
+        if (this.target.isEncrypted) {
+          this.onDRMMedia = true;
+        }
       }
       else if (editFlags & (SpellCheckHelper.INPUT | SpellCheckHelper.TEXTAREA)) {
         this.onTextInput = (editFlags & SpellCheckHelper.TEXTINPUT) !== 0;
         this.onNumeric = (editFlags & SpellCheckHelper.NUMERIC) !== 0;
         this.onEditableArea = (editFlags & SpellCheckHelper.EDITABLE) !== 0;
         if (this.onEditableArea) {
           if (this.isRemote) {
             InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
@@ -1693,16 +1702,27 @@ nsContextMenu.prototype = {
   },
 
   copyMediaLocation : function () {
     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
                     getService(Ci.nsIClipboardHelper);
     clipboard.copyString(this.mediaURL, document);
   },
 
+  drmLearnMore: function(aEvent) {
+    let drmInfoURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
+    let dest = whereToOpenLink(aEvent);
+    // Don't ever want this to open in the same tab as it'll unload the
+    // DRM'd video, which is going to be a bad idea in most cases.
+    if (dest == "current") {
+      dest = "tab";
+    }
+    openUILinkIn(drmInfoURL, dest);
+  },
+
   get imageURL() {
     if (this.onImage)
       return this.mediaURL;
     return "";
   },
 
   // Formats the 'Search <engine> for "<selection or link text>"' context menu.
   formatSearchContextItem: function() {
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -56,16 +56,17 @@ support-files =
   file_double_close_tab.html
   file_favicon_change.html
   file_favicon_change_not_in_document.html
   file_fullscreen-window-open.html
   get_user_media.html
   head.js
   healthreport_testRemoteCommands.html
   moz.png
+  navigating_window_with_download.html
   offlineQuotaNotification.cacheManifest
   offlineQuotaNotification.html
   page_style_sample.html
   parsingTestHelpers.jsm
   pinning_headers.sjs
   pinning_reports.sjs
   popup_blocker.html
   print_postdata.sjs
@@ -77,16 +78,18 @@ support-files =
   test_bug435035.html
   test_bug462673.html
   test_bug628179.html
   test_bug839103.html
   test_bug959531.html
   test_process_flags_chrome.html
   test_wyciwyg_copying.html
   title_test.svg
+  unknownContentType_file.pif
+  unknownContentType_file.pif^headers^
   video.ogg
   web_video.html
   web_video1.ogv
   web_video1.ogv^headers^
   zoom_test.html
   test_no_mcb_on_http_site_img.html
   test_no_mcb_on_http_site_img.css
   test_no_mcb_on_http_site_font.html
@@ -382,16 +385,18 @@ skip-if = e10s
 [browser_sanitize-timespans.js]
 skip-if = buildapp == 'mulet'
 [browser_sanitizeDialog.js]
 skip-if = buildapp == 'mulet'
 [browser_save_link-perwindowpb.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_private_link_perwindowpb.js]
 skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
+[browser_save_link_when_window_navigates.js]
+skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_video.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1100698 - test uses synthesizeMouse and then does a load of other stuff that breaks in e10s
 [browser_save_video_frame.js]
 [browser_scope.js]
 [browser_searchSuggestionUI.js]
 skip-if = e10s
 support-files =
   searchSuggestionUI.html
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_save_link_when_window_navigates.js
@@ -0,0 +1,173 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+const SAVE_PER_SITE_PREF = "browser.download.lastDir.savePerSite";
+const ALWAYS_DOWNLOAD_DIR_PREF = "browser.download.useDownloadDir";
+const UCT_URI = "chrome://mozapps/content/downloads/unknownContentType.xul";
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+  .getService(Ci.mozIJSSubScriptLoader)
+  .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+                 this);
+
+function createTemporarySaveDirectory() {
+  var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+                  .getService(Ci.nsIProperties)
+                  .get("TmpD", Ci.nsIFile);
+  saveDir.append("testsavedir");
+  if (!saveDir.exists()) {
+    info("create testsavedir!");
+    saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
+  }
+  info("return from createTempSaveDir: " + saveDir.path);
+  return saveDir;
+}
+
+function triggerSave(aWindow, aCallback) {
+  info("started triggerSave, persite downloads: " + (Services.prefs.getBoolPref(SAVE_PER_SITE_PREF) ? "on" : "off"));
+  var fileName;
+  let testBrowser = aWindow.gBrowser.selectedBrowser;
+  let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/navigating_window_with_download.html";
+  windowObserver.setCallback(onUCTDialog);
+  testBrowser.loadURI(testURI);
+
+  // Create the folder the link will be saved into.
+  var destDir = createTemporarySaveDirectory();
+  var destFile = destDir.clone();
+
+  MockFilePicker.displayDirectory = destDir;
+  MockFilePicker.showCallback = function(fp) {
+    info("showCallback");
+    fileName = fp.defaultString;
+    info("fileName: " + fileName);
+    destFile.append (fileName);
+    MockFilePicker.returnFiles = [destFile];
+    MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+    info("done showCallback");
+  };
+
+  mockTransferCallback = function(downloadSuccess) {
+    info("mockTransferCallback");
+    onTransferComplete(aWindow, downloadSuccess, destDir);
+    destDir.remove(true);
+    ok(!destDir.exists(), "Destination dir should be removed");
+    ok(!destFile.exists(), "Destination file should be removed");
+    mockTransferCallback = null;
+    info("done mockTransferCallback");
+  }
+
+  function onUCTDialog(dialog) {
+    function doLoad() {
+      content.document.querySelector('iframe').remove();
+    }
+    testBrowser.messageManager.loadFrameScript("data:,(" + doLoad.toString() + ")()", false);
+    executeSoon(continueDownloading);
+  }
+
+  function continueDownloading() {
+    let windows = Services.wm.getEnumerator("");
+    while (windows.hasMoreElements()) {
+      let win = windows.getNext();
+      if (win.location && win.location.href == UCT_URI) {
+        win.document.documentElement._fireButtonEvent("accept");
+        win.close();
+        return;
+      }
+    }
+    ok(false, "No Unknown Content Type dialog yet?");
+  }
+
+  function onTransferComplete(aWindow, downloadSuccess, destDir) {
+    ok(downloadSuccess, "Link should have been downloaded successfully");
+    aWindow.close();
+
+    executeSoon(aCallback);
+  }
+}
+
+
+let windowObserver = {
+  setCallback: function(aCallback) {
+    if (this._callback) {
+      ok(false, "Should only be dealing with one callback at a time.");
+    }
+    this._callback = aCallback;
+  },
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic != "domwindowopened") {
+      return;
+    }
+
+    let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
+
+    win.addEventListener("load", function onLoad(event) {
+      win.removeEventListener("load", onLoad, false);
+
+      if (win.location == UCT_URI) {
+        SimpleTest.executeSoon(function() {
+          if (windowObserver._callback) {
+            windowObserver._callback(win);
+            delete windowObserver._callback;
+          } else {
+            ok(false, "Unexpected UCT dialog!");
+          }
+        });
+      }
+    }, false);
+  }
+};
+
+Services.ww.registerNotification(windowObserver);
+
+function test() {
+  waitForExplicitFinish();
+
+  function testOnWindow(options, callback) {
+    info("testOnWindow(" + options + ")");
+    var win = OpenBrowserWindow(options);
+    info("got " + win);
+    whenDelayedStartupFinished(win, () => callback(win));
+  }
+
+  function whenDelayedStartupFinished(aWindow, aCallback) {
+    info("whenDelayedStartupFinished");
+    Services.obs.addObserver(function observer(aSubject, aTopic) {
+      info("whenDelayedStartupFinished, got topic: " + aTopic + ", got subject: " + aSubject + ", waiting for " + aWindow);
+      if (aWindow == aSubject) {
+        Services.obs.removeObserver(observer, aTopic);
+        executeSoon(aCallback);
+        info("whenDelayedStartupFinished found our window");
+      }
+    }, "browser-delayed-startup-finished", false);
+  }
+
+  mockTransferRegisterer.register();
+
+  registerCleanupFunction(function () {
+    info("Running the cleanup code");
+    mockTransferRegisterer.unregister();
+    MockFilePicker.cleanup();
+    Services.ww.unregisterNotification(windowObserver);
+    Services.prefs.clearUserPref(ALWAYS_DOWNLOAD_DIR_PREF);
+    Services.prefs.clearUserPref(SAVE_PER_SITE_PREF);
+    info("Finished running the cleanup code");
+  });
+ 
+  Services.prefs.setBoolPref(ALWAYS_DOWNLOAD_DIR_PREF, false);
+  testOnWindow(undefined, function(win) {
+    let windowGonePromise = promiseWindowWillBeClosed(win);
+    Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, true);
+    triggerSave(win, function() {
+      windowGonePromise.then(function() {
+        Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, false);
+        testOnWindow(undefined, function(win) {
+          triggerSave(win, finish);
+        });
+      });
+    });
+  });
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/navigating_window_with_download.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+  <head><title>This window will navigate while you're downloading something</title></head>
+  <body>
+    <iframe src="http://mochi.test:8888/browser/browser/base/content/test/general/unknownContentType_file.pif"></iframe>
+  </body>
+</html>
copy from toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif
copy to browser/base/content/test/general/unknownContentType_file.pif
copy from toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif^headers^
copy to browser/base/content/test/general/unknownContentType_file.pif^headers^
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -290,40 +290,64 @@ loop.standaloneRoomViews = (function(moz
      * Specifically updates the local camera stream size and position, depending
      * on the size and position of the remote video stream.
      * This method gets called from `updateVideoContainer`, which is defined in
      * the `MediaSetupMixin`.
      *
      * @param  {Object} ratio Aspect ratio of the local camera stream
      */
     updateLocalCameraPosition: function(ratio) {
+      // The local stream is a quarter of the remote stream.
+      var LOCAL_STREAM_SIZE = 0.25;
+      // The local stream overlaps the remote stream by a quarter of the local stream.
+      var LOCAL_STREAM_OVERLAP = 0.25;
+      // The minimum size of video height/width allowed by the sdk css.
+      var SDK_MIN_SIZE = 48;
+
       var node = this._getElement(".local");
-      var parent = node.offsetParent || this._getElement(".media");
-      // The local camera view should be a sixth of the size of its offset parent
-      // and positioned to overlap with the remote stream at a quarter of its width.
-      var parentWidth = parent.offsetWidth;
-      var targetWidth = parentWidth / 6;
+      var targetWidth;
 
       node.style.right = "auto";
       if (window.matchMedia && window.matchMedia("screen and (max-width:640px)").matches) {
+        // For reduced screen widths, we just go for a fixed size and no overlap.
         targetWidth = 180;
+        node.style.width = (targetWidth * ratio.width) + "px";
+        node.style.height = (targetWidth * ratio.height) + "px";
         node.style.left = "auto";
       } else {
+        // The local camera view should be a quarter of the size of the remote stream
+        // and positioned to overlap with the remote stream at a quarter of its width.
+
         // Now position the local camera view correctly with respect to the remote
         // video stream.
         var remoteVideoDimensions = this.getRemoteVideoDimensions();
+        targetWidth = remoteVideoDimensions.streamWidth * LOCAL_STREAM_SIZE;
+
+        var realWidth = targetWidth * ratio.width;
+        var realHeight = targetWidth * ratio.height;
+
+        // If we've hit the min size limits, then limit at the minimum.
+        if (realWidth < SDK_MIN_SIZE) {
+          realWidth = SDK_MIN_SIZE;
+          realHeight = realWidth / ratio.width * ratio.height;
+        }
+        if (realHeight < SDK_MIN_SIZE) {
+          realHeight = SDK_MIN_SIZE;
+          realWidth = realHeight / ratio.height * ratio.width;
+        }
+
         var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX);
         // The horizontal offset of the stream, and the width of the resulting
         // pillarbox, is determined by the height exponent of the aspect ratio.
         // Therefore we multiply the width of the local camera view by the height
         // ratio.
-        node.style.left = (offsetX - ((targetWidth * ratio.height) / 4)) + "px";
+        node.style.left = (offsetX - (realWidth * LOCAL_STREAM_OVERLAP)) + "px";
+        node.style.width = realWidth + "px";
+        node.style.height = realHeight + "px";
       }
-      node.style.width = (targetWidth * ratio.width) + "px";
-      node.style.height = (targetWidth * ratio.height) + "px";
     },
 
     /**
      * Checks if current room is active.
      *
      * @return {Boolean}
      */
     _roomIsActive: function() {
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -290,40 +290,64 @@ loop.standaloneRoomViews = (function(moz
      * Specifically updates the local camera stream size and position, depending
      * on the size and position of the remote video stream.
      * This method gets called from `updateVideoContainer`, which is defined in
      * the `MediaSetupMixin`.
      *
      * @param  {Object} ratio Aspect ratio of the local camera stream
      */
     updateLocalCameraPosition: function(ratio) {
+      // The local stream is a quarter of the remote stream.
+      var LOCAL_STREAM_SIZE = 0.25;
+      // The local stream overlaps the remote stream by a quarter of the local stream.
+      var LOCAL_STREAM_OVERLAP = 0.25;
+      // The minimum size of video height/width allowed by the sdk css.
+      var SDK_MIN_SIZE = 48;
+
       var node = this._getElement(".local");
-      var parent = node.offsetParent || this._getElement(".media");
-      // The local camera view should be a sixth of the size of its offset parent
-      // and positioned to overlap with the remote stream at a quarter of its width.
-      var parentWidth = parent.offsetWidth;
-      var targetWidth = parentWidth / 6;
+      var targetWidth;
 
       node.style.right = "auto";
       if (window.matchMedia && window.matchMedia("screen and (max-width:640px)").matches) {
+        // For reduced screen widths, we just go for a fixed size and no overlap.
         targetWidth = 180;
+        node.style.width = (targetWidth * ratio.width) + "px";
+        node.style.height = (targetWidth * ratio.height) + "px";
         node.style.left = "auto";
       } else {
+        // The local camera view should be a quarter of the size of the remote stream
+        // and positioned to overlap with the remote stream at a quarter of its width.
+
         // Now position the local camera view correctly with respect to the remote
         // video stream.
         var remoteVideoDimensions = this.getRemoteVideoDimensions();
+        targetWidth = remoteVideoDimensions.streamWidth * LOCAL_STREAM_SIZE;
+
+        var realWidth = targetWidth * ratio.width;
+        var realHeight = targetWidth * ratio.height;
+
+        // If we've hit the min size limits, then limit at the minimum.
+        if (realWidth < SDK_MIN_SIZE) {
+          realWidth = SDK_MIN_SIZE;
+          realHeight = realWidth / ratio.width * ratio.height;
+        }
+        if (realHeight < SDK_MIN_SIZE) {
+          realHeight = SDK_MIN_SIZE;
+          realWidth = realHeight / ratio.height * ratio.width;
+        }
+
         var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX);
         // The horizontal offset of the stream, and the width of the resulting
         // pillarbox, is determined by the height exponent of the aspect ratio.
         // Therefore we multiply the width of the local camera view by the height
         // ratio.
-        node.style.left = (offsetX - ((targetWidth * ratio.height) / 4)) + "px";
+        node.style.left = (offsetX - (realWidth * LOCAL_STREAM_OVERLAP)) + "px";
+        node.style.width = realWidth + "px";
+        node.style.height = realHeight + "px";
       }
-      node.style.width = (targetWidth * ratio.width) + "px";
-      node.style.height = (targetWidth * ratio.height) + "px";
     },
 
     /**
      * Checks if current room is active.
      *
      * @return {Boolean}
      */
     _roomIsActive: function() {
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -138,16 +138,118 @@ describe("loop.standaloneRoomViews", fun
         sinon.assert.calledOnce(dispatch);
         sinon.assert.calledWithExactly(dispatch, new sharedActions.SetMute({
           type: "video",
           enabled: true
         }));
       });
     });
 
+    describe("Local Stream Size Position", function() {
+      var view, localElement;
+
+      beforeEach(function() {
+        sandbox.stub(window, "matchMedia").returns({
+          matches: false
+        });
+        view = mountTestComponent();
+        localElement = view._getElement(".local");
+      });
+
+      it("should be a quarter of the width of the main stream", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 640,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 1,
+          height: 0.75
+        });
+
+        expect(localElement.style.width).eql("160px");
+        expect(localElement.style.height).eql("120px");
+      });
+
+      it("should be a quarter of the width reduced for aspect ratio", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 640,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 0.75,
+          height: 1
+        });
+
+        expect(localElement.style.width).eql("120px");
+        expect(localElement.style.height).eql("160px");
+      });
+
+      it("should ensure the height is a minimum of 48px", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 180,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 1,
+          height: 0.75
+        });
+
+        expect(localElement.style.width).eql("64px");
+        expect(localElement.style.height).eql("48px");
+      });
+
+      it("should ensure the width is a minimum of 48px", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 180,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 0.75,
+          height: 1
+        });
+
+        expect(localElement.style.width).eql("48px");
+        expect(localElement.style.height).eql("64px");
+      });
+
+      it("should position the stream to overlap the main stream by a quarter", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 640,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 1,
+          height: 0.75
+        });
+
+        expect(localElement.style.width).eql("160px");
+        expect(localElement.style.left).eql("600px");
+      });
+
+      it("should position the stream to overlap the main stream by a quarter when the aspect ratio is vertical", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 640,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 0.75,
+          height: 1
+        });
+
+        expect(localElement.style.width).eql("120px");
+        expect(localElement.style.left).eql("610px");
+      });
+    });
+
     describe("#render", function() {
       var view;
 
       beforeEach(function() {
         view = mountTestComponent();
       });
 
       describe("Empty room message", function() {
--- a/browser/components/migration/content/aboutWelcomeBack.xhtml
+++ b/browser/components/migration/content/aboutWelcomeBack.xhtml
@@ -61,17 +61,19 @@
             <treecol cycler="true" id="restore" type="checkbox" label="&restorepage.restoreHeader;"/>
             <splitter class="tree-splitter"/>
             <treecol primary="true" id="title" label="&restorepage.listHeader;" flex="1"/>
           </treecols>
           <treechildren flex="1"/>
         </tree>
 
         <hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="button-container">
-          <button class="primary" label="&welcomeback2.restoreButton;"
+          <button class="primary"
+                  id="errorTryAgain"
+                  label="&welcomeback2.restoreButton;"
                   accesskey="&welcomeback2.restoreButton.access;"
                   oncommand="restoreSession();"/>
         </hbox>
 
         <input type="text" id="sessionData" style="display: none;"/>
       </div>
     </div>
   </body>
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -133,16 +133,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/FormValidationHandler.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
                                   "resource://gre/modules/WebChannel.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
                                   "resource:///modules/ReaderParent.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "AddonWatcher",
+                                  "resource://gre/modules/AddonWatcher.jsm");
+
 const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
 const PREF_PLUGINS_UPDATEURL  = "plugins.update.url";
 
 // Seconds of idle before trying to create a bookmarks backup.
 const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 8 * 60;
 // Minimum interval between backups.  We try to not create more than one backup
 // per interval.
 const BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS = 1;
@@ -559,16 +562,86 @@ BrowserGlue.prototype = {
   },
 
   _onAppDefaults: function BG__onAppDefaults() {
     // apply distribution customizations (prefs)
     // other customizations are applied in _finalUIStartup()
     this._distributionCustomizer.applyPrefDefaults();
   },
 
+  _notifySlowAddon: function BG_notifySlowAddon(addonId) {
+    let addonCallback = function(addon) {
+      if (!addon) {
+        Cu.reportError("couldn't look up addon: " + addonId);
+        return;
+      }
+      let win = RecentWindow.getMostRecentBrowserWindow();
+
+      if (!win) {
+        return;
+      }
+
+      let brandBundle = win.document.getElementById("bundle_brand");
+      let brandShortName = brandBundle.getString("brandShortName");
+      let message = win.gNavigatorBundle.getFormattedString("addonwatch.slow", [addon.name, brandShortName]);
+      let notificationBox = win.document.getElementById("global-notificationbox");
+      let notificationId = 'addon-slow:' + addonId;
+      let notification = notificationBox.getNotificationWithValue(notificationId);
+      if(notification) {
+        notification.label = message;
+      } else {
+        let buttons = [
+          {
+            label: win.gNavigatorBundle.getFormattedString("addonwatch.disable.label", [addon.name]),
+            accessKey: win.gNavigatorBundle.getString("addonwatch.disable.accesskey"),
+            callback: function() {
+              addon.userDisabled = true;
+              if (addon.pendingOperations != addon.PENDING_NONE) {
+                let restartMessage = win.gNavigatorBundle.getFormattedString("addonwatch.restart.message", [addon.name, brandShortName]);
+                let restartButton = [
+                  {
+                    label: win.gNavigatorBundle.getFormattedString("addonwatch.restart.label", [brandShortName]),
+                    accessKey: win.gNavigatorBundle.getString("addonwatch.restart.accesskey"),
+                    callback: function() {
+                      let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+                        .getService(Ci.nsIAppStartup);
+                      appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
+                    }
+                  }
+                ];
+                const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+                notificationBox.appendNotification(restartMessage, "restart-" + addonId, "",
+                                                   priority, restartButton);
+              }
+            }
+          },
+          {
+            label: win.gNavigatorBundle.getString("addonwatch.ignoreSession.label"),
+            accessKey: win.gNavigatorBundle.getString("addonwatch.ignoreSession.accesskey"),
+            callback: function() {
+              AddonWatcher.ignoreAddonForSession(addonId);
+            }
+          },
+          {
+            label: win.gNavigatorBundle.getString("addonwatch.ignorePerm.label"),
+            accessKey: win.gNavigatorBundle.getString("addonwatch.ignorePerm.accesskey"),
+            callback: function() {
+              AddonWatcher.ignoreAddonPermanently(addonId);
+            }
+          },
+        ];
+
+        const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+        notificationBox.appendNotification(message, notificationId, "",
+                                             priority, buttons);
+      }
+    };
+    AddonManager.getAddonByID(addonId, addonCallback);
+  },
+
   // runs on startup, before the first command line handler is invoked
   // (i.e. before the first window is opened)
   _finalUIStartup: function BG__finalUIStartup() {
     this._sanitizer.onStartup();
     // check if we're in safe mode
     if (Services.appinfo.inSafeMode) {
       Services.ww.openWindow(null, "chrome://browser/content/safeMode.xul", 
                              "_blank", "chrome,centerscreen,modal,resizable=no", null);
@@ -607,16 +680,18 @@ BrowserGlue.prototype = {
     LoginManagerParent.init();
     ReaderParent.init();
 
 #ifdef NIGHTLY_BUILD
     Services.prefs.addObserver(POLARIS_ENABLED, this, false);
 #endif
 
     Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
+
+    AddonWatcher.init(this._notifySlowAddon);
   },
 
   _checkForOldBuildUpdates: function () {
     // check for update if our build is old
     if (Services.prefs.getBoolPref("app.update.enabled") &&
         Services.prefs.getBoolPref("app.update.checkInstallTime")) {
 
       let buildID = Services.appinfo.appBuildID;
--- a/browser/components/preferences/cookies.xul
+++ b/browser/components/preferences/cookies.xul
@@ -26,17 +26,17 @@
                 src="chrome://browser/locale/preferences/preferences.properties"/>
 
   <keyset>
     <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
     <key key="&focusSearch1.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/>
     <key key="&focusSearch2.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/>
   </keyset>
 
-  <vbox flex="1" class="contentPane">
+  <vbox flex="1" class="contentPane largeDialogContainer">
     <hbox align="center">
       <label accesskey="&filter.accesskey;" control="filter">&filter.label;</label>
       <textbox type="search" id="filter" flex="1"
                aria-controls="cookiesList"
                oncommand="gCookiesWindow.filter();"/>
     </hbox>
     <separator class="thin"/>
     <label control="cookiesList" id="cookiesIntro" value="&cookiesonsystem.label;"/>
--- a/browser/components/preferences/fonts.xul
+++ b/browser/components/preferences/fonts.xul
@@ -17,16 +17,17 @@
             title="&fontsDialog.title;"
             dlgbuttons="accept,cancel,help"
             ondialoghelp="openPrefsHelp()"
             style="">
 
   <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
 
   <prefpane id="FontsDialogPane"
+            class="largeDialogContainer"
             helpTopic="prefs-fonts-and-colors">
   
     <preferences id="fontPreferences">
       <preference id="font.language.group"  name="font.language.group"  type="wstring"/>
       <preference id="browser.display.use_document_fonts"
                   name="browser.display.use_document_fonts"
                   type="int"/>
       <preference id="intl.charset.fallback.override" name="intl.charset.fallback.override" type="string"/>
--- a/browser/components/preferences/in-content/subdialogs.js
+++ b/browser/components/preferences/in-content/subdialogs.js
@@ -32,16 +32,22 @@ let gSubDialog = {
     chromeBrowser.addEventListener("DOMTitleChanged", this.updateTitle, true);
 
     // Similarly DOMFrameContentLoaded only fires on the top window
     window.addEventListener("DOMFrameContentLoaded", this._onContentLoaded.bind(this), true);
 
     // Wait for the stylesheets injected during DOMContentLoaded to load before showing the dialog
     // otherwise there is a flicker of the stylesheet applying.
     this._frame.addEventListener("load", this._onLoad.bind(this));
+
+    chromeBrowser.addEventListener("unload", function(aEvent) {
+      if (aEvent.target.location.href != "about:blank") {
+        this.close();
+      }
+    }.bind(this), true);
   },
 
   uninit: function() {
     let chromeBrowser = window.QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIWebNavigation)
                               .QueryInterface(Ci.nsIDocShell)
                               .chromeEventHandler;
     chromeBrowser.removeEventListener("DOMTitleChanged", gSubDialog.updateTitle, true);
@@ -87,16 +93,18 @@ let gSubDialog = {
     }
 
     this._overlay.style.visibility = "";
     // Clear the sizing inline styles.
     this._frame.removeAttribute("style");
     // Clear the sizing attributes
     this._box.removeAttribute("width");
     this._box.removeAttribute("height");
+    this._box.style.removeProperty("min-height");
+    this._box.style.removeProperty("min-width");
 
     setTimeout(() => {
       // Unload the dialog after the event listeners run so that the load of about:blank isn't
       // cancelled by the ESC <key>.
       this._frame.loadURI("about:blank");
     }, 0);
   },
 
@@ -139,20 +147,50 @@ let gSubDialog = {
 
   _onLoad: function(aEvent) {
     if (aEvent.target.contentWindow.location == "about:blank")
       return;
 
     // Do this on load to wait for the CSS to load and apply before calculating the size.
     let docEl = this._frame.contentDocument.documentElement;
 
-    // padding-bottom doesn't seem to be included in the scrollHeight of the document element in XUL
-    // so add it ourselves.
-    let paddingBottom = parseFloat(this._frame.contentWindow.getComputedStyle(docEl).paddingBottom);
+    let groupBoxTitle = document.getAnonymousElementByAttribute(this._box, "class", "groupbox-title");
+    let groupBoxTitleHeight = groupBoxTitle.clientHeight +
+                              parseFloat(getComputedStyle(groupBoxTitle).borderBottomWidth);
+
+    let groupBoxBody = document.getAnonymousElementByAttribute(this._box, "class", "groupbox-body");
+    let boxVerticalPadding = 2 * parseFloat(getComputedStyle(groupBoxBody).paddingTop);
+    let boxHorizontalPadding = 2 * parseFloat(getComputedStyle(groupBoxBody).paddingLeft);
+    let frameWidth = docEl.style.width || docEl.scrollWidth + "px";
+    let frameHeight = docEl.style.height || docEl.scrollHeight + "px";
+    let boxVerticalBorder = 2 * parseFloat(getComputedStyle(this._box).borderTopWidth);
+    let boxHorizontalBorder = 2 * parseFloat(getComputedStyle(this._box).borderLeftWidth);
+
+    let frameRect = this._frame.getBoundingClientRect();
+    let boxRect = this._box.getBoundingClientRect();
+    let frameSizeDifference = (frameRect.top - boxRect.top) + (boxRect.bottom - frameRect.bottom);
 
-    this._frame.style.width = docEl.style.width || docEl.scrollWidth + "px";
-    this._frame.style.height = docEl.style.height || (docEl.scrollHeight + paddingBottom) + "px";
+    // Now check if the frame height we calculated is possible at this window size,
+    // accounting for titlebar, padding/border and some spacing.
+    let maxHeight = window.innerHeight - frameSizeDifference - 30;
+    if (frameHeight > maxHeight) {
+      // If not, we should probably let the dialog scroll:
+      frameHeight = maxHeight;
+      let containers = this._frame.contentDocument.querySelectorAll('.largeDialogContainer');
+      for (let container of containers) {
+        container.classList.add("doScroll");
+      }
+    }
+
+    this._frame.style.width = frameWidth;
+    this._frame.style.height = frameHeight;
+    this._box.style.minHeight = "calc(" +
+                                (boxVerticalBorder + groupBoxTitleHeight + boxVerticalPadding) +
+                                "px + " + frameHeight + ")";
+    this._box.style.minWidth = "calc(" +
+                               (boxHorizontalBorder + boxHorizontalPadding) +
+                               "px + " + frameWidth + ")";
 
     this._overlay.style.visibility = "visible";
     this._frame.focus();
     this._overlay.style.opacity = ""; // XXX: focus hack continued from _onContentLoaded
   },
 };
--- a/browser/components/preferences/in-content/tests/browser_subdialogs.js
+++ b/browser/components/preferences/in-content/tests/browser_subdialogs.js
@@ -71,17 +71,17 @@ let gTests = [{
     let dialog = yield dialogPromise;
 
     let closingPromise = promiseDialogClosing(dialog);
 
     info("cancelling the dialog");
     dialog.document.documentElement.cancelDialog();
 
     let closingEvent = yield closingPromise;
-    ise(closingEvent.detail.button, "cancel", "closing event should indicate button was 'accept'");
+    ise(closingEvent.detail.button, "cancel", "closing event should indicate button was 'cancel'");
 
     yield deferredClose.promise;
     ise(rv.acceptCount, 0, "return value should NOT have been updated");
   },
 },
 {
   desc: "Check window.close on the dialog",
   run: function* () {
@@ -114,16 +114,36 @@ let gTests = [{
     yield EventUtils.synthesizeMouseAtCenter(content.document.getElementById("dialogClose"), {},
                                              content.window);
 
     yield deferredClose.promise;
     ise(rv.acceptCount, 0, "return value should NOT have been updated");
   },
 },
 {
+  desc: "Check that 'back' navigation will close the dialog",
+  run: function* () {
+    let rv = { acceptCount: 0 };
+    let deferredClose = Promise.defer();
+    let dialogPromise = openAndLoadSubDialog(gDialogURL, null, rv,
+                                             (aEvent) => dialogClosingCallback(deferredClose, aEvent));
+    let dialog = yield dialogPromise;
+
+    // XXX Without the call to promiseDialogClosing the test causes
+    //     intermittent failures in browser_change_app_handler.js.
+    let unusedClosingPromise = promiseDialogClosing(dialog);
+
+    info("cancelling the dialog");
+    content.gSubDialog._frame.goBack();
+
+    yield deferredClose.promise;
+    ise(rv.acceptCount, 0, "return value should NOT have been updated");
+  },
+},
+{
   desc: "Hitting escape in the dialog",
   run: function* () {
     let rv = { acceptCount: 0 };
     let deferredClose = Promise.defer();
     let dialogPromise = openAndLoadSubDialog(gDialogURL, null, rv,
                                              (aEvent) => dialogClosingCallback(deferredClose, aEvent));
     let dialog = yield dialogPromise;
 
--- a/browser/components/preferences/languages.xul
+++ b/browser/components/preferences/languages.xul
@@ -17,16 +17,17 @@
             title="&languages.customize.Header;"
             dlgbuttons="accept,cancel,help"
             ondialoghelp="openPrefsHelp()"
             style="width: &window.width;;">
 
   <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
 
   <prefpane id="LanguagesDialogPane"
+            class="largeDialogContainer"
             onpaneload="gLanguagesDialog.init();"
             helpTopic="prefs-languages">
 
     <preferences>
       <preference id="intl.accept_languages" name="intl.accept_languages" type="wstring"/>
       <preference id="pref.browser.language.disable_button.up"
                   name="pref.browser.language.disable_button.up"
                   type="bool"/>
--- a/browser/components/preferences/permissions.xul
+++ b/browser/components/preferences/permissions.xul
@@ -24,17 +24,17 @@
 
   <stringbundle id="bundlePreferences"
                 src="chrome://browser/locale/preferences/preferences.properties"/>
 
   <keyset>
     <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
   </keyset>
 
-  <vbox class="contentPane" flex="1">
+  <vbox class="contentPane largeDialogContainer" flex="1">
     <description id="permissionsText" control="url"/>
     <separator class="thin"/>
     <label id="urlLabel" control="url" value="&address.label;" accesskey="&address.accesskey;"/>
     <hbox align="start">
       <textbox id="url" flex="1" 
                oninput="gPermissionManager.onHostInput(event.target);"
                onkeypress="gPermissionManager.onHostKeyPress(event);"/>
     </hbox>
--- a/browser/components/preferences/sanitize.xul
+++ b/browser/components/preferences/sanitize.xul
@@ -77,18 +77,18 @@
           </row>
         </rows>
       </grid>
     </groupbox>
     <groupbox orient="horizontal">
       <caption label="&dataSection.label;"/>
       <grid flex="1">
         <columns>
-          <column dialogWidth="&column.width2;"
-                  subdialogWidth="&inContentColumn.width;"/>
+          <column dialogWidth="&sanitizePrefs2.column.width;"
+                  subdialogWidth="&sanitizePrefs2.inContent.column.width;"/>
           <column flex="1"/>
         </columns>
         <rows>
           <row>
             <checkbox label="&itemPasswords.label;"
                       accesskey="&itemPasswords.accesskey;"
                       preference="privacy.clearOnShutdown.passwords"/>
             <checkbox label="&itemOfflineApps.label;"
--- a/browser/components/preferences/translation.xul
+++ b/browser/components/preferences/translation.xul
@@ -23,55 +23,57 @@
 
   <stringbundle id="bundlePreferences"
                 src="chrome://browser/locale/preferences/preferences.properties"/>
 
   <keyset>
     <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
   </keyset>
 
-  <vbox class="contentPane" flex="1">
-    <label id="languagesLabel" control="permissionsTree">&noTranslationForLanguages.label;</label>
-    <separator class="thin"/>
-    <tree id="languagesTree" flex="1" style="height: 12em;"
-          hidecolumnpicker="true"
-          onkeypress="gTranslationExceptions.onLanguageKeyPress(event)"
-          onselect="gTranslationExceptions.onLanguageSelected();">
-      <treecols>
-        <treecol id="languageCol" label="&treehead.languageName.label;" flex="1"/>
-      </treecols>
-      <treechildren/>
-    </tree>
-  </vbox>
-  <hbox align="end">
-    <hbox class="actionButtons" flex="1">
-      <button id="removeLanguage" disabled="true"
-              accesskey="&removeLanguage.accesskey;"
-              icon="remove" label="&removeLanguage.label;"
-              oncommand="gTranslationExceptions.onLanguageDeleted();"/>
-      <button id="removeAllLanguages"
-              icon="clear" label="&removeAllLanguages.label;"
-              accesskey="&removeAllLanguages.accesskey;"
-              oncommand="gTranslationExceptions.onAllLanguagesDeleted();"/>
-      <spacer flex="1"/>
+  <vbox class="largeDialogContainer">
+    <vbox class="contentPane" flex="1">
+      <label id="languagesLabel" control="permissionsTree">&noTranslationForLanguages.label;</label>
+      <separator class="thin"/>
+      <tree id="languagesTree" flex="1" style="height: 12em;"
+            hidecolumnpicker="true"
+            onkeypress="gTranslationExceptions.onLanguageKeyPress(event)"
+            onselect="gTranslationExceptions.onLanguageSelected();">
+        <treecols>
+          <treecol id="languageCol" label="&treehead.languageName.label;" flex="1"/>
+        </treecols>
+        <treechildren/>
+      </tree>
+    </vbox>
+    <hbox align="end">
+      <hbox class="actionButtons" flex="1">
+        <button id="removeLanguage" disabled="true"
+                accesskey="&removeLanguage.accesskey;"
+                icon="remove" label="&removeLanguage.label;"
+                oncommand="gTranslationExceptions.onLanguageDeleted();"/>
+        <button id="removeAllLanguages"
+                icon="clear" label="&removeAllLanguages.label;"
+                accesskey="&removeAllLanguages.accesskey;"
+                oncommand="gTranslationExceptions.onAllLanguagesDeleted();"/>
+        <spacer flex="1"/>
+      </hbox>
     </hbox>
-  </hbox>
-  <separator/>
-  <vbox class="contentPane" flex="1">
-    <label id="languagesLabel" control="permissionsTree">&noTranslationForSites.label;</label>
-    <separator class="thin"/>
-    <tree id="sitesTree" flex="1" style="height: 12em;"
-          hidecolumnpicker="true"
-          onkeypress="gTranslationExceptions.onSiteKeyPress(event)"
-          onselect="gTranslationExceptions.onSiteSelected();">
-      <treecols>
-        <treecol id="siteCol" label="&treehead.siteName.label;" flex="1"/>
-      </treecols>
-      <treechildren/>
-    </tree>
+    <separator/>
+    <vbox class="contentPane" flex="1">
+      <label id="languagesLabel" control="permissionsTree">&noTranslationForSites.label;</label>
+      <separator class="thin"/>
+      <tree id="sitesTree" flex="1" style="height: 12em;"
+            hidecolumnpicker="true"
+            onkeypress="gTranslationExceptions.onSiteKeyPress(event)"
+            onselect="gTranslationExceptions.onSiteSelected();">
+        <treecols>
+          <treecol id="siteCol" label="&treehead.siteName.label;" flex="1"/>
+        </treecols>
+        <treechildren/>
+      </tree>
+    </vbox>
   </vbox>
   <hbox align="end">
     <hbox class="actionButtons" flex="1">
       <button id="removeSite" disabled="true"
               accesskey="&removeSite.accesskey;"
               icon="remove" label="&removeSite.label;"
               oncommand="gTranslationExceptions.onSiteDeleted();"/>
       <button id="removeAllSites"
--- a/browser/components/search/test/browser_hiddenOneOffs_cleanup.js
+++ b/browser/components/search/test/browser_hiddenOneOffs_cleanup.js
@@ -33,19 +33,19 @@ add_task(function* test_remove() {
   info("Removing testEngine_dupe.xml");
   Services.search.removeEngine(Services.search.getEngineByName("FooDupe"));
 
   let hiddenOneOffs =
     Services.prefs.getCharPref("browser.search.hiddenOneOffs").split(",");
 
   is(hiddenOneOffs.length, 1,
      "hiddenOneOffs has the correct engine count post removal.");
-  is(hiddenOneOffs.includes("FooDupe"), false,
+  is(hiddenOneOffs.some(x => x == "FooDupe"), false,
      "Removed Engine is not in hiddenOneOffs after removal");
-  is(hiddenOneOffs.includes("Foo"), true,
+  is(hiddenOneOffs.some(x => x == "Foo"), true,
      "Current hidden engine is not affected by removal.");
 
   info("Removing testEngine.xml");
   Services.search.removeEngine(Services.search.getEngineByName("Foo"));
 
   is(Services.prefs.getCharPref("browser.search.hiddenOneOffs"), "",
      "hiddenOneOffs is empty after removing all hidden engines.");
 });
@@ -56,19 +56,19 @@ add_task(function* test_add() {
   Services.prefs.setCharPref("browser.search.hiddenOneOffs", testPref);
   yield promiseNewEngine("testEngine_dupe.xml");
 
   let hiddenOneOffs =
     Services.prefs.getCharPref("browser.search.hiddenOneOffs").split(",");
 
   is(hiddenOneOffs.length, 1,
      "hiddenOneOffs has the correct number of hidden engines present post add.");
-  is(hiddenOneOffs.includes("FooDupe"), false,
+  is(hiddenOneOffs.some(x => x == "FooDupe"), false,
      "Added engine is not present in hidden list.");
-  is(hiddenOneOffs.includes("Foo"), true,
+  is(hiddenOneOffs.some(x => x == "Foo"), true,
      "Adding an engine does not remove engines from hidden list.");
 });
 
 registerCleanupFunction(() => {
   info("Removing testEngine.xml");
   Services.search.removeEngine(Services.search.getEngineByName("Foo"));
   info("Removing testEngine_dupe.xml");
   Services.search.removeEngine(Services.search.getEngineByName("FooDupe"));
--- a/browser/devtools/animationinspector/test/head.js
+++ b/browser/devtools/animationinspector/test/head.js
@@ -154,17 +154,22 @@ let openAnimationInspector = Task.async(
   inspector.sidebar.select("animationinspector");
 
   info("Waiting for the inspector and sidebar to be ready");
   yield promise.all(initPromises);
 
   let win = inspector.sidebar.getWindowForTab("animationinspector");
   let {AnimationsController, AnimationsPanel} = win;
 
-  yield AnimationsPanel.once(AnimationsPanel.PANEL_INITIALIZED);
+  info("Waiting for the animation controller and panel to be ready");
+  if (AnimationsPanel.initialized) {
+    yield AnimationsPanel.initialized;
+  } else {
+    yield AnimationsPanel.once(AnimationsPanel.PANEL_INITIALIZED);
+  }
 
   return {
     toolbox: toolbox,
     inspector: inspector,
     controller: AnimationsController,
     panel: AnimationsPanel,
     window: win
   };
--- a/browser/devtools/netmonitor/netmonitor.xul
+++ b/browser/devtools/netmonitor/netmonitor.xul
@@ -494,30 +494,30 @@
                         flex="1">
                     <vbox id="security-info-connection"
                           class="tabpanel-summary-container">
                       <label class="plain tabpanel-summary-label"
                              value="&netmonitorUI.security.connection;"/>
                       <vbox class="security-info-section">
                         <hbox id="security-protocol-version"
                               class="tabpanel-summary-container"
-                              align="center">
+                              align="baseline">
                           <label class="plain tabpanel-summary-label"
                                  value="&netmonitorUI.security.protocolVersion;"/>
                           <label id="security-protocol-version-value"
                                  class="plain tabpanel-summary-value devtools-monospace"
                                  crop="end"
                                  flex="1"/>
                           <image class="security-warning-icon"
                                  id="security-warning-sslv3"
                                  tooltiptext="&netmonitorUI.security.warning.sslv3;" />
                         </hbox>
                         <hbox id="security-ciphersuite"
                               class="tabpanel-summary-container"
-                              align="center">
+                              align="baseline">
                           <label class="plain tabpanel-summary-label"
                                  value="&netmonitorUI.security.cipherSuite;"/>
                           <label id="security-ciphersuite-value"
                                  class="plain tabpanel-summary-value devtools-monospace"
                                  crop="end"
                                  flex="1"/>
                           <image class="security-warning-icon"
                                  id="security-warning-cipher"
@@ -527,27 +527,27 @@
                     </vbox>
                     <vbox id="security-info-domain"
                           class="tabpanel-summary-container">
                       <label class="plain tabpanel-summary-label"
                              id="security-info-host-header"/>
                       <vbox class="security-info-section">
                         <hbox id="security-http-strict-transport-security"
                               class="tabpanel-summary-container"
-                              align="center">
+                              align="baseline">
                           <label class="plain tabpanel-summary-label"
                                  value="&netmonitorUI.security.hsts;"/>
                           <label id="security-http-strict-transport-security-value"
                                  class="plain tabpanel-summary-value devtools-monospace"
                                  crop="end"
                                  flex="1"/>
                         </hbox>
                         <hbox id="security-public-key-pinning"
                               class="tabpanel-summary-container"
-                              align="center">
+                              align="baseline">
                           <label class="plain tabpanel-summary-label"
                                  value="&netmonitorUI.security.hpkp;"/>
                           <label id="security-public-key-pinning-value"
                                  class="plain tabpanel-summary-value devtools-monospace"
                                  crop="end"
                                  flex="1"/>
                         </hbox>
                       </vbox>
@@ -558,116 +558,116 @@
                                value="&netmonitorUI.security.certificate;"/>
                       <vbox class="security-info-section">
                         <vbox class="tabpanel-summary-container">
                           <label class="plain tabpanel-summary-label"
                                  value="&certmgr.subjectinfo.label;" flex="1"/>
                         </vbox>
                         <vbox class="security-info-section">
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.cn;:"/>
                             <label id="security-cert-subject-cn"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.o;:"/>
                             <label id="security-cert-subject-o"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.ou;:"/>
                             <label id="security-cert-subject-ou"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                         </vbox>
                         <vbox class="tabpanel-summary-container">
                           <label class="plain tabpanel-summary-label"
                                  value="&certmgr.issuerinfo.label;" flex="1"/>
                         </vbox>
                         <vbox class="security-info-section">
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.cn;:"/>
                             <label id="security-cert-issuer-cn"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.o;:"/>
                             <label id="security-cert-issuer-o"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.ou;:"/>
                             <label id="security-cert-issuer-ou"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                         </vbox>
                         <vbox class="tabpanel-summary-container">
                           <label class="plain tabpanel-summary-label"
                                  value="&certmgr.periodofvalidity.label;" flex="1"/>
                         </vbox>
                         <vbox class="security-info-section">
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.begins;:"/>
                             <label id="security-cert-validity-begins"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.expires;:"/>
                             <label id="security-cert-validity-expires"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                         </vbox>
                         <vbox class="tabpanel-summary-container">
                           <label class="plain tabpanel-summary-label"
                                  value="&certmgr.fingerprints.label;" flex="1"/>
                         </vbox>
                         <vbox class="security-info-section">
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.sha256fingerprint;:"/>
                             <label id="security-cert-sha256-fingerprint"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.sha1fingerprint;:"/>
                             <label id="security-cert-sha1-fingerprint"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                         </vbox>
--- a/browser/devtools/performance/modules/compatibility.js
+++ b/browser/devtools/performance/modules/compatibility.js
@@ -76,17 +76,21 @@ function createMockAllocations () {
  */
 function memoryActorSupported (target) {
   // This `target` property is used only in tests to test
   // instances where the memory actor is not available.
   if (target.TEST_MOCK_MEMORY_ACTOR) {
     return false;
   }
 
-  return !!target.getTrait("memoryActorAllocations");
+  // We need to test that both the root actor has `memoryActorAllocations`,
+  // which is in Gecko 38+, and also that the target has a memory actor. There
+  // are scenarios, like addon debugging, where memoryActorAllocations is available,
+  // but no memory actor (like addon debugging in Gecko 38+)
+  return !!target.getTrait("memoryActorAllocations") && target.hasActor("memory");
 }
 exports.memoryActorSupported = Task.async(memoryActorSupported);
 
 /**
  * Takes a TabTarget, and checks existence of a TimelineActor on
  * the server, or if TEST_MOCK_TIMELINE_ACTOR is to be used.
  *
  * @param {TabTarget} target
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -35,17 +35,17 @@ support-files =
 [browser_perf-front-profiler-03.js]
 [browser_perf-front-profiler-04.js]
 #[browser_perf-front-profiler-05.js] bug 1077464
 #[browser_perf-front-profiler-06.js]
 [browser_perf-front.js]
 [browser_perf-jump-to-debugger-01.js]
 [browser_perf-jump-to-debugger-02.js]
 [browser_perf-options-01.js]
-[browser_perf-options-02.js]
+# [browser_perf-options-02.js] bug 1133230
 [browser_perf-options-invert-call-tree-01.js]
 [browser_perf-options-invert-call-tree-02.js]
 [browser_perf-options-invert-flame-graph-01.js]
 [browser_perf-options-invert-flame-graph-02.js]
 [browser_perf-options-flatten-tree-recursion-01.js]
 [browser_perf-options-flatten-tree-recursion-02.js]
 [browser_perf-options-show-platform-data-01.js]
 [browser_perf-options-show-platform-data-02.js]
--- a/browser/devtools/performance/test/browser_perf-compatibility-02.js
+++ b/browser/devtools/performance/test/browser_perf-compatibility-02.js
@@ -1,33 +1,33 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the recording model is populated correctly when using timeline
- * and memory actor mocks.
+ * and memory actor mocks, and the correct views are shown.
  */
 
 const WAIT_TIME = 1000;
 
 let test = Task.async(function*() {
   let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "performance", {
     TEST_MOCK_MEMORY_ACTOR: true,
     TEST_MOCK_TIMELINE_ACTOR: true
   });
   Services.prefs.setBoolPref(MEMORY_PREF, true);
-  let { EVENTS, gFront, PerformanceController, PerformanceView } = panel.panelWin;
+  let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, JsCallTreeView } = panel.panelWin;
 
   let { memory: memoryMock, timeline: timelineMock } = gFront.getMocksInUse();
   ok(memoryMock, "memory should be mocked.");
   ok(timelineMock, "timeline should be mocked.");
 
-  yield startRecording(panel);
+  yield startRecording(panel, { waitForOverview: false });
   busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
-  yield stopRecording(panel);
+  yield stopRecording(panel, { waitForOverview: false });
 
   let {
     label, duration, markers, frames, memory, ticks, allocations, profile
   } = PerformanceController.getCurrentRecording().getAllData();
 
   is(label, "", "Empty label for mock.");
   is(typeof duration, "number", "duration is a number");
   ok(duration > 0, "duration is not 0");
@@ -53,16 +53,33 @@ let test = Task.async(function*() {
         ok(false, "The sample " + sample.toSource() + " doesn't have a root node.");
       }
     }
   }
 
   ok(sampleCount > 0,
     "At least some samples have been iterated over, checking for root nodes.");
 
+  is($("#overview-pane").hidden, true,
+    "overview pane hidden when timeline mocked.");
+
+  is($("#select-waterfall-view").hidden, true,
+    "waterfall view button hidden when timeline mocked");
+  is($("#select-js-calltree-view").hidden, false,
+    "jscalltree view button not hidden when timeline/memory mocked");
+  is($("#select-js-flamegraph-view").hidden, true,
+    "jsflamegraph view button hidden when timeline mocked");
+  is($("#select-memory-calltree-view").hidden, true,
+    "memorycalltree view button hidden when memory mocked");
+  is($("#select-memory-flamegraph-view").hidden, true,
+    "memoryflamegraph view button hidden when memory mocked");
+
+  ok(DetailsView.isViewSelected(JsCallTreeView),
+    "JS Call Tree view selected by default when timeline/memory mocked.");
+
   yield teardown(panel);
   finish();
 });
 
 function isEmptyArray (array, name) {
   ok(Array.isArray(array), `${name} is an array`);
   is(array.length, 0, `${name} is empty`);
 }
--- a/browser/devtools/performance/test/browser_perf-compatibility-04.js
+++ b/browser/devtools/performance/test/browser_perf-compatibility-04.js
@@ -1,24 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the recording model is populated correctly when using timeline
- * and memory actor mocks.
+ * and memory actor mocks, and that the correct button/overview displays are shown.
  */
 
 const WAIT_TIME = 1000;
 
 let test = Task.async(function*() {
   let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "performance", {
     TEST_MOCK_MEMORY_ACTOR: true
   });
   Services.prefs.setBoolPref(MEMORY_PREF, true);
-  let { EVENTS, gFront, PerformanceController, PerformanceView } = panel.panelWin;
+  let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, WaterfallView } = panel.panelWin;
 
 
   let { memory: memoryMock, timeline: timelineMock } = gFront.getMocksInUse();
   ok(memoryMock, "memory should be mocked.");
   ok(!timelineMock, "timeline should not be mocked.");
 
   yield startRecording(panel);
   yield busyWait(100);
@@ -52,16 +52,33 @@ let test = Task.async(function*() {
         ok(false, "The sample " + sample.toSource() + " doesn't have a root node.");
       }
     }
   }
 
   ok(sampleCount > 0,
     "At least some samples have been iterated over, checking for root nodes.");
 
+  is($("#overview-pane").hidden, false,
+    "overview pane not hidden when only memory mocked.");
+
+  is($("#select-waterfall-view").hidden, false,
+    "waterfall view button not hidden when memory mocked");
+  is($("#select-js-calltree-view").hidden, false,
+    "jscalltree view button not hidden when memory mocked");
+  is($("#select-js-flamegraph-view").hidden, false,
+    "jsflamegraph view button not hidden when memory mocked");
+  is($("#select-memory-calltree-view").hidden, true,
+    "memorycalltree view button hidden when memory mocked");
+  is($("#select-memory-flamegraph-view").hidden, true,
+    "memoryflamegraph view button hidden when memory mocked");
+
+  ok(DetailsView.isViewSelected(WaterfallView),
+    "Waterfall view selected by default when memory mocked.");
+
   yield teardown(panel);
   finish();
 });
 
 function isEmptyArray (array, name) {
   ok(Array.isArray(array), `${name} is an array`);
   is(array.length, 0, `${name} is empty`);
 }
--- a/browser/devtools/performance/test/head.js
+++ b/browser/devtools/performance/test/head.js
@@ -261,17 +261,17 @@ function command (button) {
 function click (win, button) {
   EventUtils.sendMouseEvent({ type: "click" }, button, win);
 }
 
 function mousedown (win, button) {
   EventUtils.sendMouseEvent({ type: "mousedown" }, button, win);
 }
 
-function* startRecording(panel) {
+function* startRecording(panel, options={}) {
   let win = panel.panelWin;
   let clicked = panel.panelWin.PerformanceView.once(win.EVENTS.UI_START_RECORDING);
   let willStart = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_WILL_START);
   let hasStarted = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_STARTED);
   let button = win.$("#main-record-button");
 
   ok(!button.hasAttribute("checked"),
     "The record button should not be checked yet.");
@@ -285,31 +285,31 @@ function* startRecording(panel) {
     "The record button should now be checked.");
   ok(button.hasAttribute("locked"),
     "The record button should be locked.");
 
   yield willStart;
   let stateChanged = once(win.PerformanceView, win.EVENTS.UI_STATE_CHANGED);
 
   yield hasStarted;
-  let overviewRendered = once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED);
+  let overviewRendered = options.waitForOverview ? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED) : Promise.resolve();
 
   yield stateChanged;
   yield overviewRendered;
 
   is(win.PerformanceView.getState(), "recording",
     "The current state is 'recording'.");
 
   ok(button.hasAttribute("checked"),
     "The record button should still be checked.");
   ok(!button.hasAttribute("locked"),
     "The record button should not be locked.");
 }
 
-function* stopRecording(panel) {
+function* stopRecording(panel, options={}) {
   let win = panel.panelWin;
   let clicked = panel.panelWin.PerformanceView.once(win.EVENTS.UI_STOP_RECORDING);
   let willStop = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_WILL_STOP);
   let hasStopped = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_STOPPED);
   let button = win.$("#main-record-button");
 
   ok(button.hasAttribute("checked"),
     "The record button should already be checked.");
@@ -323,17 +323,17 @@ function* stopRecording(panel) {
     "The record button should not be checked.");
   ok(button.hasAttribute("locked"),
     "The record button should be locked.");
 
   yield willStop;
   let stateChanged = once(win.PerformanceView, win.EVENTS.UI_STATE_CHANGED);
 
   yield hasStopped;
-  let overviewRendered = once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED);
+  let overviewRendered = options.waitForOverview ? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED) : Promise.resolve();
 
   yield stateChanged;
   yield overviewRendered;
 
   is(win.PerformanceView.getState(), "recorded",
     "The current state is 'recorded'.");
 
   ok(!button.hasAttribute("checked"),
--- a/browser/devtools/performance/views/details.js
+++ b/browser/devtools/performance/views/details.js
@@ -1,29 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const DEFAULT_DETAILS_SUBVIEW = "waterfall";
-
 /**
  * Details view containing profiler call tree and markers waterfall. Manages
  * subviews and toggles visibility between them.
  */
 let DetailsView = {
   /**
    * Name to node+object mapping of subviews.
    */
   components: {
-    "waterfall": { id: "waterfall-view", view: WaterfallView },
+    "waterfall": { id: "waterfall-view", view: WaterfallView, requires: ["timeline"] },
     "js-calltree": { id: "js-calltree-view", view: JsCallTreeView },
-    "js-flamegraph": { id: "js-flamegraph-view", view: JsFlameGraphView },
-    "memory-calltree": { id: "memory-calltree-view", view: MemoryCallTreeView, pref: "enable-memory" },
-    "memory-flamegraph": { id: "memory-flamegraph-view", view: MemoryFlameGraphView, pref: "enable-memory" }
+    "js-flamegraph": { id: "js-flamegraph-view", view: JsFlameGraphView, requires: ["timeline"] },
+    "memory-calltree": { id: "memory-calltree-view", view: MemoryCallTreeView, requires: ["memory"], pref: "enable-memory" },
+    "memory-flamegraph": { id: "memory-flamegraph-view", view: MemoryFlameGraphView, requires: ["memory", "timeline"], pref: "enable-memory" }
   },
 
   /**
    * Sets up the view with event binding, initializes subviews.
    */
   initialize: Task.async(function *() {
     this.el = $("#details-pane");
     this.toolbar = $("#performance-toolbar-controls-detail-views");
@@ -31,17 +29,17 @@ let DetailsView = {
     this._onViewToggle = this._onViewToggle.bind(this);
     this._onRecordingStoppedOrSelected = this._onRecordingStoppedOrSelected.bind(this);
     this.setAvailableViews = this.setAvailableViews.bind(this);
 
     for (let button of $$("toolbarbutton[data-view]", this.toolbar)) {
       button.addEventListener("command", this._onViewToggle);
     }
 
-    yield this.selectView(DEFAULT_DETAILS_SUBVIEW);
+    yield this.selectDefaultView();
     yield this.setAvailableViews();
 
     PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
     PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
     PerformanceController.on(EVENTS.PREF_CHANGED, this.setAvailableViews);
   }),
 
   /**
@@ -57,32 +55,37 @@ let DetailsView = {
     }
 
     PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
     PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
     PerformanceController.off(EVENTS.PREF_CHANGED, this.setAvailableViews);
   }),
 
   /**
-   * Sets the possible views based off of prefs by hiding/showing the
+   * Sets the possible views based off of prefs and server actor support by hiding/showing the
    * buttons that select them and going to default view if currently selected.
    * Called when a preference changes in `devtools.performance.ui.`.
    */
   setAvailableViews: Task.async(function* () {
-    for (let [name, { view, pref }] of Iterator(this.components)) {
+    let mocks = gFront.getMocksInUse();
+    for (let [name, { view, pref, requires }] of Iterator(this.components)) {
       let recording = PerformanceController.getCurrentRecording();
 
       let isRecorded = recording && !recording.isRecording();
+      // View is enabled view prefs
       let isEnabled = !pref || PerformanceController.getPref(pref);
-      $(`toolbarbutton[data-view=${name}]`).hidden = !isRecorded || !isEnabled;
+      // View is supported by the server actor, and the requried actor is not being mocked
+      let isSupported = !requires || requires.every(r => !mocks[r]);
+
+      $(`toolbarbutton[data-view=${name}]`).hidden = !isRecorded || !(isEnabled && isSupported);
 
       // If the view is currently selected and not enabled, go back to the
       // default view.
       if (!isEnabled && this.isViewSelected(view)) {
-        yield this.selectView(DEFAULT_DETAILS_SUBVIEW);
+        yield this.selectDefaultView();
       }
     }
   }),
 
   /**
    * Select one of the DetailView's subviews to be rendered,
    * hiding the others.
    *
@@ -102,16 +105,32 @@ let DetailsView = {
         button.removeAttribute("checked");
       }
     }
 
     this.emit(EVENTS.DETAILS_VIEW_SELECTED, viewName);
   }),
 
   /**
+   * Selects a default view based off of protocol support
+   * and preferences enabled.
+   */
+  selectDefaultView: function () {
+    let { timeline: mockTimeline } = gFront.getMocksInUse();
+    // If timelines are mocked, the first view available is the js-calltree.
+    if (mockTimeline) {
+      return this.selectView("js-calltree");
+    } else {
+      // In every other scenario with preferences and mocks, waterfall will
+      // be the default view.
+      return this.selectView("waterfall");
+    }
+  },
+
+  /**
    * Checks if the provided view is currently selected.
    *
    * @param object viewObject
    * @return boolean
    */
   isViewSelected: function(viewObject) {
     let selectedPanel = this.el.selectedPanel;
     let selectedId = selectedPanel.id;
--- a/browser/devtools/performance/views/overview.js
+++ b/browser/devtools/performance/views/overview.js
@@ -21,16 +21,19 @@ const MEMORY_GRAPH_HEIGHT = 30; // px
  * View handler for the overview panel's time view, displaying
  * framerate, markers and memory over time.
  */
 let OverviewView = {
   /**
    * Sets up the view with event binding.
    */
   initialize: function () {
+    if (gFront.getMocksInUse().timeline) {
+      this.disable();
+    }
     this._onRecordingWillStart = this._onRecordingWillStart.bind(this);
     this._onRecordingStarted = this._onRecordingStarted.bind(this);
     this._onRecordingWillStop = this._onRecordingWillStop.bind(this);
     this._onRecordingStopped = this._onRecordingStopped.bind(this);
     this._onRecordingSelected = this._onRecordingSelected.bind(this);
     this._onRecordingTick = this._onRecordingTick.bind(this);
     this._onGraphSelecting = this._onGraphSelecting.bind(this);
     this._onPrefChanged = this._onPrefChanged.bind(this);
@@ -56,42 +59,70 @@ let OverviewView = {
     PerformanceController.off(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
     PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
     PerformanceController.off(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
     PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
     PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
   },
 
   /**
+   * Disabled in the event we're using a Timeline mock, so we'll have no
+   * markers, ticks or memory data to show, so just block rendering and hide
+   * the panel.
+   */
+  disable: function () {
+    this._disabled = true;
+    $("#overview-pane").hidden = true;
+  },
+
+  /**
+   * Returns the disabled status.
+   *
+   * @return boolean
+   */
+  isDisabled: function () {
+    return this._disabled;
+  },
+
+  /**
    * Sets the time interval selection for all graphs in this overview.
    *
    * @param object interval
    *        The { starTime, endTime }, in milliseconds.
    */
   setTimeInterval: function(interval, options = {}) {
+    if (this.isDisabled()) {
+      return;
+    }
+
     let recording = PerformanceController.getCurrentRecording();
     if (recording == null) {
       throw new Error("A recording should be available in order to set the selection.");
     }
     let mapStart = () => 0;
     let mapEnd = () => recording.getDuration();
     let selection = { start: interval.startTime, end: interval.endTime };
     this._stopSelectionChangeEventPropagation = options.stopPropagation;
     this.markersOverview.setMappedSelection(selection, { mapStart, mapEnd });
     this._stopSelectionChangeEventPropagation = false;
   },
 
   /**
    * Gets the time interval selection for all graphs in this overview.
    *
    * @return object
-   *         The { starTime, endTime }, in milliseconds.
+   *         The { startTime, endTime }, in milliseconds.
    */
   getTimeInterval: function() {
     let recording = PerformanceController.getCurrentRecording();
+
+    if (this.isDisabled()) {
+      return { startTime: 0, endTime: recording.getDuration() };
+    }
+
     if (recording == null) {
       throw new Error("A recording should be available in order to get the selection.");
     }
     let mapStart = () => 0;
     let mapEnd = () => recording.getDuration();
     let selection = this.markersOverview.getMappedSelection({ mapStart, mapEnd });
     return { startTime: selection.min, endTime: selection.max };
   },
@@ -167,16 +198,19 @@ let OverviewView = {
 
   /**
    * Method for handling all the set up for rendering the overview graphs.
    *
    * @param number resolution
    *        The fps graph resolution. @see Graphs.jsm
    */
   render: Task.async(function *(resolution) {
+    if (this.isDisabled()) {
+      return;
+    }
     let recording = PerformanceController.getCurrentRecording();
     let duration = recording.getDuration();
     let markers = recording.getMarkers();
     let memory = recording.getMemory();
     let timestamps = recording.getTicks();
 
     // Empty or older recordings might yield no markers, memory or timestamps.
     if (markers && (yield this._markersGraphAvailable())) {
--- a/browser/devtools/webide/modules/runtimes.js
+++ b/browser/devtools/webide/modules/runtimes.js
@@ -1,18 +1,18 @@
 /* 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/. */
 
 const {Cu, Ci} = require("chrome");
 const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
-const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
-const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
+const {Connection} = require("devtools/client/connection-manager");
 const {DebuggerServer} = require("resource://gre/modules/devtools/dbg-server.jsm");
+const {Simulators} = require("devtools/webide/simulators");
 const discovery = require("devtools/toolkit/discovery/discovery");
 const EventEmitter = require("devtools/toolkit/event-emitter");
 const promise = require("promise");
 loader.lazyRequireGetter(this, "AuthenticationResult",
   "devtools/toolkit/security/auth", true);
 loader.lazyRequireGetter(this, "DevToolsUtils",
   "devtools/toolkit/DevToolsUtils");
 
@@ -188,36 +188,36 @@ exports.RuntimeScanners = RuntimeScanner
 /* SCANNERS */
 
 let SimulatorScanner = {
 
   _runtimes: [],
 
   enable() {
     this._updateRuntimes = this._updateRuntimes.bind(this);
-    Simulator.on("register", this._updateRuntimes);
-    Simulator.on("unregister", this._updateRuntimes);
+    Simulators.on("updated", this._updateRuntimes);
     this._updateRuntimes();
   },
 
   disable() {
-    Simulator.off("register", this._updateRuntimes);
-    Simulator.off("unregister", this._updateRuntimes);
+    Simulators.off("updated", this._updateRuntimes);
   },
 
   _emitUpdated() {
     this.emit("runtime-list-updated");
   },
 
   _updateRuntimes() {
-    this._runtimes = [];
-    for (let name of Simulator.availableNames()) {
-      this._runtimes.push(new SimulatorRuntime(name));
-    }
-    this._emitUpdated();
+    Simulators.getAll().then(simulators => {
+      this._runtimes = [];
+      for (let simulator of simulators) {
+        this._runtimes.push(new SimulatorRuntime(simulator));
+      }
+      this._emitUpdated();
+    });
   },
 
   scan() {
     return promise.resolve();
   },
 
   listRuntimes: function() {
     return this._runtimes;
@@ -537,38 +537,36 @@ WiFiRuntime.prototype = {
       }
     };
   }
 };
 
 // For testing use only
 exports._WiFiRuntime = WiFiRuntime;
 
-function SimulatorRuntime(name) {
-  this.name = name;
+function SimulatorRuntime(simulator) {
+  this.simulator = simulator;
 }
 
 SimulatorRuntime.prototype = {
   type: RuntimeTypes.SIMULATOR,
   connect: function(connection) {
-    let port = ConnectionManager.getFreeTCPPort();
-    let simulator = Simulator.getByName(this.name);
-    if (!simulator || !simulator.launch) {
-      return promise.reject(new Error("Can't find simulator: " + this.name));
-    }
-    return simulator.launch({port: port}).then(() => {
+    return this.simulator.launch().then(port => {
       connection.host = "localhost";
       connection.port = port;
       connection.keepConnecting = true;
-      connection.once(Connection.Events.DISCONNECTED, simulator.close);
+      connection.once(Connection.Events.DISCONNECTED, e => this.simulator.kill());
       connection.connect();
     });
   },
   get id() {
-    return this.name;
+    return this.simulator.id;
+  },
+  get name() {
+    return this.simulator.name;
   },
 };
 
 // For testing use only
 exports._SimulatorRuntime = SimulatorRuntime;
 
 let gLocalRuntime = {
   type: RuntimeTypes.LOCAL,
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/modules/simulator-process.js
@@ -0,0 +1,272 @@
+/* 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 { Cc, Ci, Cu } = require("chrome");
+
+const Environment = require("sdk/system/environment").env;
+const Subprocess = require("sdk/system/child_process/subprocess");
+const { EventEmitter } = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
+const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+let platform = Services.appShell.hiddenDOMWindow.navigator.platform;
+let OS = "";
+if (platform.indexOf("Win") != -1) {
+  OS = "win32";
+} else if (platform.indexOf("Mac") != -1) {
+  OS = "mac64";
+} else if (platform.indexOf("Linux") != -1) {
+  if (platform.indexOf("x86_64") != -1) {
+    OS = "linux64";
+  } else {
+    OS = "linux32";
+  }
+}
+
+function SimulatorProcess() {}
+SimulatorProcess.prototype = {
+
+  // Check if B2G is running.
+  get isRunning() !!this.process,
+
+  // Start the process and connect the debugger client.
+  run() {
+
+    // Resolve B2G binary.
+    let b2g = this.b2gBinary;
+    if (!b2g || !b2g.exists()) {
+      throw Error("B2G executable not found.");
+    }
+
+    this.once("stdout", function () {
+      if (OS == "mac64") {
+        console.debug("WORKAROUND run osascript to show b2g-desktop window on OS=='mac64'");
+        // Escape double quotes and escape characters for use in AppleScript.
+        let path = b2g.path.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
+
+        Subprocess.call({
+          command: "/usr/bin/osascript",
+          arguments: ["-e", 'tell application "' + path + '" to activate'],
+        });
+      }
+    });
+
+    this.on("stdout", (e, data) => this.log(e, data.trim()));
+    this.on("stderr", (e, data) => this.log(e, data.trim()));
+
+    let environment;
+    if (OS.indexOf("linux") > -1) {
+      environment = ["TMPDIR=" + Services.dirsvc.get("TmpD", Ci.nsIFile).path];
+      if ("DISPLAY" in Environment) {
+        environment.push("DISPLAY=" + Environment.DISPLAY);
+      }
+    }
+
+    // Spawn a B2G instance.
+    this.process = Subprocess.call({
+      command: b2g,
+      arguments: this.args,
+      environment: environment,
+      stdout: data => this.emit("stdout", data),
+      stderr: data => this.emit("stderr", data),
+      // On B2G instance exit, reset tracked process, remote debugger port and
+      // shuttingDown flag, then finally emit an exit event.
+      done: result => {
+        console.log("B2G terminated with " + result.exitCode);
+        this.process = null;
+        this.emit("exit", result.exitCode);
+      }
+    });
+  },
+
+  // Request a B2G instance kill.
+  kill() {
+    let deferred = promise.defer();
+    if (this.process) {
+      this.once("exit", (e, exitCode) => {
+        this.shuttingDown = false;
+        deferred.resolve(exitCode);
+      });
+      if (!this.shuttingDown) {
+        this.shuttingDown = true;
+        this.emit("kill", null);
+        this.process.kill();
+      }
+      return deferred.promise;
+    } else {
+      return promise.resolve(undefined);
+    }
+  },
+
+  // Maybe log output messages.
+  log(level, message) {
+    if (!Services.prefs.getBoolPref("devtools.webide.logSimulatorOutput")) {
+      return;
+    }
+    if (level === "stderr" || level === "error") {
+      console.error(message);
+      return;
+    }
+    console.log(message);
+  },
+
+  // Compute B2G CLI arguments.
+  get args() {
+    let args = [];
+
+    let gaia = this.gaiaProfile;
+    if (!gaia || !gaia.exists()) {
+      throw Error("Gaia profile directory not found.");
+    }
+    args.push("-profile", gaia.path);
+
+    args.push("-start-debugger-server", "" + this.options.port);
+
+    // Ignore eventual zombie instances of b2g that are left over.
+    args.push("-no-remote");
+
+    return args;
+  },
+};
+
+EventEmitter.decorate(SimulatorProcess.prototype);
+
+
+function CustomSimulatorProcess(options) {
+  this.options = options;
+}
+
+let CSPp = CustomSimulatorProcess.prototype = Object.create(SimulatorProcess.prototype);
+
+// Compute B2G binary file handle.
+Object.defineProperty(CSPp, "b2gBinary", {
+  get: function() {
+    let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
+    file.initWithPath(this.options.b2gBinary);
+    return file;
+  }
+});
+
+// Compute Gaia profile file handle.
+Object.defineProperty(CSPp, "gaiaProfile", {
+  get: function() {
+    let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
+    file.initWithPath(this.options.gaiaProfile);
+    return file;
+  }
+});
+
+exports.CustomSimulatorProcess = CustomSimulatorProcess;
+
+
+function AddonSimulatorProcess(addon, options) {
+  this.addon = addon;
+  this.options = options;
+}
+
+let ASPp = AddonSimulatorProcess.prototype = Object.create(SimulatorProcess.prototype);
+
+// Compute B2G binary file handle.
+Object.defineProperty(ASPp, "b2gBinary", {
+  get: function() {
+    let file;
+    try {
+      let pref = "extensions." + this.addon.id + ".customRuntime";
+      file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
+    } catch(e) {}
+
+    if (!file) {
+      file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
+      file.append("b2g");
+      let binaries = {
+        win32: "b2g-bin.exe",
+        mac64: "B2G.app/Contents/MacOS/b2g-bin",
+        linux32: "b2g-bin",
+        linux64: "b2g-bin",
+      };
+      binaries[OS].split("/").forEach(node => file.append(node));
+    }
+    return file;
+  }
+});
+
+// Compute Gaia profile file handle.
+Object.defineProperty(ASPp, "gaiaProfile", {
+  get: function() {
+    let file;
+    try {
+      let pref = "extensions." + this.addon.id + ".gaiaProfile";
+      file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
+    } catch(e) {}
+
+    if (!file) {
+      file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
+      file.append("profile");
+    }
+    return file;
+  }
+});
+
+exports.AddonSimulatorProcess = AddonSimulatorProcess;
+
+
+function OldAddonSimulatorProcess(addon, options) {
+  this.addon = addon;
+  this.options = options;
+}
+
+let OASPp = OldAddonSimulatorProcess.prototype = Object.create(AddonSimulatorProcess.prototype);
+
+// Compute B2G binary file handle.
+Object.defineProperty(OASPp, "b2gBinary", {
+  get: function() {
+    let file;
+    try {
+      let pref = "extensions." + this.addon.id + ".customRuntime";
+      file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
+    } catch(e) {}
+
+    if (!file) {
+      file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
+      let version = this.addon.name.match(/\d+\.\d+/)[0].replace(/\./, "_");
+      file.append("resources");
+      file.append("fxos_" + version + "_simulator");
+      file.append("data");
+      file.append(OS == "linux32" ? "linux" : OS);
+      let binaries = {
+        win32: "b2g/b2g-bin.exe",
+        mac64: "B2G.app/Contents/MacOS/b2g-bin",
+        linux32: "b2g/b2g-bin",
+        linux64: "b2g/b2g-bin",
+      };
+      binaries[OS].split("/").forEach(node => file.append(node));
+    }
+    return file;
+  }
+});
+
+// Compute B2G CLI arguments.
+Object.defineProperty(OASPp, "args", {
+  get: function() {
+    let args = [];
+
+    let gaia = this.gaiaProfile;
+    if (!gaia || !gaia.exists()) {
+      throw Error("Gaia profile directory not found.");
+    }
+    args.push("-profile", gaia.path);
+
+    args.push("-dbgport", "" + this.options.port);
+
+    // Ignore eventual zombie instances of b2g that are left over.
+    args.push("-no-remote");
+
+    return args;
+  }
+});
+
+exports.OldAddonSimulatorProcess = OldAddonSimulatorProcess;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/modules/simulators.js
@@ -0,0 +1,96 @@
+/* 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/. */
+
+const { Cu } = require("chrome");
+const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm");
+const { EventEmitter } = Cu.import("resource://gre/modules/devtools/event-emitter.js");
+const { ConnectionManager } = require("devtools/client/connection-manager");
+const { AddonSimulatorProcess, OldAddonSimulatorProcess } = require("devtools/webide/simulator-process");
+const promise = require("promise");
+
+const SimulatorRegExp = new RegExp(Services.prefs.getCharPref("devtools.webide.simulatorAddonRegExp"));
+
+let Simulators = {
+  // TODO (Bug 1090949) Don't generate this list from installed simulator
+  // addons, but instead implement a persistent list of user-configured
+  // simulators.
+  getAll() {
+    let deferred = promise.defer();
+    AddonManager.getAllAddons(addons => {
+      let simulators = [];
+      for (let addon of addons) {
+        if (SimulatorRegExp.exec(addon.id)) {
+          simulators.push(new Simulator(addon));
+        }
+      }
+      // Sort simulators alphabetically by name.
+      simulators.sort((a, b) => {
+        return a.name.toLowerCase().localeCompare(b.name.toLowerCase())
+      });
+      deferred.resolve(simulators);
+    });
+    return deferred.promise;
+  },
+}
+EventEmitter.decorate(Simulators);
+exports.Simulators = Simulators;
+
+function update() {
+  Simulators.emit("updated");
+}
+AddonManager.addAddonListener({
+  onEnabled: update,
+  onDisabled: update,
+  onInstalled: update,
+  onUninstalled: update
+});
+
+
+function Simulator(addon) {
+  this.addon = addon;
+}
+
+Simulator.prototype = {
+  launch() {
+    // Close already opened simulation.
+    if (this.process) {
+      return this.kill().then(this.launch.bind(this));
+    }
+
+    let options = {
+      port: ConnectionManager.getFreeTCPPort()
+    };
+
+    if (this.version <= "1.3") {
+      // Support older simulator addons.
+      this.process = new OldAddonSimulatorProcess(this.addon, options);
+    } else {
+      this.process = new AddonSimulatorProcess(this.addon, options);
+    }
+    this.process.run();
+
+    return promise.resolve(options.port);
+  },
+
+  kill() {
+    let process = this.process;
+    if (!process) {
+      return promise.resolve();
+    }
+    this.process = null;
+    return process.kill();
+  },
+
+  get id() {
+    return this.addon.id;
+  },
+
+  get name() {
+    return this.addon.name.replace(" Simulator", "");
+  },
+
+  get version() {
+    return this.name.match(/\d+\.\d+/)[0];
+  },
+};
--- a/browser/devtools/webide/moz.build
+++ b/browser/devtools/webide/moz.build
@@ -15,16 +15,18 @@ MOCHITEST_CHROME_MANIFESTS += ['test/chr
 
 EXTRA_JS_MODULES.devtools.webide += [
     'modules/addons.js',
     'modules/app-manager.js',
     'modules/build.js',
     'modules/config-view.js',
     'modules/remote-resources.js',
     'modules/runtimes.js',
+    'modules/simulator-process.js',
+    'modules/simulators.js',
     'modules/tab-store.js',
     'modules/utils.js'
 ]
 
 JS_PREFERENCE_FILES += [
     'webide-prefs.js',
 ]
 
--- a/browser/devtools/webide/webide-prefs.js
+++ b/browser/devtools/webide/webide-prefs.js
@@ -8,23 +8,25 @@ pref("devtools.webide.templatesURL", "ht
 pref("devtools.webide.autoinstallADBHelper", true);
 pref("devtools.webide.autoinstallFxdtAdapters", true);
 pref("devtools.webide.autoConnectRuntime", true);
 pref("devtools.webide.restoreLastProject", true);
 pref("devtools.webide.enableLocalRuntime", false);
 pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
 pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/#VERSION#/#OS#/fxos_#SLASHED_VERSION#_simulator-#OS#-latest.xpi");
 pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozilla.org");
+pref("devtools.webide.simulatorAddonRegExp", "fxos_(.*)_simulator@mozilla\.org$");
 pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
 pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");
 pref("devtools.webide.adaptersAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxdt-adapters/#OS#/fxdt-adapters-#OS#-latest.xpi");
 pref("devtools.webide.adaptersAddonID", "fxdevtools-adapters@mozilla.org");
 pref("devtools.webide.monitorWebSocketURL", "ws://localhost:9000");
 pref("devtools.webide.lastConnectedRuntime", "");
 pref("devtools.webide.lastSelectedProject", "");
+pref("devtools.webide.logSimulatorOutput", false);
 pref("devtools.webide.widget.autoinstall", true);
 #ifdef MOZ_DEV_EDITION
 pref("devtools.webide.widget.enabled", true);
 pref("devtools.webide.widget.inNavbarByDefault", true);
 #else
 pref("devtools.webide.widget.enabled", false);
 pref("devtools.webide.widget.inNavbarByDefault", false);
 #endif
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -832,8 +832,11 @@ just addresses the organization to follo
 <!ENTITY processHang.terminateScript.label        "Stop Script">
 <!ENTITY processHang.terminateScript.accessKey    "S">
 <!ENTITY processHang.debugScript.label            "Debug Script">
 <!ENTITY processHang.debugScript.accessKey        "D">
 <!ENTITY processHang.terminatePlugin.label        "Kill Plugin">
 <!ENTITY processHang.terminatePlugin.accessKey    "P">
 <!ENTITY processHang.terminateProcess.label       "Kill Web Process">
 <!ENTITY processHang.terminateProcess.accessKey   "K">
+
+<!ENTITY emeLearnMoreContextMenu.label            "Learn more about DRM…">
+<!ENTITY emeLearnMoreContextMenu.accesskey        "D">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -35,16 +35,27 @@ xpinstallDisabledButton.accesskey=n
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # Also see https://bugzilla.mozilla.org/show_bug.cgi?id=570012 for mockups
 addonDownloading=Add-on downloading;Add-ons downloading
 addonDownloadCancelled=Add-on download cancelled.;Add-on downloads cancelled.
 addonDownloadRestart=Restart Download;Restart Downloads
 addonDownloadRestart.accessKey=R
 addonDownloadCancelTooltip=Cancel
 
+addonwatch.slow=%S might be making %S run slowly
+addonwatch.disable.label=Disable %S
+addonwatch.disable.accesskey=D
+addonwatch.ignoreSession.label=Ignore for now
+addonwatch.ignoreSession.accesskey=I
+addonwatch.ignorePerm.label=Ignore permanently
+addonwatch.ignorePerm.accesskey=p
+addonwatch.restart.message=To disable %S you must restart %S
+addonwatch.restart.label=Restart %s
+addonwatch.restart.accesskey=R
+
 # LOCALIZATION NOTE (addonsInstalled, addonsInstalledNeedsRestart):
 # Semicolon-separated list of plural forms. See:
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 first add-on's name, #2 number of add-ons, #3 application name
 addonsInstalled=#1 has been installed successfully.;#2 add-ons have been installed successfully.
 addonsInstalledNeedsRestart=#1 will be installed after you restart #3.;#2 add-ons will be installed after you restart #3.
 addonInstallRestartButton=Restart Now
 addonInstallRestartButton.accesskey=R
--- a/browser/modules/BrowserUITelemetry.jsm
+++ b/browser/modules/BrowserUITelemetry.jsm
@@ -664,16 +664,17 @@ this.BrowserUITelemetry = {
     "keywordfield", "searchselect", "shareselect", "frame", "showonlythisframe",
     "openframeintab", "openframe", "reloadframe", "bookmarkframe", "saveframe",
     "printframe", "viewframesource", "viewframeinfo",
     "viewpartialsource-selection", "viewpartialsource-mathml",
     "viewsource", "viewinfo", "spell-check-enabled",
     "spell-add-dictionaries-main", "spell-dictionaries",
     "spell-dictionaries-menu", "spell-add-dictionaries",
     "bidi-text-direction-toggle", "bidi-page-direction-toggle", "inspect",
+    "media-eme-learn-more"
   ]),
 
   _contextMenuInteractions: {},
 
   registerContextMenuInteraction: function(keys, itemID) {
     if (itemID) {
       if (!this._contextMenuItemWhitelist.has(itemID)) {
         itemID = "other-item";
--- a/browser/themes/shared/aboutSessionRestore.css
+++ b/browser/themes/shared/aboutSessionRestore.css
@@ -6,17 +6,17 @@
 
 .title {
   background-image: url("chrome://browser/skin/session-restore.svg");
 }
 
 treechildren::-moz-tree-image(icon),
 treechildren::-moz-tree-image(noicon) {
   padding-right: 2px;
-  margin: 0px 2px;
+  margin: 0 2px;
   width: 16px;
   height: 16px;
 }
 
 treechildren::-moz-tree-image(noicon) {
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
 }
 treechildren::-moz-tree-image(container, noicon) {
--- a/browser/themes/shared/aboutWelcomeBack.css
+++ b/browser/themes/shared/aboutWelcomeBack.css
@@ -15,8 +15,37 @@
 */
 #tabList {
   display: none;
 }
 
 #tabList[available] {
   display: -moz-box;
 }
+
+treechildren::-moz-tree-image(icon),
+treechildren::-moz-tree-image(noicon) {
+  padding-right: 2px;
+  margin: 0 2px;
+  width: 16px;
+  height: 16px;
+}
+
+treechildren::-moz-tree-image(noicon) {
+  list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+treechildren::-moz-tree-image(container, noicon) {
+  list-style-image: url("chrome://browser/skin/aboutSessionRestore-window-icon.png");
+}
+
+treechildren::-moz-tree-image(checked) {
+  list-style-image: url("chrome://global/skin/in-content/check.svg#check");
+}
+treechildren::-moz-tree-image(checked, selected) {
+  list-style-image: url("chrome://global/skin/in-content/check.svg#check-inverted");
+}
+
+treechildren::-moz-tree-image(partial) {
+  list-style-image: url("chrome://global/skin/in-content/check-partial.svg#check-partial");
+}
+treechildren::-moz-tree-image(partial, selected) {
+  list-style-image: url("chrome://global/skin/in-content/check-partial.svg#check-partial-inverted");
+}
\ No newline at end of file
--- a/browser/themes/shared/contextmenu.inc.css
+++ b/browser/themes/shared/contextmenu.inc.css
@@ -74,8 +74,12 @@
   transform: scaleX(-1);
 }
 
 #context-navigation > .menuitem-iconic > .menu-iconic-left > .menu-iconic-icon {
   width: 16px;
   height: 16px;
   margin: 7px;
 }
+
+#context-media-eme-learnmore {
+  list-style-image: url("chrome://browser/skin/drm-icon.svg#chains");
+}
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -345,16 +345,21 @@ description > html|a {
 
 #dialogFrame {
   -moz-box-flex: 1;
   /* Default dialog dimensions */
   height: 20em;
   width: 66ch;
 }
 
+.largeDialogContainer.doScroll {
+  overflow-y: auto;
+  -moz-box-flex: 1;
+}
+
 /**
  * End Dialog
  */
 
 /**
  * Sync migration
  */
 #sync-migrate-upgrade-description {
--- a/browser/themes/shared/tabbrowser/crashed.svg
+++ b/browser/themes/shared/tabbrowser/crashed.svg
@@ -1,16 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 viewBox="22 22 16 16" enable-background="new 22 22 16 16" xml:space="preserve">
-<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="30" y1="23" x2="30" y2="37">
-	<stop  offset="0" style="stop-color:#E63B2E"/>
-	<stop  offset="1" style="stop-color:#C33931"/>
-</linearGradient>
-<circle fill="url(#SVGID_1_)" cx="30" cy="30" r="7"/>
-<g>
-	<path fill="#FFFFFF" d="M31.03,33.304c0,0.6-0.479,1.092-1.091,1.092c-0.6,0-1.079-0.492-1.079-1.092
-		c0-0.588,0.479-1.079,1.079-1.079C30.551,32.225,31.03,32.716,31.03,33.304z M29.171,31.133l-0.24-5.253h2.015l-0.24,5.253H29.171z
-		"/>
-</g>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     viewBox="22 22 16 16">
+  <linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="30" y1="23" x2="30" y2="37">
+    <stop offset="0" style="stop-color: #e63b2e"/>
+    <stop offset="1" style="stop-color: #c33931"/>
+  </linearGradient>
+  <circle fill="url(#gradient)" cx="30" cy="30" r="7"/>
+  <path fill="#fff" d="M31.03,33.304c0,0.6-0.479,1.092-1.091,1.092c-0.6,0-1.079-0.492-1.079-1.092 c0-0.588,0.479-1.079,1.079-1.079C30.551,32.225,31.03,32.716,31.03,33.304z M29.171,31.133l-0.24-5.253h2.015l-0.24,5.253H29.171z"/>
 </svg>
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -703,16 +703,17 @@ GK_ATOM(oncommandupdate, "oncommandupdat
 GK_ATOM(oncomplete, "oncomplete")
 GK_ATOM(oncompositionend, "oncompositionend")
 GK_ATOM(oncompositionstart, "oncompositionstart")
 GK_ATOM(oncompositionupdate, "oncompositionupdate")
 GK_ATOM(onconfigurationchange, "onconfigurationchange")
 GK_ATOM(onconnect, "onconnect")
 GK_ATOM(onconnected, "onconnected")
 GK_ATOM(onconnecting, "onconnecting")
+GK_ATOM(onconnectionstatechanged, "onconnectionstatechanged")
 GK_ATOM(oncontextmenu, "oncontextmenu")
 GK_ATOM(oncopy, "oncopy")
 GK_ATOM(oncurrentchannelchanged, "oncurrentchannelchanged")
 GK_ATOM(oncurrentsourcechanged, "oncurrentsourcechanged")
 GK_ATOM(oncut, "oncut")
 GK_ATOM(ondatachange, "ondatachange")
 GK_ATOM(ondataerror, "ondataerror")
 GK_ATOM(ondblclick, "ondblclick")
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -147,16 +147,20 @@ DOMInterfaces = {
 'BluetoothDevice': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothDevice',
 },
 
 'BluetoothDiscoveryHandle': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothDiscoveryHandle',
 },
 
+'BluetoothGatt': {
+    'nativeType': 'mozilla::dom::bluetooth::BluetoothGatt',
+},
+
 'BluetoothManager': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothManager',
 },
 
 'BluetoothPairingHandle': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothPairingHandle',
 },
 
--- a/dom/bluetooth2/BluetoothCommon.h
+++ b/dom/bluetooth2/BluetoothCommon.h
@@ -191,16 +191,22 @@ extern bool gBluetoothDebugFlag;
 #define DEVICE_UNPAIRED_ID                   "deviceunpaired"
 
 /**
  * When receiving a query about current play status from remote device, we'll
  * dispatch an event.
  */
 #define REQUEST_MEDIA_PLAYSTATUS_ID          "requestmediaplaystatus"
 
+/**
+ * When a remote BLE device gets connected / disconnected, we'll dispatch an
+ * event
+ */
+#define GATT_CONNECTION_STATE_CHANGED_ID     "connectionstatechanged"
+
 // Bluetooth address format: xx:xx:xx:xx:xx:xx (or xx_xx_xx_xx_xx_xx)
 #define BLUETOOTH_ADDRESS_LENGTH 17
 #define BLUETOOTH_ADDRESS_NONE   "00:00:00:00:00:00"
 #define BLUETOOTH_ADDRESS_BYTES  6
 
 // Bluetooth stack internal error, such as I/O error
 #define ERR_INTERNAL_ERROR "InternalError"
 
--- a/dom/bluetooth2/BluetoothDevice.cpp
+++ b/dom/bluetooth2/BluetoothDevice.cpp
@@ -1,31 +1,35 @@
 /* -*- 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 "BluetoothClassOfDevice.h"
-#include "BluetoothDevice.h"
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "BluetoothUtils.h"
 
-#include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/dom/BluetoothAttributeEvent.h"
 #include "mozilla/dom/BluetoothDevice2Binding.h"
+#include "mozilla/dom/bluetooth/BluetoothClassOfDevice.h"
+#include "mozilla/dom/bluetooth/BluetoothDevice.h"
+#include "mozilla/dom/bluetooth/BluetoothGatt.h"
+#include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/dom/Promise.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 USING_BLUETOOTH_NAMESPACE
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED(BluetoothDevice, DOMEventTargetHelper, mCod)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(BluetoothDevice,
+                                   DOMEventTargetHelper,
+                                   mCod,
+                                   mGatt)
 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 MOZ_FINAL : public BluetoothReplyRunnable
 {
@@ -297,13 +301,26 @@ BluetoothDevice::DispatchAttributeEvent(
   init.mAttrs = value;
   nsRefPtr<BluetoothAttributeEvent> event =
     BluetoothAttributeEvent::Constructor(this,
                                          NS_LITERAL_STRING("attributechanged"),
                                          init);
   DispatchTrustedEvent(event);
 }
 
+BluetoothGatt*
+BluetoothDevice::GetGatt()
+{
+  NS_ENSURE_TRUE(mType == BluetoothDeviceType::Le ||
+                 mType == BluetoothDeviceType::Dual,
+                 nullptr);
+  if (!mGatt) {
+    mGatt = new BluetoothGatt(GetOwner(), mAddress);
+  }
+
+  return mGatt;
+}
+
 JSObject*
 BluetoothDevice::WrapObject(JSContext* aContext)
 {
   return BluetoothDeviceBinding::Wrap(aContext, this);
 }
--- a/dom/bluetooth2/BluetoothDevice.h
+++ b/dom/bluetooth2/BluetoothDevice.h
@@ -5,29 +5,30 @@
  * 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 "mozilla/dom/bluetooth/BluetoothCommon.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 
 namespace mozilla {
 namespace dom {
   class Promise;
 }
 }
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothClassOfDevice;
+class BluetoothGatt;
 class BluetoothNamedValue;
 class BluetoothValue;
 class BluetoothSignal;
 class BluetoothSocket;
 
 class BluetoothDevice MOZ_FINAL : public DOMEventTargetHelper
                                 , public BluetoothSignalObserver
 {
@@ -64,16 +65,18 @@ public:
     aUuids = mUuids;
   }
 
   BluetoothDeviceType Type() const
   {
     return mType;
   }
 
+  BluetoothGatt* GetGatt();
+
   /****************************************************************************
    * Event Handlers
    ***************************************************************************/
   IMPL_EVENT_HANDLER(attributechanged);
 
   /****************************************************************************
    * Methods (Web API Implementation)
    ***************************************************************************/
@@ -168,16 +171,21 @@ private:
    * Cached UUID list of services which this device provides.
    */
   nsTArray<nsString> mUuids;
 
   /**
    * Type of this device. Can be unknown/classic/le/dual.
    */
   BluetoothDeviceType mType;
+
+  /**
+   * GATT client object to interact with the remote device.
+   */
+  nsRefPtr<BluetoothGatt> mGatt;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 /**
  * Explicit Specialization of Function Templates
  *
  * Allows customizing the template code for a given set of template arguments.
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth2/BluetoothGatt.cpp
@@ -0,0 +1,217 @@
+/* -*- 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 "BluetoothReplyRunnable.h"
+#include "BluetoothService.h"
+#include "BluetoothUtils.h"
+#include "mozilla/dom/bluetooth/BluetoothGatt.h"
+#include "mozilla/dom/bluetooth/BluetoothTypes.h"
+#include "mozilla/dom/BluetoothGattBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "nsIUUIDGenerator.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+USING_BLUETOOTH_NAMESPACE
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothGatt)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothGatt,
+                                                DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothGatt,
+                                                  DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothGatt)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(BluetoothGatt, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(BluetoothGatt, DOMEventTargetHelper)
+
+BluetoothGatt::BluetoothGatt(nsPIDOMWindow* aWindow,
+                             const nsAString& aDeviceAddr)
+  : DOMEventTargetHelper(aWindow)
+  , mAppUuid(EmptyString())
+  , mClientIf(0)
+  , mConnectionState(BluetoothConnectionState::Disconnected)
+  , mDeviceAddr(aDeviceAddr)
+{
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(!mDeviceAddr.IsEmpty());
+}
+
+BluetoothGatt::~BluetoothGatt()
+{
+  BluetoothService* bs = BluetoothService::Get();
+  // bs can be null on shutdown, where destruction might happen.
+  NS_ENSURE_TRUE_VOID(bs);
+
+  if (mClientIf > 0) {
+    nsRefPtr<BluetoothVoidReplyRunnable> result =
+      new BluetoothVoidReplyRunnable(nullptr);
+    bs->UnregisterGattClientInternal(mClientIf, result);
+  }
+
+  bs->UnregisterBluetoothSignalHandler(mAppUuid, this);
+}
+
+void
+BluetoothGatt::GenerateUuid(nsAString &aUuidString)
+{
+  nsresult rv;
+  nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
+    do_GetService("@mozilla.org/uuid-generator;1", &rv);
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  nsID uuid;
+  rv = uuidGenerator->GenerateUUIDInPlace(&uuid);
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format
+  char uuidBuffer[NSID_LENGTH];
+  uuid.ToProvidedString(uuidBuffer);
+  NS_ConvertASCIItoUTF16 uuidString(uuidBuffer);
+
+  // Remove {} and the null terminator
+  aUuidString.Assign(Substring(uuidString, 1, NSID_LENGTH - 3));
+}
+
+void
+BluetoothGatt::DisconnectFromOwner()
+{
+  DOMEventTargetHelper::DisconnectFromOwner();
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  if (mClientIf > 0) {
+    nsRefPtr<BluetoothVoidReplyRunnable> result =
+      new BluetoothVoidReplyRunnable(nullptr);
+    bs->UnregisterGattClientInternal(mClientIf, result);
+  }
+
+  bs->UnregisterBluetoothSignalHandler(mAppUuid, this);
+}
+
+already_AddRefed<Promise>
+BluetoothGatt::Connect(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+  BT_ENSURE_TRUE_REJECT(
+    mConnectionState == BluetoothConnectionState::Disconnected,
+    NS_ERROR_DOM_INVALID_STATE_ERR);
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
+
+  if (mAppUuid.IsEmpty()) {
+    GenerateUuid(mAppUuid);
+    BT_ENSURE_TRUE_REJECT(!mAppUuid.IsEmpty(),
+                          NS_ERROR_DOM_OPERATION_ERR);
+    bs->RegisterBluetoothSignalHandler(mAppUuid, this);
+  }
+
+  UpdateConnectionState(BluetoothConnectionState::Connecting);
+  nsRefPtr<BluetoothReplyRunnable> result =
+    new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
+                                   promise,
+                                   NS_LITERAL_STRING("ConnectGattClient"));
+  bs->ConnectGattClientInternal(mAppUuid,
+                                mDeviceAddr,
+                                result);
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+BluetoothGatt::Disconnect(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+  BT_ENSURE_TRUE_REJECT(
+    mConnectionState == BluetoothConnectionState::Connected,
+    NS_ERROR_DOM_INVALID_STATE_ERR);
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
+
+  UpdateConnectionState(BluetoothConnectionState::Disconnecting);
+  nsRefPtr<BluetoothReplyRunnable> result =
+    new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
+                                   promise,
+                                   NS_LITERAL_STRING("DisconnectGattClient"));
+  bs->DisconnectGattClientInternal(mAppUuid, mDeviceAddr, result);
+
+  return promise.forget();
+}
+
+void
+BluetoothGatt::UpdateConnectionState(BluetoothConnectionState aState)
+{
+  BT_API2_LOGR("GATT connection state changes to: %d", int(aState));
+  mConnectionState = aState;
+
+  // Dispatch connectionstatechanged event to application
+  nsCOMPtr<nsIDOMEvent> event;
+  nsresult rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  rv = event->InitEvent(NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+                        false,
+                        false);
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  DispatchTrustedEvent(event);
+}
+
+void
+BluetoothGatt::Notify(const BluetoothSignal& aData)
+{
+  BT_LOGD("[D] %s", NS_ConvertUTF16toUTF8(aData.name()).get());
+
+  BluetoothValue v = aData.value();
+  if (aData.name().EqualsLiteral("ClientRegistered")) {
+    MOZ_ASSERT(v.type() == BluetoothValue::Tuint32_t);
+    mClientIf = v.get_uint32_t();
+  } else if (aData.name().EqualsLiteral("ClientUnregistered")) {
+    mClientIf = 0;
+  } else if (aData.name().EqualsLiteral(GATT_CONNECTION_STATE_CHANGED_ID)) {
+    MOZ_ASSERT(v.type() == BluetoothValue::Tbool);
+
+    BluetoothConnectionState state =
+      v.get_bool() ? BluetoothConnectionState::Connected
+                   : BluetoothConnectionState::Disconnected;
+    UpdateConnectionState(state);
+  } else {
+    BT_WARNING("Not handling GATT signal: %s",
+               NS_ConvertUTF16toUTF8(aData.name()).get());
+  }
+}
+
+JSObject*
+BluetoothGatt::WrapObject(JSContext* aContext)
+{
+  return BluetoothGattBinding::Wrap(aContext, this);
+}
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth2/BluetoothGatt.h
@@ -0,0 +1,116 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_bluetooth_bluetoothgatt_h__
+#define mozilla_dom_bluetooth_bluetoothgatt_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/BluetoothGattBinding.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace dom {
+class Promise;
+}
+}
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothReplyRunnable;
+class BluetoothService;
+class BluetoothSignal;
+class BluetoothValue;
+
+class BluetoothGatt MOZ_FINAL : public DOMEventTargetHelper
+                              , public BluetoothSignalObserver
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothGatt, DOMEventTargetHelper)
+
+  /****************************************************************************
+   * Attribute Getters
+   ***************************************************************************/
+  BluetoothConnectionState ConnectionState() const
+  {
+    return mConnectionState;
+  }
+
+  /****************************************************************************
+   * Event Handlers
+   ***************************************************************************/
+  IMPL_EVENT_HANDLER(connectionstatechanged);
+
+  /****************************************************************************
+   * Methods (Web API Implementation)
+   ***************************************************************************/
+  already_AddRefed<Promise> Connect(ErrorResult& aRv);
+  already_AddRefed<Promise> Disconnect(ErrorResult& aRv);
+
+  /****************************************************************************
+   * Others
+   ***************************************************************************/
+  void Notify(const BluetoothSignal& aParam); // BluetoothSignalObserver
+
+  nsPIDOMWindow* GetParentObject() const
+  {
+     return GetOwner();
+  }
+
+  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+  virtual void DisconnectFromOwner() MOZ_OVERRIDE;
+
+  BluetoothGatt(nsPIDOMWindow* aOwner,
+                const nsAString& aDeviceAddr);
+
+private:
+  ~BluetoothGatt();
+
+  /**
+   * Update mConnectionState to aState and fire
+   * connectionstatechanged event to the application.
+   *
+   * @param aState [in] New connection state
+   */
+  void UpdateConnectionState(BluetoothConnectionState aState);
+
+  /**
+   * Generate a random uuid.
+   *
+   * @param aUuidString [out] String to store the generated uuid.
+   */
+  void GenerateUuid(nsAString &aUuidString);
+
+  /****************************************************************************
+   * Variables
+   ***************************************************************************/
+  /**
+   * Random generated UUID of this GATT client.
+   */
+  nsString mAppUuid;
+
+  /**
+   * Id of the GATT client interface given by bluetooth stack.
+   * 0 if the client is not registered yet, nonzero otherwise.
+   */
+  int mClientIf;
+
+  /**
+   * Connection state of this remote device.
+   */
+  BluetoothConnectionState mConnectionState;
+
+  /**
+   * Address of the remote device.
+   */
+  nsString mDeviceAddr;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif
--- a/dom/bluetooth2/BluetoothService.h
+++ b/dom/bluetooth2/BluetoothService.h
@@ -302,16 +302,41 @@ public:
   virtual nsresult
   SendSinkMessage(const nsAString& aDeviceAddresses,
                   const nsAString& aMessage) = 0;
 
   virtual nsresult
   SendInputMessage(const nsAString& aDeviceAddresses,
                    const nsAString& aMessage) = 0;
 
+  /**
+   * Connect to a remote GATT server. (platform specific implementation)
+   */
+  virtual void
+  ConnectGattClientInternal(const nsAString& aAppUuid,
+                            const nsAString& aDeviceAddress,
+                            BluetoothReplyRunnable* aRunnable) = 0;
+
+  /**
+   * Disconnect GATT client from a remote GATT server.
+   * (platform specific implementation)
+   */
+  virtual void
+  DisconnectGattClientInternal(const nsAString& aAppUuid,
+                               const nsAString& aDeviceAddress,
+                               BluetoothReplyRunnable* aRunnable) = 0;
+
+  /**
+   * Unregister a GATT client. (platform specific implementation)
+   */
+  virtual void
+  UnregisterGattClientInternal(int aClientIf,
+                               BluetoothReplyRunnable* aRunnable) = 0;
+
+
   bool
   IsEnabled() const
   {
     return mEnabled;
   }
 
   bool
   IsToggling() const;
--- a/dom/bluetooth2/BluetoothUtils.cpp
+++ b/dom/bluetooth2/BluetoothUtils.cpp
@@ -35,16 +35,40 @@ UuidToString(const BluetoothUuid& aUuid,
           ntohl(uuid0), ntohs(uuid1),
           ntohs(uuid2), ntohs(uuid3),
           ntohl(uuid4), ntohs(uuid5));
 
   aString.Truncate();
   aString.AssignLiteral(uuidStr);
 }
 
+void
+StringToUuid(const char* aString, BluetoothUuid& aUuid)
+{
+  uint32_t uuid0, uuid4;
+  uint16_t uuid1, uuid2, uuid3, uuid5;
+
+  sscanf(aString, "%08x-%04hx-%04hx-%04hx-%08x%04hx",
+         &uuid0, &uuid1, &uuid2, &uuid3, &uuid4, &uuid5);
+
+  uuid0 = htonl(uuid0);
+  uuid1 = htons(uuid1);
+  uuid2 = htons(uuid2);
+  uuid3 = htons(uuid3);
+  uuid4 = htonl(uuid4);
+  uuid5 = htons(uuid5);
+
+  memcpy(&aUuid.mUuid[0], &uuid0, 4);
+  memcpy(&aUuid.mUuid[4], &uuid1, 2);
+  memcpy(&aUuid.mUuid[6], &uuid2, 2);
+  memcpy(&aUuid.mUuid[8], &uuid3, 2);
+  memcpy(&aUuid.mUuid[10], &uuid4, 4);
+  memcpy(&aUuid.mUuid[14], &uuid5, 2);
+}
+
 bool
 SetJsObject(JSContext* aContext,
             const BluetoothValue& aValue,
             JS::Handle<JSObject*> aObj)
 {
   MOZ_ASSERT(aContext && aObj);
 
   if (aValue.type() != BluetoothValue::TArrayOfBluetoothNamedValue) {
--- a/dom/bluetooth2/BluetoothUtils.h
+++ b/dom/bluetooth2/BluetoothUtils.h
@@ -14,16 +14,24 @@ BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothNamedValue;
 class BluetoothValue;
 class BluetoothReplyRunnable;
 
 void
 UuidToString(const BluetoothUuid& aUuid, nsAString& aString);
 
+/**
+ * Convert xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx uuid string to BluetoothUuid object.
+ * This utility function is used by gecko internal only to convert uuid string
+ * created by gecko back to BluetoothUuid representation.
+ */
+void
+StringToUuid(const char* aString, BluetoothUuid& aUuid);
+
 bool
 SetJsObject(JSContext* aContext,
             const BluetoothValue& aValue,
             JS::Handle<JSObject*> aObj);
 
 bool
 BroadcastSystemMessage(const nsAString& aType,
                        const BluetoothValue& aData);
--- a/dom/bluetooth2/bluedroid/BluetoothGattManager.cpp
+++ b/dom/bluetooth2/bluedroid/BluetoothGattManager.cpp
@@ -1,39 +1,100 @@
 /* -*- 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 "BluetoothGattManager.h"
+
 #include "BluetoothCommon.h"
+#include "BluetoothInterface.h"
+#include "BluetoothReplyRunnable.h"
+#include "BluetoothService.h"
 #include "BluetoothUtils.h"
-#include "BluetoothInterface.h"
-
+#include "MainThreadUtils.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
-#include "MainThreadUtils.h"
 #include "nsIObserverService.h"
 #include "nsThreadUtils.h"
 
+#define ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(runnable)                       \
+  do {                                                                        \
+    if (!sBluetoothGattInterface) {                                           \
+      NS_NAMED_LITERAL_STRING(errorStr,                                       \
+                              "BluetoothGattClientInterface is not ready");   \
+      DispatchBluetoothReply(runnable, BluetoothValue(), errorStr);           \
+      return;                                                                 \
+    }                                                                         \
+  } while(0)
+
 using namespace mozilla;
 USING_BLUETOOTH_NAMESPACE
 
 namespace {
   StaticRefPtr<BluetoothGattManager> sBluetoothGattManager;
   static BluetoothGattInterface* sBluetoothGattInterface;
   static BluetoothGattClientInterface* sBluetoothGattClientInterface;
 } // anonymous namespace
 
 bool BluetoothGattManager::mInShutdown = false;
 
-/*
- * Static functions
- */
+class BluetoothGattClient;
+static StaticAutoPtr<nsTArray<nsRefPtr<BluetoothGattClient> > > sClients;
+
+class BluetoothGattClient MOZ_FINAL : public nsISupports
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  BluetoothGattClient(const nsAString& aAppUuid, const nsAString& aDeviceAddr)
+  : mAppUuid(aAppUuid)
+  , mDeviceAddr(aDeviceAddr)
+  , mClientIf(0)
+  , mConnId(0)
+  { }
+
+  ~BluetoothGattClient()
+  {
+    mConnectRunnable = nullptr;
+    mDisconnectRunnable = nullptr;
+    mUnregisterClientRunnable = nullptr;
+  }
+
+  nsString mAppUuid;
+  nsString mDeviceAddr;
+  int mClientIf;
+  int mConnId;
+  nsRefPtr<BluetoothReplyRunnable> mConnectRunnable;
+  nsRefPtr<BluetoothReplyRunnable> mDisconnectRunnable;
+  nsRefPtr<BluetoothReplyRunnable> mUnregisterClientRunnable;
+};
+
+NS_IMPL_ISUPPORTS0(BluetoothGattClient)
+
+class UuidComparator
+{
+public:
+  bool Equals(const nsRefPtr<BluetoothGattClient>& aClient,
+              const nsAString& aAppUuid) const
+  {
+    return aClient->mAppUuid.Equals(aAppUuid);
+  }
+};
+
+class ClientIfComparator
+{
+public:
+  bool Equals(const nsRefPtr<BluetoothGattClient>& aClient,
+              int aClientIf) const
+  {
+    return aClient->mClientIf == aClientIf;
+  }
+};
 
 BluetoothGattManager*
 BluetoothGattManager::Get()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // If sBluetoothGattManager already exists, exit early
   if (sBluetoothGattManager) {
@@ -98,16 +159,20 @@ BluetoothGattManager::InitGattInterface(
     }
     return;
   }
 
   sBluetoothGattClientInterface =
     sBluetoothGattInterface->GetBluetoothGattClientInterface();
   NS_ENSURE_TRUE_VOID(sBluetoothGattClientInterface);
 
+  if (!sClients) {
+    sClients = new nsTArray<nsRefPtr<BluetoothGattClient> >;
+  }
+
   BluetoothGattManager* gattManager = BluetoothGattManager::Get();
   sBluetoothGattInterface->Init(gattManager,
                                 new InitGattResultHandler(aRes));
 }
 
 class BluetoothGattManager::CleanupResultHandler MOZ_FINAL
   : public BluetoothGattResultHandler
 {
@@ -124,16 +189,18 @@ public:
       mRes->OnError(NS_ERROR_FAILURE);
     }
   }
 
   void Cleanup() MOZ_OVERRIDE
   {
     sBluetoothGattClientInterface = nullptr;
     sBluetoothGattInterface = nullptr;
+    sClients = nullptr;
+
     if (mRes) {
       mRes->Deinit();
     }
   }
 
 private:
   nsRefPtr<BluetoothProfileResultHandler> mRes;
 };
@@ -171,44 +238,455 @@ BluetoothGattManager::DeinitGattInterfac
     // behave as if GATT was initialized.
     nsRefPtr<nsRunnable> r = new CleanupResultHandlerRunnable(aRes);
     if (NS_FAILED(NS_DispatchToMainThread(r))) {
       BT_LOGR("Failed to dispatch cleanup-result-handler runnable");
     }
   }
 }
 
+class BluetoothGattManager::RegisterClientResultHandler MOZ_FINAL
+  : public BluetoothGattClientResultHandler
+{
+public:
+  RegisterClientResultHandler(BluetoothGattClient* aClient)
+  : mClient(aClient)
+  {
+    MOZ_ASSERT(mClient);
+  }
+
+  void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothGattClientInterface::RegisterClient failed: %d",
+               (int)aStatus);
+
+    BluetoothService* bs = BluetoothService::Get();
+    NS_ENSURE_TRUE_VOID(bs);
+
+    // Notify BluetoothGatt for client disconnected
+    BluetoothSignal signal(
+      NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+      mClient->mAppUuid,
+      BluetoothValue(false)); // Disconnected
+    bs->DistributeSignal(signal);
+
+    // Reject the connect request
+    if (mClient->mConnectRunnable) {
+      NS_NAMED_LITERAL_STRING(errorStr, "Register GATT client failed");
+      DispatchBluetoothReply(mClient->mConnectRunnable,
+                             BluetoothValue(),
+                             errorStr);
+      mClient->mConnectRunnable = nullptr;
+    }
+
+    sClients->RemoveElement(mClient);
+  }
+
+private:
+  nsRefPtr<BluetoothGattClient> mClient;
+};
+
+class BluetoothGattManager::UnregisterClientResultHandler MOZ_FINAL
+  : public BluetoothGattClientResultHandler
+{
+public:
+  UnregisterClientResultHandler(BluetoothGattClient* aClient)
+  : mClient(aClient)
+  {
+    MOZ_ASSERT(mClient);
+  }
+
+  void UnregisterClient() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(mClient->mUnregisterClientRunnable);
+    BluetoothService* bs = BluetoothService::Get();
+    NS_ENSURE_TRUE_VOID(bs);
+
+    // Notify BluetoothGatt to clear the clientIf
+    BluetoothSignal signal(
+      NS_LITERAL_STRING("ClientUnregistered"),
+      mClient->mAppUuid,
+      BluetoothValue(true));
+    bs->DistributeSignal(signal);
+
+    // Resolve the unregister request
+    DispatchBluetoothReply(mClient->mUnregisterClientRunnable,
+                           BluetoothValue(true),
+                           EmptyString());
+    mClient->mUnregisterClientRunnable = nullptr;
+
+    sClients->RemoveElement(mClient);
+  }
+
+  void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothGattClientInterface::UnregisterClient failed: %d",
+               (int)aStatus);
+    MOZ_ASSERT(mClient->mUnregisterClientRunnable);
+
+    // Reject the unregister request
+    NS_NAMED_LITERAL_STRING(errorStr, "Unregister GATT client failed");
+    DispatchBluetoothReply(mClient->mUnregisterClientRunnable,
+                           BluetoothValue(),
+                           errorStr);
+    mClient->mUnregisterClientRunnable = nullptr;
+  }
+
+private:
+  nsRefPtr<BluetoothGattClient> mClient;
+};
+
+void
+BluetoothGattManager::UnregisterClient(int aClientIf,
+                                       BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
+                                   ClientIfComparator());
+
+  // Reject the unregister request if the client is not found
+  if (index == sClients->NoIndex) {
+    NS_NAMED_LITERAL_STRING(errorStr, "Unregister GATT client failed");
+    DispatchBluetoothReply(aRunnable,
+                           BluetoothValue(),
+                           errorStr);
+    return;
+  }
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+  client->mUnregisterClientRunnable = aRunnable;
+
+  sBluetoothGattClientInterface->UnregisterClient(
+    aClientIf,
+    new UnregisterClientResultHandler(client));
+}
+
+class BluetoothGattManager::ConnectResultHandler MOZ_FINAL
+  : public BluetoothGattClientResultHandler
+{
+public:
+  ConnectResultHandler(BluetoothGattClient* aClient)
+  : mClient(aClient)
+  {
+    MOZ_ASSERT(mClient);
+  }
+
+  void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothGattClientInterface::Connect failed: %d",
+               (int)aStatus);
+    MOZ_ASSERT(mClient->mConnectRunnable);
+
+    BluetoothService* bs = BluetoothService::Get();
+    NS_ENSURE_TRUE_VOID(bs);
+
+    // Notify BluetoothGatt for client disconnected
+    BluetoothSignal signal(
+      NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+      mClient->mAppUuid,
+      BluetoothValue(false)); // Disconnected
+    bs->DistributeSignal(signal);
+
+    // Reject the connect request
+    NS_NAMED_LITERAL_STRING(errorStr, "Connect failed");
+    DispatchBluetoothReply(mClient->mConnectRunnable,
+                           BluetoothValue(),
+                           errorStr);
+    mClient->mConnectRunnable = nullptr;
+  }
+
+private:
+  nsRefPtr<BluetoothGattClient> mClient;
+};
+
+void
+BluetoothGattManager::Connect(const nsAString& aAppUuid,
+                              const nsAString& aDeviceAddr,
+                              BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  if (index == sClients->NoIndex) {
+    index = sClients->Length();
+    sClients->AppendElement(new BluetoothGattClient(aAppUuid, aDeviceAddr));
+  }
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+  client->mConnectRunnable = aRunnable;
+
+  if (client->mClientIf > 0) {
+    sBluetoothGattClientInterface->Connect(client->mClientIf,
+                                           aDeviceAddr,
+                                           true, // direct connect
+                                           new ConnectResultHandler(client));
+  } else {
+    BluetoothUuid uuid;
+    StringToUuid(NS_ConvertUTF16toUTF8(aAppUuid).get(), uuid);
+
+    // connect will be proceeded after client registered
+    sBluetoothGattClientInterface->RegisterClient(
+      uuid, new RegisterClientResultHandler(client));
+  }
+}
+
+class BluetoothGattManager::DisconnectResultHandler MOZ_FINAL
+  : public BluetoothGattClientResultHandler
+{
+public:
+  DisconnectResultHandler(BluetoothGattClient* aClient)
+  : mClient(aClient)
+  {
+    MOZ_ASSERT(mClient);
+  }
+
+  void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothGattClientInterface::Disconnect failed: %d",
+               (int)aStatus);
+    MOZ_ASSERT(mClient->mDisconnectRunnable);
+
+    BluetoothService* bs = BluetoothService::Get();
+    NS_ENSURE_TRUE_VOID(bs);
+
+    // Notify BluetoothGatt that the client remains connected
+    BluetoothSignal signal(
+      NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+      mClient->mAppUuid,
+      BluetoothValue(true)); // Connected
+    bs->DistributeSignal(signal);
+
+    // Reject the disconnect request
+    NS_NAMED_LITERAL_STRING(errorStr, "Disconnect failed");
+    DispatchBluetoothReply(mClient->mDisconnectRunnable,
+                           BluetoothValue(),
+                           errorStr);
+    mClient->mDisconnectRunnable = nullptr;
+  }
+
+private:
+  nsRefPtr<BluetoothGattClient> mClient;
+};
+
+void
+BluetoothGattManager::Disconnect(const nsAString& aAppUuid,
+                                 const nsAString& aDeviceAddr,
+                                 BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+
+  // Reject the disconnect request if the client is not found
+  if (index == sClients->NoIndex) {
+    NS_NAMED_LITERAL_STRING(errorStr, "Disconnect failed");
+    DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
+    return;
+  }
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+  client->mDisconnectRunnable = aRunnable;
+
+  sBluetoothGattClientInterface->Disconnect(
+    client->mClientIf,
+    aDeviceAddr,
+    client->mConnId,
+    new DisconnectResultHandler(client));
+}
+
 //
 // Notification Handlers
 //
 void
 BluetoothGattManager::RegisterClientNotification(int aStatus,
                                                  int aClientIf,
                                                  const BluetoothUuid& aAppUuid)
-{ }
+{
+  BT_API2_LOGR("Client Registered, clientIf = %d", aClientIf);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsString uuid;
+  UuidToString(aAppUuid, uuid);
+
+  size_t index = sClients->IndexOf(uuid, 0 /* Start */, UuidComparator());
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  if (aStatus) { // operation failed
+    BT_API2_LOGR(
+      "RegisterClient failed, clientIf = %d, status = %d, appUuid = %s",
+      aClientIf, aStatus, NS_ConvertUTF16toUTF8(uuid).get());
+
+    // Notify BluetoothGatt for client disconnected
+    BluetoothSignal signal(
+      NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+      uuid, BluetoothValue(false)); // Disconnected
+    bs->DistributeSignal(signal);
+
+    // Reject the connect request
+    if (client->mConnectRunnable) {
+      NS_NAMED_LITERAL_STRING(errorStr,
+                              "Connect failed due to registration failed");
+      DispatchBluetoothReply(client->mConnectRunnable,
+                             BluetoothValue(),
+                             errorStr);
+      client->mConnectRunnable = nullptr;
+    }
+
+    sClients->RemoveElement(client);
+    return;
+  }
+
+  client->mClientIf = aClientIf;
+
+  // Notify BluetoothGatt to update the clientIf
+  BluetoothSignal signal(
+    NS_LITERAL_STRING("ClientRegistered"),
+    uuid, BluetoothValue(uint32_t(aClientIf)));
+  bs->DistributeSignal(signal);
+
+  // Client just registered, proceed remaining connect request.
+  if (client->mConnectRunnable) {
+    sBluetoothGattClientInterface->Connect(
+      aClientIf, client->mDeviceAddr, true /* direct connect */,
+      new ConnectResultHandler(client));
+  }
+}
 
 void
 BluetoothGattManager::ScanResultNotification(
   const nsAString& aBdAddr, int aRssi,
   const BluetoothGattAdvData& aAdvData)
 { }
 
 void
 BluetoothGattManager::ConnectNotification(int aConnId,
                                           int aStatus,
                                           int aClientIf,
-                                          const nsAString& aBdAddr)
-{ }
+                                          const nsAString& aDeviceAddr)
+{
+  BT_API2_LOGR();
+  MOZ_ASSERT(NS_IsMainThread());
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
+                                   ClientIfComparator());
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+  if (aStatus) { // operation failed
+    BT_API2_LOGR("Connect failed, clientIf = %d, connId = %d, status = %d",
+                 aClientIf, aConnId, aStatus);
+
+    // Notify BluetoothGatt that the client remains disconnected
+    BluetoothSignal signal(
+      NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+      client->mAppUuid,
+      BluetoothValue(false)); // Disconnected
+    bs->DistributeSignal(signal);
+
+    // Reject the connect request
+    if (client->mConnectRunnable) {
+      NS_NAMED_LITERAL_STRING(errorStr, "Connect failed");
+      DispatchBluetoothReply(client->mConnectRunnable,
+                             BluetoothValue(),
+                             errorStr);
+      client->mConnectRunnable = nullptr;
+    }
+
+    return;
+  }
+
+  client->mConnId = aConnId;
+
+  // Notify BluetoothGatt for client connected
+  BluetoothSignal signal(
+    NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+    client->mAppUuid,
+    BluetoothValue(true)); // Connected
+  bs->DistributeSignal(signal);
+
+  // Resolve the connect request
+  if (client->mConnectRunnable) {
+    DispatchBluetoothReply(client->mConnectRunnable,
+                           BluetoothValue(true),
+                           EmptyString());
+    client->mConnectRunnable = nullptr;
+  }
+}
 
 void
 BluetoothGattManager::DisconnectNotification(int aConnId,
                                              int aStatus,
                                              int aClientIf,
-                                             const nsAString& aBdAddr)
-{ }
+                                             const nsAString& aDeviceAddr)
+{
+  BT_API2_LOGR();
+  MOZ_ASSERT(NS_IsMainThread());
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
+                                   ClientIfComparator());
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+  if (aStatus) { // operation failed
+    // Notify BluetoothGatt that the client remains connected
+    BluetoothSignal signal(
+      NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+      client->mAppUuid,
+      BluetoothValue(true)); // Connected
+    bs->DistributeSignal(signal);
+
+    // Reject the disconnect request
+    if (client->mDisconnectRunnable) {
+      NS_NAMED_LITERAL_STRING(errorStr, "Disconnect failed");
+      DispatchBluetoothReply(client->mDisconnectRunnable,
+                             BluetoothValue(),
+                             errorStr);
+      client->mDisconnectRunnable = nullptr;
+    }
+
+    return;
+  }
+
+  client->mConnId = 0;
+
+  // Notify BluetoothGatt for client disconnected
+  BluetoothSignal signal(
+    NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
+    client->mAppUuid,
+    BluetoothValue(false)); // Disconnected
+  bs->DistributeSignal(signal);
+
+  // Resolve the disconnect request
+  if (client->mDisconnectRunnable) {
+    DispatchBluetoothReply(client->mDisconnectRunnable,
+                           BluetoothValue(true),
+                           EmptyString());
+    client->mDisconnectRunnable = nullptr;
+  }
+}
 
 void
 BluetoothGattManager::SearchCompleteNotification(int aConnId, int aStatus)
 { }
 
 void
 BluetoothGattManager::SearchResultNotification(
   int aConnId, const BluetoothGattServiceId& aServiceId)
--- a/dom/bluetooth2/bluedroid/BluetoothGattManager.h
+++ b/dom/bluetooth2/bluedroid/BluetoothGattManager.h
@@ -7,32 +7,50 @@
 #ifndef mozilla_dom_bluetooth_bluetoothgattmanager_h__
 #define mozilla_dom_bluetooth_bluetoothgattmanager_h__
 
 #include "BluetoothCommon.h"
 #include "BluetoothInterface.h"
 #include "BluetoothProfileManagerBase.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothReplyRunnable;
+
 class BluetoothGattManager MOZ_FINAL : public nsIObserver
                                      , public BluetoothGattNotificationHandler
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   static BluetoothGattManager* Get();
   static void InitGattInterface(BluetoothProfileResultHandler* aRes);
   static void DeinitGattInterface(BluetoothProfileResultHandler* aRes);
   virtual ~BluetoothGattManager();
 
+  void Connect(const nsAString& aAppUuid,
+               const nsAString& aDeviceAddr,
+               BluetoothReplyRunnable* aRunnable);
+
+  void Disconnect(const nsAString& aAppUuid,
+                  const nsAString& aDeviceAddr,
+                  BluetoothReplyRunnable* aRunnable);
+
+  void UnregisterClient(int aClientIf,
+                        BluetoothReplyRunnable* aRunnable);
+
 private:
   class CleanupResultHandler;
   class CleanupResultHandlerRunnable;
   class InitGattResultHandler;
+  class RegisterClientResultHandler;
+  class UnregisterClientResultHandler;
+  class ConnectResultHandler;
+  class DisconnectResultHandler;
 
   BluetoothGattManager();
 
   void HandleShutdown();
 
   void RegisterClientNotification(int aStatus,
                                   int aClientIf,
                                   const BluetoothUuid& aAppUuid) MOZ_OVERRIDE;
--- a/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp
@@ -50,16 +50,25 @@
   do {                                                                 \
     if (!sBtInterface || !IsEnabled()) {                               \
       NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth is not ready");     \
       DispatchBluetoothReply(runnable, BluetoothValue(), errorStr);    \
       return;                                                          \
     }                                                                  \
   } while(0)
 
+#define ENSURE_GATT_MGR_IS_READY_VOID(gatt, runnable)                  \
+  do {                                                                 \
+    if (!gatt) {                                                       \
+      NS_NAMED_LITERAL_STRING(replyError, "GattManager is not ready"); \
+      DispatchBluetoothReply(runnable, BluetoothValue(), replyError);  \
+      return;                                                          \
+    }                                                                  \
+  } while(0)
+
 using namespace mozilla;
 using namespace mozilla::ipc;
 USING_BLUETOOTH_NAMESPACE
 
 static nsString sAdapterBdAddress;
 static nsString sAdapterBdName;
 static bool sAdapterDiscoverable(false);
 static bool sAdapterDiscovering(false);
@@ -1103,16 +1112,64 @@ BluetoothServiceBluedroid::IgnoreWaiting
 }
 
 void
 BluetoothServiceBluedroid::ToggleCalls(BluetoothReplyRunnable* aRunnable)
 {
 }
 
 //
+// GATT Client
+//
+
+void
+BluetoothServiceBluedroid::ConnectGattClientInternal(
+  const nsAString& aAppUuid, const nsAString& aDeviceAddress,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->Connect(aAppUuid, aDeviceAddress, aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::DisconnectGattClientInternal(
+  const nsAString& aAppUuid, const nsAString& aDeviceAddress,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->Disconnect(aAppUuid, aDeviceAddress, aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::UnregisterGattClientInternal(
+  int aClientIf, BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->UnregisterClient(aClientIf, aRunnable);
+}
+
+//
 // Bluetooth notifications
 //
 
 /* |ProfileDeinitResultHandler| collects the results of all profile
  * result handlers and calls |Proceed| after all results handlers
  * have been run.
  */
 class BluetoothServiceBluedroid::ProfileDeinitResultHandler MOZ_FINAL
--- a/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.h
+++ b/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.h
@@ -166,16 +166,34 @@ public:
   SendSinkMessage(const nsAString& aDeviceAddresses,
                   const nsAString& aMessage) MOZ_OVERRIDE;
 
   virtual nsresult
   SendInputMessage(const nsAString& aDeviceAddresses,
                    const nsAString& aMessage) MOZ_OVERRIDE;
 
   //
+  // GATT Client
+  //
+
+  virtual void
+  ConnectGattClientInternal(const nsAString& aAppUuid,
+                            const nsAString& aDeviceAddress,
+                            BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual void
+  DisconnectGattClientInternal(const nsAString& aAppUuid,
+                               const nsAString& aDeviceAddress,
+                               BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual void
+  UnregisterGattClientInternal(int aClientIf,
+                               BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  //
   // Bluetooth notifications
   //
 
   virtual void AdapterStateChangedNotification(bool aState) MOZ_OVERRIDE;
   virtual void AdapterPropertiesNotification(
     BluetoothStatus aStatus, int aNumProperties,
     const BluetoothProperty* aProperties) MOZ_OVERRIDE;
 
--- a/dom/bluetooth2/bluez/BluetoothDBusService.cpp
+++ b/dom/bluetooth2/bluez/BluetoothDBusService.cpp
@@ -4261,8 +4261,28 @@ BluetoothDBusService::UpdateNotification
   MOZ_ASSERT(!sAdapterPath.IsEmpty());
 
   nsAutoString deviceAddress;
   a2dp->GetAddress(deviceAddress);
 
   Task* task = new UpdateNotificationTask(deviceAddress, aEventId, aData);
   DispatchToDBusThread(task);
 }
+
+void
+BluetoothDBusService::ConnectGattClientInternal(
+  const nsAString& aAppUuid, const nsAString& aDeviceAddress,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::DisconnectGattClientInternal(
+  const nsAString& aAppUuid, const nsAString& aDeviceAddress,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::UnregisterGattClientInternal(
+  int aClientIf, BluetoothReplyRunnable* aRunnable)
+{
+}
--- a/dom/bluetooth2/bluez/BluetoothDBusService.h
+++ b/dom/bluetooth2/bluez/BluetoothDBusService.h
@@ -179,16 +179,30 @@ public:
 
   virtual nsresult
   SendSinkMessage(const nsAString& aDeviceAddresses,
                   const nsAString& aMessage) MOZ_OVERRIDE;
 
   virtual nsresult
   SendInputMessage(const nsAString& aDeviceAddresses,
                    const nsAString& aMessage) MOZ_OVERRIDE;
+
+  virtual void
+  ConnectGattClientInternal(const nsAString& aAppUuid,
+                            const nsAString& aDeviceAddress,
+                            BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual void
+  DisconnectGattClientInternal(const nsAString& aAppUuid,
+                               const nsAString& aDeviceAddress,
+                               BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+  virtual void
+  UnregisterGattClientInternal(int aClientIf,
+                               BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
 private:
   nsresult SendGetPropertyMessage(const nsAString& aPath,
                                   const char* aInterface,
                                   void (*aCB)(DBusMessage *, void *),
                                   BluetoothReplyRunnable* aRunnable);
 
   nsresult SendDiscoveryMessage(const char* aMessageName,
                                 BluetoothReplyRunnable* aRunnable);
--- a/dom/bluetooth2/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth2/ipc/BluetoothParent.cpp
@@ -245,16 +245,22 @@ BluetoothParent::RecvPBluetoothRequestCo
       return actor->DoRequest(aRequest.get_IgnoreWaitingCallRequest());
     case Request::TToggleCallsRequest:
       return actor->DoRequest(aRequest.get_ToggleCallsRequest());
 #endif
     case Request::TSendMetaDataRequest:
       return actor->DoRequest(aRequest.get_SendMetaDataRequest());
     case Request::TSendPlayStatusRequest:
       return actor->DoRequest(aRequest.get_SendPlayStatusRequest());
+    case Request::TConnectGattClientRequest:
+      return actor->DoRequest(aRequest.get_ConnectGattClientRequest());
+    case Request::TDisconnectGattClientRequest:
+      return actor->DoRequest(aRequest.get_DisconnectGattClientRequest());
+    case Request::TUnregisterGattClientRequest:
+      return actor->DoRequest(aRequest.get_UnregisterGattClientRequest());
     default:
       MOZ_CRASH("Unknown type!");
   }
 
   MOZ_CRASH("Should never get here!");
 }
 
 PBluetoothRequestParent*
@@ -679,8 +685,46 @@ BluetoothRequestParent::DoRequest(const 
   MOZ_ASSERT(mRequestType == Request::TSendPlayStatusRequest);
 
   mService->SendPlayStatus(aRequest.duration(),
                            aRequest.position(),
                            aRequest.playStatus(),
                            mReplyRunnable.get());
   return true;
 }
+
+bool
+BluetoothRequestParent::DoRequest(const ConnectGattClientRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TConnectGattClientRequest);
+
+  mService->ConnectGattClientInternal(aRequest.appUuid(),
+                                      aRequest.deviceAddress(),
+                                      mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(const DisconnectGattClientRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TDisconnectGattClientRequest);
+
+  mService->DisconnectGattClientInternal(aRequest.appUuid(),
+                                         aRequest.deviceAddress(),
+                                         mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(const UnregisterGattClientRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TUnregisterGattClientRequest);
+
+  mService->UnregisterGattClientInternal(aRequest.clientIf(),
+                                         mReplyRunnable.get());
+
+  return true;
+}
--- a/dom/bluetooth2/ipc/BluetoothParent.h
+++ b/dom/bluetooth2/ipc/BluetoothParent.h
@@ -211,13 +211,22 @@ protected:
   DoRequest(const ToggleCallsRequest& aRequest);
 #endif
 
   bool
   DoRequest(const SendMetaDataRequest& aRequest);
 
   bool
   DoRequest(const SendPlayStatusRequest& aRequest);
+
+  bool
+  DoRequest(const ConnectGattClientRequest& aRequest);
+
+  bool
+  DoRequest(const DisconnectGattClientRequest& aRequest);
+
+  bool
+  DoRequest(const UnregisterGattClientRequest& aRequest);
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_ipc_bluetoothparent_h__
--- a/dom/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
@@ -136,17 +136,17 @@ BluetoothServiceChildProcess::GetPairedD
   request.addresses().AppendElements(aDeviceAddresses);
 
   SendRequest(aRunnable, request);
   return NS_OK;
 }
 
 nsresult
 BluetoothServiceChildProcess::FetchUuidsInternal(
-    const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
+  const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, FetchUuidsRequest(nsString(aDeviceAddress)));
   return NS_OK;
 }
 
 nsresult
 BluetoothServiceChildProcess::StopDiscoveryInternal(
                                               BluetoothReplyRunnable* aRunnable)
@@ -374,16 +374,41 @@ BluetoothServiceChildProcess::SendPlaySt
                                              const nsAString& aPlayStatus,
                                              BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable,
               SendPlayStatusRequest(aDuration, aPosition,
                                     nsString(aPlayStatus)));
 }
 
+void
+BluetoothServiceChildProcess::ConnectGattClientInternal(
+  const nsAString& aAppUuid, const nsAString& aDeviceAddress,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable, ConnectGattClientRequest(nsString(aAppUuid),
+                                                  nsString(aDeviceAddress)));
+}
+
+void
+BluetoothServiceChildProcess::DisconnectGattClientInternal(
+  const nsAString& aAppUuid, const nsAString& aDeviceAddress,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    DisconnectGattClientRequest(nsString(aAppUuid), nsString(aDeviceAddress)));
+}
+
+void
+BluetoothServiceChildProcess::UnregisterGattClientInternal(
+  int aClientIf, BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable, UnregisterGattClientRequest(aClientIf));
+}
+
 nsresult
 BluetoothServiceChildProcess::HandleStartup()
 {
   // Don't need to do anything here for startup since our Create function takes
   // care of the actor machinery.
   return NS_OK;
 }
 
--- a/dom/bluetooth2/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth2/ipc/BluetoothServiceChildProcess.h
@@ -187,16 +187,30 @@ public:
   virtual nsresult
   SendSinkMessage(const nsAString& aDeviceAddresses,
                   const nsAString& aMessage) MOZ_OVERRIDE;
 
   virtual nsresult
   SendInputMessage(const nsAString& aDeviceAddresses,
                    const nsAString& aMessage) MOZ_OVERRIDE;
 
+  virtual void
+  ConnectGattClientInternal(const nsAString& aAppUuid,
+                            const nsAString& aDeviceAddress,
+                            BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual void
+  DisconnectGattClientInternal(const nsAString& aAppUuid,
+                               const nsAString& aDeviceAddress,
+                               BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual void
+  UnregisterGattClientInternal(int aClientIf,
+                               BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
 protected:
   BluetoothServiceChildProcess();
   virtual ~BluetoothServiceChildProcess();
 
   void
   NoteDeadActor();
 
   void
--- a/dom/bluetooth2/ipc/PBluetooth.ipdl
+++ b/dom/bluetooth2/ipc/PBluetooth.ipdl
@@ -171,16 +171,33 @@ struct SendMetaDataRequest
 
 struct SendPlayStatusRequest
 {
   int64_t duration;
   int64_t position;
   nsString playStatus;
 };
 
+struct ConnectGattClientRequest
+{
+  nsString appUuid;
+  nsString deviceAddress;
+};
+
+struct DisconnectGattClientRequest
+{
+  nsString appUuid;
+  nsString deviceAddress;
+};
+
+struct UnregisterGattClientRequest
+{
+  int clientIf;
+};
+
 union Request
 {
   GetAdaptersRequest;
   StartBluetoothRequest;
   StopBluetoothRequest;
   SetPropertyRequest;
   GetPropertyRequest;
   StartDiscoveryRequest;
@@ -203,16 +220,19 @@ union Request
   ConnectScoRequest;
   DisconnectScoRequest;
   IsScoConnectedRequest;
   AnswerWaitingCallRequest;
   IgnoreWaitingCallRequest;
   ToggleCallsRequest;
   SendMetaDataRequest;
   SendPlayStatusRequest;
+  ConnectGattClientRequest;
+  DisconnectGattClientRequest;
+  UnregisterGattClientRequest;
 };
 
 protocol PBluetooth
 {
   manager PContent;
   manages PBluetoothRequest;
 
   /**
--- a/dom/bluetooth2/moz.build
+++ b/dom/bluetooth2/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 if CONFIG['MOZ_B2G_BT']:
     SOURCES += [
         'BluetoothAdapter.cpp',
         'BluetoothClassOfDevice.cpp',
         'BluetoothDevice.cpp',
         'BluetoothDiscoveryHandle.cpp',
+        'BluetoothGatt.cpp',
         'BluetoothHidManager.cpp',
         'BluetoothInterface.cpp',
         'BluetoothManager.cpp',
         'BluetoothPairingHandle.cpp',
         'BluetoothPairingListener.cpp',
         'BluetoothProfileController.cpp',
         'BluetoothReplyRunnable.cpp',
         'BluetoothService.cpp',
@@ -117,16 +118,17 @@ EXPORTS.mozilla.dom.bluetooth.ipc += [
 ]
 
 EXPORTS.mozilla.dom.bluetooth += [
     'BluetoothAdapter.h',
     'BluetoothClassOfDevice.h',
     'BluetoothCommon.h',
     'BluetoothDevice.h',
     'BluetoothDiscoveryHandle.h',
+    'BluetoothGatt.h',
     'BluetoothManager.h',
     'BluetoothPairingHandle.h',
     'BluetoothPairingListener.h',
 ]
 
 IPDL_SOURCES += [
     'ipc/BluetoothTypes.ipdlh',
     'ipc/PBluetooth.ipdl',
--- a/dom/camera/GonkRecorderProfiles.def
+++ b/dom/camera/GonkRecorderProfiles.def
@@ -1,10 +1,10 @@
 /*
- * Copyright (C) 2012 Mozilla Foundation
+ * Copyright (C) 2012-2015 Mozilla Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
@@ -35,19 +35,20 @@ DEF_GONK_RECORDER_PROFILE(CAMCORDER_QUAL
 DEF_GONK_RECORDER_PROFILE(CAMCORDER_QUALITY_1080P, "1080p")
 
 /**
  * The following profiles do not appear in all versions of the
  * MediaProfiles.h and must be detected at runtime. Additionally some
  * profiles may have more than one resolution, depending on the camera.
  */
 
-DEF_GONK_RECORDER_PROFILE_DETECT("fwvga", 864, 480)
-DEF_GONK_RECORDER_PROFILE_DETECT("fwvga", 854, 480)
-DEF_GONK_RECORDER_PROFILE_DETECT("wvga",  800, 480)
-DEF_GONK_RECORDER_PROFILE_DETECT("wvga",  768, 480)
-DEF_GONK_RECORDER_PROFILE_DETECT("vga",   640, 480)
-DEF_GONK_RECORDER_PROFILE_DETECT("hvga",  480, 320)
-DEF_GONK_RECORDER_PROFILE_DETECT("wqvga", 400, 240)
-DEF_GONK_RECORDER_PROFILE_DETECT("qvga",  320, 240)
+DEF_GONK_RECORDER_PROFILE_DETECT("4kuhd", 3840, 2160)
+DEF_GONK_RECORDER_PROFILE_DETECT("fwvga",  864,  480)
+DEF_GONK_RECORDER_PROFILE_DETECT("fwvga",  854,  480)
+DEF_GONK_RECORDER_PROFILE_DETECT("wvga",   800,  480)
+DEF_GONK_RECORDER_PROFILE_DETECT("wvga",   768,  480)
+DEF_GONK_RECORDER_PROFILE_DETECT("vga",    640,  480)
+DEF_GONK_RECORDER_PROFILE_DETECT("hvga",   480,  320)
+DEF_GONK_RECORDER_PROFILE_DETECT("wqvga",  400,  240)
+DEF_GONK_RECORDER_PROFILE_DETECT("qvga",   320,  240)
 
 #undef DEF_GONK_RECORDER_PROFILE
 #undef DEF_GONK_RECORDER_PROFILE_DETECT
--- a/dom/media/omx/OmxDecoder.cpp
+++ b/dom/media/omx/OmxDecoder.cpp
@@ -595,17 +595,17 @@ bool OmxDecoder::ReadVideo(VideoFrame *a
         continue;
       } else if (err != OK) {
         OD_LOG("Unexpected error when seeking to %lld", aTimeUs);
         break;
       }
       // For some codecs, the length of first decoded frame after seek is 0.
       // Need to ignore it and continue to find the next one
       if (mVideoBuffer->range_length() == 0) {
-        ReleaseVideoBuffer();
+        PostReleaseVideoBuffer(mVideoBuffer, FenceHandle());
         findNextBuffer = true;
       }
     }
     aDoSeek = false;
   } else {
     err = mVideoSource->read(&mVideoBuffer);
   }
 
--- a/dom/nfc/gonk/Nfc.js
+++ b/dom/nfc/gonk/Nfc.js
@@ -173,18 +173,20 @@ XPCOMUtils.defineLazyGetter(this, "gMess
         dump("invalid target");
         return;
       }
 
       target.sendAsyncMessage("NFC:DOMEvent", options);
     },
 
     setFocusApp: function setFocusApp(id, isFocus) {
-      // if calling setNFCFocus(true) on the same browser-element, ignore.
-      if (isFocus && (id == this.focusApp)) {
+      // if calling setNFCFocus(true) on the browser-element which is already
+      // focused, or calling setNFCFocus(false) on the browser-element which has
+      // lost focus already, ignore.
+      if (isFocus == (id == this.focusApp)) {
         return;
       }
 
       if (this.focusApp != NFC.SYSTEM_APP_ID) {
         this.onFocusChanged(this.focusApp, false);
       }
 
       if (isFocus) {
--- a/dom/system/gonk/android_audio/AudioSystem.h
+++ b/dom/system/gonk/android_audio/AudioSystem.h
@@ -309,17 +309,17 @@ typedef enum {
                                AUDIO_DEVICE_IN_VOICE_CALL |
                                AUDIO_DEVICE_IN_BACK_MIC |
                                AUDIO_DEVICE_IN_ANC_HEADSET |
                                AUDIO_DEVICE_IN_FM_RX |
                                AUDIO_DEVICE_IN_FM_RX_A2DP |
                                AUDIO_DEVICE_IN_DEFAULT),
     AUDIO_DEVICE_IN_ALL_SCO = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
 } audio_devices_t;
-#else
+#elif ANDROID_VERSION < 21
 enum {
     AUDIO_DEVICE_NONE                          = 0x0,
     /* reserved bits */
     AUDIO_DEVICE_BIT_IN                        = 0x80000000,
     AUDIO_DEVICE_BIT_DEFAULT                   = 0x40000000,
     /* output devices */
     AUDIO_DEVICE_OUT_EARPIECE                  = 0x1,
     AUDIO_DEVICE_OUT_SPEAKER                   = 0x2,
@@ -411,16 +411,143 @@ enum {
                                AUDIO_DEVICE_IN_FM_RX |
                                AUDIO_DEVICE_IN_FM_RX_A2DP |
                                AUDIO_DEVICE_IN_PROXY |
                                AUDIO_DEVICE_IN_DEFAULT),
     AUDIO_DEVICE_IN_ALL_SCO = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
 };
 
 typedef uint32_t audio_devices_t;
+#else
+enum {
+    AUDIO_DEVICE_NONE                          = 0x0,
+    /* reserved bits */
+    AUDIO_DEVICE_BIT_IN                        = 0x80000000,
+    AUDIO_DEVICE_BIT_DEFAULT                   = 0x40000000,
+    /* output devices */
+    AUDIO_DEVICE_OUT_EARPIECE                  = 0x1,
+    AUDIO_DEVICE_OUT_SPEAKER                   = 0x2,
+    AUDIO_DEVICE_OUT_WIRED_HEADSET             = 0x4,
+    AUDIO_DEVICE_OUT_WIRED_HEADPHONE           = 0x8,
+    AUDIO_DEVICE_OUT_BLUETOOTH_SCO             = 0x10,
+    AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET     = 0x20,
+    AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT      = 0x40,
+    AUDIO_DEVICE_OUT_BLUETOOTH_A2DP            = 0x80,
+    AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100,
+    AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER    = 0x200,
+    AUDIO_DEVICE_OUT_AUX_DIGITAL               = 0x400,
+    AUDIO_DEVICE_OUT_HDMI                      = AUDIO_DEVICE_OUT_AUX_DIGITAL,
+    /* uses an analog connection (multiplexed over the USB connector pins for instance) */
+    AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET         = 0x800,
+    AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET         = 0x1000,
+    /* USB accessory mode: your Android device is a USB device and the dock is a USB host */
+    AUDIO_DEVICE_OUT_USB_ACCESSORY             = 0x2000,
+    /* USB host mode: your Android device is a USB host and the dock is a USB device */
+    AUDIO_DEVICE_OUT_USB_DEVICE                = 0x4000,
+    AUDIO_DEVICE_OUT_REMOTE_SUBMIX             = 0x8000,
+    /* Telephony voice TX path */
+    AUDIO_DEVICE_OUT_TELEPHONY_TX              = 0x10000,
+    /* Analog jack with line impedance detected */
+    AUDIO_DEVICE_OUT_LINE                      = 0x20000,
+    /* HDMI Audio Return Channel */
+    AUDIO_DEVICE_OUT_HDMI_ARC                  = 0x40000,
+    /* S/PDIF out */
+    AUDIO_DEVICE_OUT_SPDIF                     = 0x80000,
+    /* FM transmitter out */
+    AUDIO_DEVICE_OUT_FM                        = 0x100000,
+    /* Line out for av devices */
+    AUDIO_DEVICE_OUT_AUX_LINE                  = 0x200000,
+    /* limited-output speaker device for acoustic safety */
+    AUDIO_DEVICE_OUT_SPEAKER_SAFE              = 0x400000,
+    AUDIO_DEVICE_OUT_DEFAULT                   = AUDIO_DEVICE_BIT_DEFAULT,
+    AUDIO_DEVICE_OUT_ALL      = (AUDIO_DEVICE_OUT_EARPIECE |
+                                 AUDIO_DEVICE_OUT_SPEAKER |
+                                 AUDIO_DEVICE_OUT_WIRED_HEADSET |
+                                 AUDIO_DEVICE_OUT_WIRED_HEADPHONE |
+                                 AUDIO_DEVICE_OUT_BLUETOOTH_SCO |
+                                 AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
+                                 AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT |
+                                 AUDIO_DEVICE_OUT_BLUETOOTH_A2DP |
+                                 AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+                                 AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
+                                 AUDIO_DEVICE_OUT_AUX_DIGITAL |
+                                 AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET |
+                                 AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET |
+                                 AUDIO_DEVICE_OUT_USB_ACCESSORY |
+                                 AUDIO_DEVICE_OUT_USB_DEVICE |
+                                 AUDIO_DEVICE_OUT_REMOTE_SUBMIX |
+                                 AUDIO_DEVICE_OUT_TELEPHONY_TX |
+                                 AUDIO_DEVICE_OUT_LINE |
+                                 AUDIO_DEVICE_OUT_HDMI_ARC |
+                                 AUDIO_DEVICE_OUT_SPDIF |
+                                 AUDIO_DEVICE_OUT_FM |
+                                 AUDIO_DEVICE_OUT_AUX_LINE |
+                                 AUDIO_DEVICE_OUT_SPEAKER_SAFE |
+                                 AUDIO_DEVICE_OUT_DEFAULT),
+    AUDIO_DEVICE_OUT_ALL_A2DP = (AUDIO_DEVICE_OUT_BLUETOOTH_A2DP |
+                                 AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+                                 AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER),
+    AUDIO_DEVICE_OUT_ALL_SCO  = (AUDIO_DEVICE_OUT_BLUETOOTH_SCO |
+                                 AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
+                                 AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT),
+    AUDIO_DEVICE_OUT_ALL_USB  = (AUDIO_DEVICE_OUT_USB_ACCESSORY |
+                                 AUDIO_DEVICE_OUT_USB_DEVICE),
+    /* input devices */
+    AUDIO_DEVICE_IN_COMMUNICATION         = AUDIO_DEVICE_BIT_IN | 0x1,
+    AUDIO_DEVICE_IN_AMBIENT               = AUDIO_DEVICE_BIT_IN | 0x2,
+    AUDIO_DEVICE_IN_BUILTIN_MIC           = AUDIO_DEVICE_BIT_IN | 0x4,
+    AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET = AUDIO_DEVICE_BIT_IN | 0x8,
+    AUDIO_DEVICE_IN_WIRED_HEADSET         = AUDIO_DEVICE_BIT_IN | 0x10,
+    AUDIO_DEVICE_IN_AUX_DIGITAL           = AUDIO_DEVICE_BIT_IN | 0x20,
+    AUDIO_DEVICE_IN_HDMI                  = AUDIO_DEVICE_IN_AUX_DIGITAL,
+    /* Telephony voice RX path */
+    AUDIO_DEVICE_IN_VOICE_CALL            = AUDIO_DEVICE_BIT_IN | 0x40,
+    AUDIO_DEVICE_IN_BACK_MIC              = AUDIO_DEVICE_BIT_IN | 0x80,
+    AUDIO_DEVICE_IN_REMOTE_SUBMIX         = AUDIO_DEVICE_BIT_IN | 0x100,
+    AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET     = AUDIO_DEVICE_BIT_IN | 0x200,
+    AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET     = AUDIO_DEVICE_BIT_IN | 0x400,
+    AUDIO_DEVICE_IN_USB_ACCESSORY         = AUDIO_DEVICE_BIT_IN | 0x800,
+    AUDIO_DEVICE_IN_USB_DEVICE            = AUDIO_DEVICE_BIT_IN | 0x1000,
+    /* FM tuner input */
+    AUDIO_DEVICE_IN_FM_TUNER              = AUDIO_DEVICE_BIT_IN | 0x2000,
+    /* TV tuner input */
+    AUDIO_DEVICE_IN_TV_TUNER              = AUDIO_DEVICE_BIT_IN | 0x4000,
+    /* Analog jack with line impedance detected */
+    AUDIO_DEVICE_IN_LINE                  = AUDIO_DEVICE_BIT_IN | 0x8000,
+    /* S/PDIF in */
+    AUDIO_DEVICE_IN_SPDIF                 = AUDIO_DEVICE_BIT_IN | 0x10000,
+    AUDIO_DEVICE_IN_BLUETOOTH_A2DP        = AUDIO_DEVICE_BIT_IN | 0x20000,
+    AUDIO_DEVICE_IN_LOOPBACK              = AUDIO_DEVICE_BIT_IN | 0x40000,
+    AUDIO_DEVICE_IN_DEFAULT               = AUDIO_DEVICE_BIT_IN | AUDIO_DEVICE_BIT_DEFAULT,
+    AUDIO_DEVICE_IN_ALL     = (AUDIO_DEVICE_IN_COMMUNICATION |
+                               AUDIO_DEVICE_IN_AMBIENT |
+                               AUDIO_DEVICE_IN_BUILTIN_MIC |
+                               AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET |
+                               AUDIO_DEVICE_IN_WIRED_HEADSET |
+                               AUDIO_DEVICE_IN_AUX_DIGITAL |
+                               AUDIO_DEVICE_IN_VOICE_CALL |
+                               AUDIO_DEVICE_IN_BACK_MIC |
+                               AUDIO_DEVICE_IN_REMOTE_SUBMIX |
+                               AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET |
+                               AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET |
+                               AUDIO_DEVICE_IN_USB_ACCESSORY |
+                               AUDIO_DEVICE_IN_USB_DEVICE |
+                               AUDIO_DEVICE_IN_FM_TUNER |
+                               AUDIO_DEVICE_IN_TV_TUNER |
+                               AUDIO_DEVICE_IN_LINE |
+                               AUDIO_DEVICE_IN_SPDIF |
+                               AUDIO_DEVICE_IN_BLUETOOTH_A2DP |
+                               AUDIO_DEVICE_IN_LOOPBACK |
+                               AUDIO_DEVICE_IN_DEFAULT),
+    AUDIO_DEVICE_IN_ALL_SCO = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
+    AUDIO_DEVICE_IN_ALL_USB = (AUDIO_DEVICE_IN_USB_ACCESSORY |
+                               AUDIO_DEVICE_IN_USB_DEVICE),
+};
+
+typedef uint32_t audio_devices_t;
 #endif
 
 /* device connection states used for audio_policy->set_device_connection_state()
  *  */
 typedef enum {
     AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
     AUDIO_POLICY_DEVICE_STATE_AVAILABLE,
     
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -460,16 +460,18 @@ this.NETWORK_CREG_TECH_EVDOA = 8;
 this.NETWORK_CREG_TECH_HSDPA = 9;
 this.NETWORK_CREG_TECH_HSUPA = 10;
 this.NETWORK_CREG_TECH_HSPA = 11;
 this.NETWORK_CREG_TECH_EVDOB = 12;
 this.NETWORK_CREG_TECH_EHRPD = 13;
 this.NETWORK_CREG_TECH_LTE = 14;
 this.NETWORK_CREG_TECH_HSPAP = 15;
 this.NETWORK_CREG_TECH_GSM = 16;
+this.NETWORK_CREG_TECH_DCHSPAP_1 = 18; // Some devices reports as 18
+this.NETWORK_CREG_TECH_DCHSPAP_2 = 19; // Some others report it as 19
 
 this.CELL_INFO_TYPE_GSM = 1;
 this.CELL_INFO_TYPE_CDMA = 2;
 this.CELL_INFO_TYPE_LTE = 3;
 this.CELL_INFO_TYPE_WCDMA = 4;
 
 // Order matters.
 this.AUDIO_STATE_NO_CALL  = 0;
@@ -2908,17 +2910,20 @@ this.GECKO_RADIO_TECH = [
   "evdoa",
   "hsdpa",
   "hsupa",
   "hspa",
   "evdob",
   "ehrpd",
   "lte",
   "hspa+",
-  "gsm"
+  "gsm",
+  null,
+  "hspa+", // DC-HSPA+
+  "hspa+"
 ];
 
 this.GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN = -1;
 
 // Call forwarding action. Must be in sync with nsIMobileConnectionService interface
 this.CALL_FORWARD_ACTION_DISABLE = 0;
 this.CALL_FORWARD_ACTION_ENABLE = 1;
 this.CALL_FORWARD_ACTION_QUERY_STATUS = 2;
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -4221,16 +4221,18 @@ RilObject.prototype = {
       case NETWORK_CREG_TECH_EDGE:
       case NETWORK_CREG_TECH_UMTS:
       case NETWORK_CREG_TECH_HSDPA:
       case NETWORK_CREG_TECH_HSUPA:
       case NETWORK_CREG_TECH_HSPA:
       case NETWORK_CREG_TECH_LTE:
       case NETWORK_CREG_TECH_HSPAP:
       case NETWORK_CREG_TECH_GSM:
+      case NETWORK_CREG_TECH_DCHSPAP_1:
+      case NETWORK_CREG_TECH_DCHSPAP_2:
         return true;
     }
 
     return false;
   },
 
   /**
    * Process radio technology change.
@@ -6189,16 +6191,18 @@ RilObject.prototype[REQUEST_GET_NEIGHBOR
         cellId.gsmCellId = this.parseInt(cid.substring(4), -1, 16);
         cellId.gsmLocationAreaCode = this.parseInt(cid.substring(0, 4), -1, 16);
         break;
       case NETWORK_CREG_TECH_UMTS:
       case NETWORK_CREG_TECH_HSDPA:
       case NETWORK_CREG_TECH_HSUPA:
       case NETWORK_CREG_TECH_HSPA:
       case NETWORK_CREG_TECH_HSPAP:
+      case NETWORK_CREG_TECH_DCHSPAP_1:
+      case NETWORK_CREG_TECH_DCHSPAP_2:
         cellId.wcdmaPsc = this.parseInt(cid, -1, 16);
         break;
     }
 
     neighboringCellIds.push(cellId);
   }
 
   options.result = neighboringCellIds;
--- a/dom/webidl/BluetoothDevice2.webidl
+++ b/dom/webidl/BluetoothDevice2.webidl
@@ -8,16 +8,22 @@
 interface BluetoothDevice : EventTarget
 {
   readonly attribute DOMString              address;
   readonly attribute BluetoothClassOfDevice cod;
   readonly attribute DOMString              name;
   readonly attribute boolean                paired;
   readonly attribute BluetoothDeviceType    type;
 
+  /**
+   * Retrieve the BluetoothGatt interface to interact with remote BLE devices.
+   * This attribute is null if the device type is not dual or le.
+   */
+  readonly attribute BluetoothGatt?         gatt;
+
   [Cached, Pure]
   readonly attribute sequence<DOMString>    uuids;
 
   // Fired when attribute(s) of BluetoothDevice changed
            attribute EventHandler           onattributechanged;
 
   /**
    * Fetch the up-to-date UUID list of each bluetooth service that the device
new file mode 100644
--- /dev/null
+++ b/dom/webidl/BluetoothGatt.webidl
@@ -0,0 +1,38 @@
+/* -*- 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/. */
+
+[CheckPermissions="bluetooth"]
+interface BluetoothGatt : EventTarget
+{
+  readonly attribute BluetoothConnectionState       connectionState;
+
+  // Fired when attribute connectionState changed
+           attribute EventHandler                   onconnectionstatechanged;
+
+  /**
+   * Connect/Disconnect to the remote BLE device if the connectionState is
+   * disconnected/connected. Otherwise, the Promise will be rejected directly.
+   *
+   * If current connectionState is disconnected/connected,
+   *   1) connectionState change to connecting/disconnecting along with a
+   *      connectionstatechanged event.
+   *   2) connectionState change to connected/disconnected if the operation
+   *      succeeds. Otherwise, change to disconnected/connected.
+   *   3) Promise is resolved or rejected according to the operation result.
+   */
+  [NewObject]
+  Promise<void>                                     connect();
+  [NewObject]
+  Promise<void>                                     disconnect();
+};
+
+enum BluetoothConnectionState
+{
+  "disconnected",
+  "disconnecting",
+  "connected",
+  "connecting"
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -627,16 +627,17 @@ if CONFIG['MOZ_DEBUG']:
 
 if CONFIG['MOZ_B2G_BT']:
     if CONFIG['MOZ_B2G_BT_API_V2']:
         WEBIDL_FILES += [
             'BluetoothAdapter2.webidl',
             'BluetoothClassOfDevice.webidl',
             'BluetoothDevice2.webidl',
             'BluetoothDiscoveryHandle.webidl',
+            'BluetoothGatt.webidl',
             'BluetoothManager2.webidl',
             'BluetoothPairingHandle.webidl',
             'BluetoothPairingListener.webidl',
         ]
     else:
         WEBIDL_FILES += [
             'BluetoothAdapter.webidl',
             'BluetoothDevice.webidl',
--- a/dom/xbl/test/mochitest.ini
+++ b/dom/xbl/test/mochitest.ini
@@ -32,14 +32,15 @@ skip-if = (buildapp == 'b2g' && toolkit 
 [test_bug468210.xhtml]
 [test_bug481558.html]
 [test_bug526178.xhtml]
 [test_bug542406.xhtml]
 [test_bug591198.html]
 [test_bug639338.xhtml]
 [test_bug790265.xhtml]
 [test_bug821850.html]
+skip-if = buildapp == 'mulet'
 [test_bug844783.html]
 [test_bug872273.xhtml]
 [test_bug946815.html]
 skip-if = toolkit != "gonk"
 [test_bug1086996.xhtml]
 [test_bug1098628_throw_from_construct.xhtml]
index b7159c24b8a6b2b91202f7a3e52aa44d65a14908..441067672e0418d6b0ae25ea2febd866e81167af
GIT binary patch
literal 2130
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
OEALG8pwv4Cru+fp41Od4
index f350cd393d94a043c5756c40e4b73b93d8ca92fd..cf033ace8a7f4895cf8e54a90f17ad199fe17912
GIT binary patch
literal 2760
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
F{2x<^d_MpH
index a3b4725cea1ec4e3a891ded2a5d7bd70d70b7f50..153b7339657e245b35c339c9c7b5f063a3f9c4a3
GIT binary patch
literal 3593
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
F{|9PQDU<*J
index 6599b66391d1d381a6d724d7ce846b15e1427cb2..ae8b0f40bfbb365e1eed213ddaeee7cb4459d54d
GIT binary patch
literal 3244
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
JB{l)E{{{4gswe;e
index 1259d00f770c74afdef2f6d4b498d71ea77b33fb..6dd2bd8beeb16e0066f5e32710d29c371bb25b29
GIT binary patch
literal 2016
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
CGC~pn
index 4e529badb4f47efc85bdc77ea04262a82a30ddba..bb26138b552711982d7a50e7bc186f06658420d2
GIT binary patch
literal 2572
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
E1+1nXga7~l
index 43244bfc79cce347e93feb903c18f7993eb2004b..c803546da756433ce00abcfde5ede6fcaa20d8dd
GIT binary patch
literal 3383
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
index f0a0a5dc8e4edd3d44bc8763deee0cb9cb015301..103561b642e5649c46a7a2af99440ae265e10542
GIT binary patch
literal 3060
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
ZfY;6CRZ49~+SmTWZE$#Og{5!Ye*l|EMc)7b
index 076276acf6f83f35f2b8e2fe4e38db6cf157fda7..84cde557460efe99928d07a876d99076bfde282f
GIT binary patch
literal 2034
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
BD^~yj
index 6da5782e86ed44591466a07e745ea2d7c9d24e4c..b7e37b1fa066510ee627bde90e7c5dcb06a5ca8f
GIT binary patch
literal 2594
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
X-adK8ne16L_iCegZlG3rgr@%m{Z>1S
index f8f61d22116c7a5362cf460d5eaa230fd187182f..8ce1f9453906a258aea9df37e0a7d65a988ef745
GIT binary patch
literal 3405
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
index 1d7553d52a084553aae96bc20d1b265cd55ce344..2ead80fa4e5bfd552bb485ce57c9adfb78b1c23b
GIT binary patch
literal 3105
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
Xt~&tGxBF#q{n75`?CDgw8<X;HUHN1D
--- a/embedding/test/mochitest.ini
+++ b/embedding/test/mochitest.ini
@@ -4,11 +4,11 @@ support-files =
   bug293834_form.html
 
 [test_bug293834.html]
 [test_bug499115.html]
 [test_nsFind.html]
 [test_private_window_from_content.html]
 # Next two tests are disabled in e10s because of bug 989501.
 [test_window_open_position_constraint.html]
-skip-if = toolkit == 'android' || e10s
+skip-if = toolkit == 'android' || e10s || buildapp == 'mulet'
 [test_window_open_units.html]
-skip-if = toolkit == 'android' || e10s
+skip-if = toolkit == 'android' || e10s || buildapp == 'mulet'
--- a/layout/base/tests/marionette/test_selectioncarets.py
+++ b/layout/base/tests/marionette/test_selectioncarets.py
@@ -1,16 +1,17 @@
 # -*- coding: utf-8 -*-
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from by import By
 from marionette import Actions
 from marionette_test import MarionetteTestCase
+from math import ceil, floor
 from selection import SelectionManager
 from gestures import long_press_without_contextmenu
 
 
 class SelectionCaretsTest(MarionetteTestCase):
     _long_press_time = 1        # 1 second
     _input_selector = (By.ID, 'input')
     _textarea_selector = (By.ID, 'textarea')
@@ -180,27 +181,34 @@ class SelectionCaretsTest(MarionetteTest
         self._long_press_to_select(el, x, y)
         target_content = sel.selected_content
 
         # Move the left caret to the position of the right caret to trigger
         # carets overlapping.
         (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
         self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()
 
-        # Caret width is 29px, so we make a series of hit tests for the two
-        # tilted carets. If any of the hits is missed, selection would be
-        # collapsed and both two carets should not be draggable.
+        # We make two hit tests targeting the left edge of the left tilted caret
+        # and the right edge of the right tilted caret. If either of the hits is
+        # missed, selection would be collapsed and both carets should not be
+        # draggable.
         (caret3_x, caret3_y), (caret4_x, caret4_y) = sel.selection_carets_location()
-        right_x = int(caret4_x + 0.5)
-        for i in range (right_x, right_x + 29, + 1):
-            self.actions.press(el, i, caret4_y).release().perform()
+
+        # The following values are from ua.css.
+        caret_width = 44
+        caret_margin_left = -23
+        tilt_right_margin_left = 18
+        tilt_left_margin_left = -17
 
-        left_x = int(caret3_x - 0.5)
-        for i in range (left_x, left_x - 29, - 1):
-            self.actions.press(el, i, caret3_y).release().perform()
+        left_caret_left_edge_x = caret3_x + caret_margin_left + tilt_left_margin_left
+        el.tap(ceil(left_caret_left_edge_x), caret3_y)
+
+        right_caret_right_edge_x = (caret4_x + caret_margin_left +
+                                    tilt_right_margin_left + caret_width)
+        el.tap(floor(right_caret_right_edge_x), caret4_y)
 
         # Drag the left caret back to the initial selection, the first word.
         self.actions.flick(el, caret3_x, caret3_y, caret1_x, caret1_y).perform()
 
         assertFunc(target_content, sel.selected_content)
 
     ########################################################################
     # <input> test cases with selection carets enabled
@@ -375,13 +383,11 @@ class SelectionCaretsTest(MarionetteTest
 
     def test_content_non_editable_handle_tilt_when_carets_overlap_to_each_other(self):
         self.openTestHtml(enabled=True)
         self._test_handle_tilt_when_carets_overlap_to_each_other(self._content, self.assertEqual)
 
     ########################################################################
     # <div> contenteditable2 test cases with selection carets enabled
     ########################################################################
-    def test_contenteditable_minimum_select_one_character(self):
+    def test_contenteditable2_minimum_select_one_character(self):
         self.openTestHtml(enabled=True)
         self._test_minimum_select_one_character(self._contenteditable2, self.assertEqual)
-
-
--- a/layout/style/ua.css
+++ b/layout/style/ua.css
@@ -320,43 +320,43 @@ div:-moz-native-anonymous.moz-selectionc
   position: absolute;
 }
 
 div:-moz-native-anonymous.moz-touchcaret,
 div:-moz-native-anonymous.moz-selectioncaret-left,
 div:-moz-native-anonymous.moz-selectioncaret-right,
 div:-moz-native-anonymous.moz-selectioncaret-left > div,
 div:-moz-native-anonymous.moz-selectioncaret-right > div {
-  width: 29px;
-  height: 31px;
+  width: 44px;
+  height: 47px;
   background-position: center center;
   background-size: 100% 100%;
   z-index: 2147483647;
 }
 
 div:-moz-native-anonymous.moz-touchcaret,
 div:-moz-native-anonymous.moz-selectioncaret-left > div,
 div:-moz-native-anonymous.moz-selectioncaret-right > div {
   background-image: url("resource://gre/res/text_caret.png");
 }
 
 div:-moz-native-anonymous.moz-touchcaret,
 div:-moz-native-anonymous.moz-selectioncaret-left,
 div:-moz-native-anonymous.moz-selectioncaret-right {
-  margin-left: -15px;
+  margin-left: -23px;
 }
 
 div:-moz-native-anonymous.moz-selectioncaret-left.tilt > div {
   background-image: url("resource://gre/res/text_caret_tilt_left.png");
-  margin-left: -14px;
+  margin-left: -17px;
 }
 
 div:-moz-native-anonymous.moz-selectioncaret-right.tilt > div {
   background-image: url("resource://gre/res/text_caret_tilt_right.png");
-  margin-left: 14px;
+  margin-left: 18px;
 }
 
 @media (min-resolution: 1.5dppx) {
   div:-moz-native-anonymous.moz-touchcaret,
   div:-moz-native-anonymous.moz-selectioncaret-left > div,
   div:-moz-native-anonymous.moz-selectioncaret-right > div {
     background-image: url("resource://gre/res/text_caret@1.5x.png");
   }
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -275,22 +275,24 @@ pref("browser.search.suggest.prompted", 
 pref("browser.search.loadFromJars", true);
 pref("browser.search.jarURIs", "chrome://browser/locale/searchplugins/");
 
 // tell the search service that we don't really expose the "current engine"
 pref("browser.search.noCurrentEngine", true);
 
 // Control media casting & mirroring features
 pref("browser.casting.enabled", true);
-pref("browser.mirroring.enabled", true);
 #ifdef RELEASE_BUILD
 // Roku does not yet support mirroring in production
 pref("browser.mirroring.enabled.roku", false);
+// Chromecast mirroring is broken (bug 1131084)
+pref("browser.mirroring.enabled", false);
 #else
 pref("browser.mirroring.enabled.roku", true);
+pref("browser.mirroring.enabled", true);
 #endif
 
 // Enable sparse localization by setting a few package locale overrides
 pref("chrome.override_package.global", "browser");
 pref("chrome.override_package.mozapps", "browser");
 pref("chrome.override_package.passwordmgr", "browser");
 
 // enable xul error pages
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -9,16 +9,17 @@ skip-if = android_version == "10"
 # disabled on 2.3; bug 941624, bug 1063509, bug 1073374, bug 1087221, bug 1088023, bug 1088027, bug 1090206
 skip-if = android_version == "10" || processor == "x86"
 [testAddSearchEngine]
 # disabled on Android 2.3; bug 979552
 skip-if = android_version == "10"
 [testAdobeFlash]
 skip-if = processor == "x86"
 [testANRReporter]
+[testAppConstants]
 [testAwesomebar]
 [testAxisLocking]
 # disabled on x86 only; bug 927476
 skip-if = processor == "x86"
 # [testBookmark] # see bug 915350
 [testBookmarksPanel]
 # disabled on 2.3; bug 979615
 skip-if = android_version == "10"
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testAppConstants.java
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.tests;
+
+public class testAppConstants extends JavascriptTest {
+    public testAppConstants() {
+        super("testAppConstants.js");
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testAppConstants.js
@@ -0,0 +1,15 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+
+add_task(function* testAppConstants() {
+  let packageName = AppConstants.ANDROID_PACKAGE_NAME
+  do_check_neq(packageName, "@ANDROID_PACKAGE_NAME@");
+  do_check_true(packageName.length > 0);
+});
+
+
+run_next_test();
--- a/mobile/android/chrome/content/FindHelper.js
+++ b/mobile/android/chrome/content/FindHelper.js
@@ -145,27 +145,14 @@ var FindHelper = {
           // this should never happen
           Cu.reportError("Warning: selected tab changed during find!");
           // fall through and restore viewport on the initial tab anyway
         }
         this._targetTab.setViewport(JSON.parse(this._initialViewport));
         this._targetTab.sendViewportUpdate();
       }
     } else {
-      // Defines the space around the highlighted element as a factor of the element's size.
-      const spacingFactor = 6;
-
-      // We replace the start of the zoom rect to keep the highlighted word in the middle.
-      // We divide this offset by two to consider a spacing on each side of the rect.
-      let x = aData.rect.x + (aData.rect.width * (1 - spacingFactor)) / 2;
-      let y = aData.rect.y + (aData.rect.height * (1 - spacingFactor)) / 2;
-
-      let rect = new Rect(Math.max(x, 0),
-                          Math.max(y, 0),
-                          // we use a bigger viewport than just the highlighted word
-                          aData.rect.width * spacingFactor,
-                          aData.rect.height * spacingFactor);
-
-      ZoomHelper.zoomToRect(rect);
+      // Disabled until bug 1014113 is fixed
+      // ZoomHelper.zoomToRect(aData.rect);
       this._viewportChanged = true;
     }
   }
 };
--- a/mobile/android/chrome/content/ZoomHelper.js
+++ b/mobile/android/chrome/content/ZoomHelper.js
@@ -118,21 +118,16 @@ var ZoomHelper = {
     }
   },
 
   /* Zoom to a specific part of the screen defined by a rect,
    * optionally keeping a particular part of it in view
    * if it is really tall.
    */
   zoomToRect: function(aRect, aClickY = -1) {
-    if(aRect.isEmpty()) {
-      // Protect from empty or negative-sized rects & potentials NaN in following calculations
-      return;
-    }
-
     let viewport = BrowserApp.selectedTab.getViewport();
 
     let rect = {
       x: aRect.x,
       y: aRect.y,
       w: aRect.width,
       h: Math.min(aRect.width * viewport.cssHeight / viewport.cssWidth, aRect.height)
     };
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4460,16 +4460,22 @@ pref("dom.mozSettings.SettingsManager.ve
 pref("dom.mozSettings.SettingsRequestManager.verbose.enabled", false);
 pref("dom.mozSettings.SettingsService.verbose.enabled", false);
 
 // Controlling whether we want to allow forcing some Settings
 // IndexedDB transactions to be opened as readonly or keep everything as
 // readwrite.
 pref("dom.mozSettings.allowForceReadOnly", false);
 
+// The interval at which to check for slow running addons
+pref("browser.addon-watch.interval", 120000);
+pref("browser.addon-watch.ignore", "[\"mochikit@mozilla.org\",\"special-powers@mozilla.org\"]");
+// the percentage of time addons are allowed to use without being labeled slow
+pref("browser.addon-watch.percentage-limit", 1);
+
 // RequestSync API is disabled by default.
 pref("dom.requestSync.enabled", false);
 
 // Search service settings
 pref("browser.search.log", false);
 pref("browser.search.update", true);
 pref("browser.search.update.log", false);
 pref("browser.search.update.interval", 21600);
--- a/security/manager/boot/src/StaticHPKPins.h
+++ b/security/manager/boot/src/StaticHPKPins.h
@@ -1075,9 +1075,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 = 346;
 
 static const int32_t kUnknownId = -1;
 
-static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1431774962838000);
+static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1432379583107000);
--- a/security/manager/boot/src/nsSTSPreloadList.errors
+++ b/security/manager/boot/src/nsSTSPreloadList.errors
@@ -62,39 +62,42 @@ codereview.chromium.org: did not receive
 console.python.org: did not receive HSTS header
 coursella.com: did not receive HSTS header
 cr.search.yahoo.com: did not receive HSTS header
 crate.io: did not receive HSTS header
 crbug.com: did not receive HSTS header
 crowdcurity.com: did not receive HSTS header
 crowdjuris.com: could not connect to host
 crypto.is: did not receive HSTS header
-csawctf.poly.edu: did not receive HSTS header
+csawctf.poly.edu: could not connect to host
 ct.search.yahoo.com: did not receive HSTS header
 cujanovic.com: did not receive HSTS header
 cyanogenmod.xxx: could not connect to host
 cybershambles.com: could not connect to host
 daylightcompany.com: did not receive HSTS header
 de.search.yahoo.com: did not receive HSTS header
 decibelios.li: did not receive HSTS header
 destinationbijoux.fr: max-age too low: 2678400
+devh.de: did not receive HSTS header
 digitaldaddy.net: could not connect to host
 discovery.lookout.com: did not receive HSTS header
 dk.search.yahoo.com: did not receive HSTS header
 dl.google.com: did not receive HSTS header (error ignored - included regardless)
 do.search.yahoo.com: did not receive HSTS header
 docs.google.com: did not receive HSTS header (error ignored - included regardless)
 domaris.de: did not receive HSTS header
 download.jitsi.org: did not receive HSTS header
 drive.google.com: did not receive HSTS header (error ignored - included regardless)
 dropcam.com: did not receive HSTS header
 dynaloop.net: did not receive HSTS header
 dzlibs.io: could not connect to host
+e-aut.net: [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 134"  data: no]
 ed.gs: did not receive HSTS header
 edmodo.com: did not receive HSTS header
+egit.co: could not connect to host
 email.lookout.com: could not connect to host
 en-maktoob.search.yahoo.com: did not receive HSTS header
 encrypted.google.com: did not receive HSTS header (error ignored - included regardless)
 epoxate.com: did not receive HSTS header
 errors.zenpayroll.com: could not connect to host
 es.search.yahoo.com: did not receive HSTS header
 esec.rs: did not receive HSTS header
 espanol.search.yahoo.com: did not receive HSTS header
@@ -125,17 +128,18 @@ gparent.org: did not receive HSTS header
 gr.search.yahoo.com: did not receive HSTS header
 grandmascookieblog.com: did not receive HSTS header
 greplin.com: could not connect to host
 groups.google.com: did not receive HSTS header (error ignored - included regardless)
 gunnarhafdal.com: could not connect to host
 hachre.de: did not receive HSTS header
 hackerone-user-content.com: could not connect to host
 haste.ch: could not connect to host
-hicoria.com: could not connect to host
+hatoko.net: [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 134"  data: no]
+helpadmin.net: could not connect to host
 history.google.com: did not receive HSTS header (error ignored - included regardless)
 hk.search.yahoo.com: did not receive HSTS header
 hn.search.yahoo.com: did not receive HSTS header
 hoerbuecher-und-hoerspiele.de: did not receive HSTS header
 honeytracks.com: [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 134"  data: no]
 horosho.in: could not connect to host
 hostedtalkgadget.google.com: did not receive HSTS header (error ignored - included regardless)
 howrandom.org: could not connect to host
@@ -150,16 +154,17 @@ inertianetworks.com: did not receive HST
 intercom.io: did not receive HSTS header
 iop.intuit.com: max-age too low: 86400
 irccloud.com: did not receive HSTS header
 it.search.yahoo.com: did not receive HSTS header
 itriskltd.com: did not receive HSTS header
 jonas-keidel.de: could not connect to host
 jonathan.ir: did not receive HSTS header
 jottit.com: could not connect to host
+k-dev.de: could not connect to host
 keymaster.lookout.com: did not receive HSTS header
 kirkforcongress.com: did not receive HSTS header
 kitsta.com: could not connect to host
 kiwiirc.com: max-age too low: 5256000
 klaxn.com: could not connect to host
 klaxn.org: could not connect to host
 koop-bremen.de: [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 134"  data: no]
 kr.search.yahoo.com: did not receive HSTS header
@@ -177,28 +182,33 @@ lt.search.yahoo.com: did not receive HST
 lu.search.yahoo.com: did not receive HSTS header
 lumi.do: [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 134"  data: no]
 luxus-russen.de: could not connect to host
 lv.search.yahoo.com: did not receive HSTS header
 m.gparent.org: could not connect to host
 mail.google.com: did not receive HSTS header (error ignored - included regardless)
 maktoob.search.yahoo.com: did not receive HSTS header
 malaysia.search.yahoo.com: did not receive HSTS header
-man3s.jp: [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 134"  data: no]
 manage.zenpayroll.com: [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 134"  data: no]
 market.android.com: did not receive HSTS header (error ignored - included regardless)
 marshut.net: could not connect to host
+matatall.com: could not connect to host
+mattmccutchen.net: could not connect to host
+mediacru.sh: could not connect to host
 megashur.se: did not receive HSTS header
 megaxchange.com: did not receive HSTS header
+meinebo.it: could not connect to host
 minikneet.nl: did not receive HSTS header
-mirindadomo.ru: did not receive HSTS header
+miniku.net: did not receive HSTS header
+mirindadomo.ru: could not connect to host
 mobilethreat.net: could not connect to host
 mobilethreatnetwork.net: could not connect to host
 mocloud.eu: [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 134"  data: no]
 movelaria.com.br: did not receive HSTS header
+mqas.net: could not connect to host
 mt.search.yahoo.com: did not receive HSTS header
 mu.search.yahoo.com: did not receive HSTS header
 mw.search.yahoo.com: did not receive HSTS header
 mx.search.yahoo.com: did not receive HSTS header
 my.alfresco.com: did not receive HSTS header
 mydigipass.com: did not receive HSTS header
 mykolab.com: did not receive HSTS header
 mykreuzfahrt.de: did not receive HSTS header
@@ -209,17 +219,16 @@ nexth.de: could not connect to host
 nexth.net: could not connect to host
 nexth.us: could not connect to host
 ni.search.yahoo.com: did not receive HSTS header
 nl.search.yahoo.com: did not receive HSTS header
 no.search.yahoo.com: did not receive HSTS header
 noexpect.org: could not connect to host
 np.search.yahoo.com: did not receive HSTS header
 npw.net: [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 134"  data: no]
-nutsandboltsmedia.com: [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 134"  data: no]
 nz.search.yahoo.com: did not receive HSTS header
 openshift.redhat.com: did not receive HSTS header
 otakurepublic.com: did not receive HSTS header
 ottospora.nl: could not connect to host
 pa.search.yahoo.com: did not receive HSTS header
 passwordbox.com: did not receive HSTS header
 passwords.google.com: did not receive HSTS header (error ignored - included regardless)
 pe.search.yahoo.com: did not receive HSTS header
@@ -246,17 +255,16 @@ riftnetwork.net: did not receive HSTS he
 riseup.net: did not receive HSTS header
 rme.li: did not receive HSTS header
 ro.search.yahoo.com: did not receive HSTS header
 robteix.com: did not receive HSTS header
 roddis.net: did not receive HSTS header
 ru.search.yahoo.com: did not receive HSTS header
 rw.search.yahoo.com: did not receive HSTS header
 sah3.net: could not connect to host
-samba.org: could not connect to host
 saturngames.co.uk: could not connect to host
 script.google.com: did not receive HSTS header (error ignored - included regardless)
 se.search.yahoo.com: did not receive HSTS header
 search.yahoo.com: did not receive HSTS header
 security.google.com: did not receive HSTS header (error ignored - included regardless)
 semenkovich.com: did not receive HSTS header
 seomobo.com: did not receive HSTS header
 seowarp.net: max-age too low: 1576800
@@ -269,22 +277,20 @@ silentcircle.org: could not connect to h
 simon.butcher.name: max-age too low: 2629743
 simplyfixit.co.uk: [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 134"  data: no]
 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
 spdysync.com: did not receive HSTS header
-spongepowered.org: did not receive HSTS header
 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)
 ssl.panoramio.com: did not receive HSTS header
-staticanime.net: did not receive HSTS header
 stocktrade.de: could not connect to host
 suite73.org: could not connect to host
 sunshinepress.org: could not connect to host
 surfeasy.com: did not receive HSTS header
 suzukikenichi.com: did not receive HSTS header
 sv.search.yahoo.com: did not receive HSTS header
 t.facebook.com: did not receive HSTS header
 tablet.facebook.com: did not receive HSTS header
@@ -323,25 +329,27 @@ wohnungsbau-ludwigsburg.de: did not rece
 www.apollo-auto.com: [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 134"  data: no]
 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 134"  data: no]
 www.cueup.com: could not connect to host
 www.developer.mydigipass.com: could not connect to host
 www.elanex.biz: did not receive HSTS header
 www.gmail.com: did not receive HSTS header (error ignored - included regardless)
 www.googlemail.com: did not receive HSTS header (error ignored - included regardless)
 www.greplin.com: could not connect to host
+www.heliosnet.com: could not connect to host
 www.jitsi.org: did not receive HSTS header
 www.logentries.com: did not receive HSTS header
 www.moneybookers.com: did not receive HSTS header
 www.neonisi.com: could not connect to host
 www.paycheckrecords.com: max-age too low: 86400
 www.rme.li: did not receive HSTS header
 www.sandbox.mydigipass.com: could not connect to host
 www.surfeasy.com: did not receive HSTS header
 xa.search.yahoo.com: did not receive HSTS header
+xplore-dna.net: [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 134"  data: no]
 xtream-hosting.com: could not connect to host
 xtream-hosting.de: could not connect to host
 xtream-hosting.eu: could not connect to host
 xtreamhosting.eu: could not connect to host
 za.search.yahoo.com: did not receive HSTS header
 zh.search.yahoo.com: did not receive HSTS header
 zoo24.de: could not connect to host
 zotero.org: did not receive HSTS header
--- 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(1434194152739000);
+const PRTime gPreloadListExpirationTime = INT64_C(1434798773028000);
 
 class nsSTSPreload
 {
   public:
     const char *mHost;
     const bool mIncludeSubdomains;
 };
 
@@ -26,28 +26,31 @@ static const nsSTSPreload kSTSPreloadLis
   { "1a-vermessung.at", true },
   { "1a-werkstattgeraete.de", true },
   { "2048game.co.uk", true },
   { "302.nyc", true },
   { "accounts.firefox.com", true },
   { "accounts.google.com", true },
   { "aclu.org", false },
   { "activiti.alfresco.com", false },
+  { "acus.gov", true },
   { "adamkostecki.de", true },
   { "addvocate.com", true },
   { "adlershop.ch", true },
   { "admin.google.com", true },
   { "adorai.tk", true },
   { "adsfund.org", true },
+  { "agrimap.com", true },
   { "ahoyconference.com", true },
   { "ahwatukeefoothillsmontessori.com", true },
   { "aids.gov", true },
   { "aie.de", true },
   { "airlea.com", true },
   { "aiticon.com", true },
+  { "ajouin.com", true },
   { "aladdinschools.appspot.com", false },
   { "alainwolf.net", true },
   { "alecvannoten.be", true },
   { "alexsexton.com", true },
   { "alexyang.me", true },
   { "alpha.irccloud.com", false },
   { "anadoluefessporkulubu.org", true },
   { "andreasbreitenlohner.de", true },
@@ -80,16 +83,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "archlinux.de", true },
   { "arendburgers.nl", true },
   { "arguggi.co.uk", true },
   { "arivo.com.br", true },
   { "arlen.io", true },
   { "atavio.at", true },
   { "atavio.ch", true },
   { "atavio.de", true },
+  { "atishchenko.com", true },
   { "atlantischild.hu", true },
   { "atlassian.net", true },
   { "atte.fi", true },
   { "auf-feindgebiet.de", true },
   { "authentication.io", true },
   { "azabani.com", true },
   { "balcan-underground.net", true },
   { "baldwinkoo.com", true },
@@ -106,23 +110,25 @@ static const nsSTSPreload kSTSPreloadLis
   { "beamitapp.com", true },
   { "beastowner.com", true },
   { "beastowner.li", true },
   { "bedeta.de", true },
   { "bedreid.dk", true },
   { "beneathvt.com", true },
   { "benjamin.pe", true },
   { "benjamins.com", true },
+  { "bentertain.de", true },
   { "best-wedding-quotes.com", true },
   { "bfelob.gov", true },
   { "bgneuesheim.de", true },
   { "bhatia.at", true },
   { "biathloncup.ru", true },
   { "big-andy.co.uk", true },
   { "bigbrownpromotions.com.au", true },
+  { "bit.voyage", true },
   { "bitbucket.org", false },
   { "bitfactory.ws", true },
   { "bitmex.com", true },
   { "bitmon.net", true },
   { "bitpod.de", true },
   { "bjornjohansen.no", true },
   { "bl4ckb0x.com", true },
   { "bl4ckb0x.de", true },
@@ -152,16 +158,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "bugzil.la", true },
   { "bugzilla.mozilla.org", true },
   { "bulktrade.de", true },
   { "business.facebook.com", false },
   { "business.lookout.com", false },
   { "buttercoin.com", true },
   { "buzzconcert.com", true },
   { "bytepark.de", false },
+  { "bzv-fr.eu", true },
   { "ca.gparent.org", false },
   { "cackette.com", true },
   { "call.me", true },
   { "calomel.org", true },
   { "camolist.com", true },
   { "cao.gov", true },
   { "caremad.io", true },
   { "carezone.com", false },
@@ -195,96 +202,104 @@ static const nsSTSPreload kSTSPreloadLis
   { "cloudcert.org", true },
   { "cloudns.com.au", true },
   { "cloudsecurityalliance.org", true },
   { "cloudstoragemaus.com", true },
   { "cloudup.com", true },
   { "code-poets.co.uk", true },
   { "code.facebook.com", false },
   { "code.google.com", true },
+  { "codepoints.net", true },
   { "codepref.com", true },
+  { "codepx.com", true },
   { "codereview.appspot.com", false },
   { "codereview.chromium.org", true },
   { "coffeeetc.co.uk", true },
   { "coinapult.com", true },
   { "comdurav.com", true },
   { "comssa.org.au", true },
-  { "config.schokokeks.org", true },
+  { "config.schokokeks.org", false },
   { "conformal.com", true },
   { "conrad-kostecki.de", true },
   { "controlcenter.gigahost.dk", true },
   { "cor-ser.es", true },
   { "cordial-restaurant.com", true },
   { "cotonea.de", true },
   { "crm.onlime.ch", false },
   { "crowdjuris.com", true },
   { "crypto.cat", false },
   { "cryptobin.org", true },
   { "cryptopartyatx.org", true },
   { "cs50.harvard.edu", true },
+  { "cs50.net", true },
   { "cspbuilder.info", true },
   { "csuw.net", true },
   { "cube.de", true },
   { "cupcake.io", true },
   { "cupcake.is", true },
   { "curiosity-driven.org", true },
   { "curlybracket.co.uk", true },
   { "cyanogenmod.xxx", true },
   { "cybershambles.com", true },
   { "cybozu.com", true },
   { "cyon.ch", true },
   { "cyphertite.com", true },
+  { "cyprus-company-service.com", true },
   { "czbix.com", true },
   { "daphne.informatik.uni-freiburg.de", true },
   { "darchoods.net", false },
+  { "darkpony.ru", true },
+  { "darom.jp", true },
   { "data-abundance.com", true },
   { "data.qld.gov.au", false },
   { "datenkeks.de", true },
   { "daveoc64.co.uk", true },
   { "davidlyness.com", true },
+  { "davidnoren.com", true },
   { "dccode.gov", true },
   { "deadbeef.ninja", true },
   { "dealcruiser.nl", true },
   { "debtkit.co.uk", true },
   { "dedimax.de", true },
   { "dee.pe", true },
   { "denh.am", true },
   { "depechemode-live.com", true },
   { "derevtsov.com", false },
   { "derhil.de", true },
   { "detectify.com", false },
   { "developer.mydigipass.com", false },
   { "developers.facebook.com", false },
-  { "devh.de", true },
   { "diamante.ro", true },
   { "die-besten-weisheiten.de", true },
   { "dillonkorman.com", true },
   { "dinamoelektrik.com", true },
   { "dist.torproject.org", false },
   { "dixmag.com", true },
   { "dl.google.com", true },
   { "dlc.viasinc.com", true },
   { "dm.lookout.com", false },
   { "dm.mylookout.com", false },
   { "doc.python.org", true },
   { "docs.google.com", true },
   { "docs.python.org", true },
   { "domains.google.com", true },
   { "donmez.ws", false },
+  { "donotcall.gov", true },
   { "dreadbyte.com", true },
   { "drive.google.com", true },
   { "dropbox.com", true },
   { "dylanscott.com.au", true },
   { "dzlibs.io", true },
   { "e-kontakti.fi", true },
   { "earmarks.gov", true },
   { "easysimplecrm.com", false },
   { "ebanking.indovinabank.com.vn", false },
   { "ecdn.cz", true },
   { "ecfs.link", true },
+  { "ecg.fr", false },
   { "ecosystem.atlassian.net", true },
   { "edit.yahoo.com", false },
   { "eduroam.no", true },
   { "edyou.eu", true },
   { "ef.gy", true },
   { "eff.org", true },
   { "egfl.org.uk", true },
   { "egit.co", true },
@@ -301,41 +316,46 @@ static const nsSTSPreload kSTSPreloadLis
   { "errors.zenpayroll.com", false },
   { "espra.com", true },
   { "ethack.org", true },
   { "ethitter.com", true },
   { "eurotramp.com", true },
   { "everhome.de", true },
   { "evstatus.com", true },
   { "exiahost.com", true },
+  { "exon.io", true },
   { "expatads.com", true },
   { "explodie.org", true },
+  { "extendwings.com", true },
   { "f-droid.org", true },
   { "fabhub.io", true },
   { "fabianfischer.de", true },
   { "facebook.com", false },
   { "factor.cc", false },
   { "fairbill.com", true },
   { "fakturoid.cz", true },
   { "fant.dk", true },
   { "faq.lookout.com", false },
   { "fassadenverkleidung24.de", true },
   { "fastcomcorp.net", true },
+  { "federalregister.gov", true },
+  { "fedorahosted.org", true },
   { "fedorapeople.org", true },
   { "feedbin.com", false },
   { "ferienhaus-polchow-ruegen.de", false },
   { "fewo-thueringer-wald.de", true },
   { "fiken.no", true },
   { "filedir.com", false },
   { "filip-prochazka.com", true },
   { "finn.io", true },
   { "firebaseio-demo.com", true },
   { "firebaseio.com", true },
   { "firefart.at", true },
   { "firemail.io", true },
+  { "firma-offshore.com", true },
   { "firstlook.org", true },
   { "fischer-its.com", true },
   { "fj.simple.com", false },
   { "flamer-scene.com", true },
   { "fleximus.org", false },
   { "floobits.com", true },
   { "florianmitrea.uk", true },
   { "flynn.io", true },
@@ -343,32 +363,37 @@ static const nsSTSPreload kSTSPreloadLis
   { "forewordreviews.com", true },
   { "forodeespanol.com", true },
   { "forum.linode.com", false },
   { "forum.quantifiedself.com", true },
   { "fralef.me", false },
   { "frederik-braun.com", true },
   { "freenetproject.org", true },
   { "freeshell.de", true },
+  { "freesounding.com", true },
+  { "freesounding.ru", true },
   { "freethought.org.au", true },
   { "fronteers.nl", true },
+  { "ftccomplaintassistant.gov", true },
   { "fundingempire.com", true },
   { "futos.de", true },
   { "gambitnash.co.uk", true },
   { "gamercredo.com", true },
   { "garron.net", true },
   { "gavick.com", true },
   { "gaytorrent.ru", true },
   { "gemeinfreie-lieder.de", true },
   { "gerardozamudio.mx", true },
   { "gernert-server.de", true },
   { "get.zenpayroll.com", false },
   { "getable.com", true },
   { "getcloak.com", false },
   { "getdigitized.net", true },
+  { "getfedora.org", true },
+  { "getfittedstore.com", true },
   { "getssl.uz", true },
   { "giacomopelagatti.it", true },
   { "github.com", true },
   { "gizzo.sk", true },
   { "glass.google.com", true },
   { "globuli-info.de", true },
   { "glossopnorthendafc.co.uk", true },
   { "gmail.com", false },
@@ -396,16 +421,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "hackerone.com", true },
   { "hansvaneijsden.com", true },
   { "happylifestyle.com", true },
   { "harvestapp.com", true },
   { "hasilocke.de", true },
   { "haste.ch", true },
   { "haufschild.de", true },
   { "hausverbrauch.de", true },
+  { "hda.me", true },
   { "heartlandrentals.com", true },
   { "heha.co", false },
   { "heid.ws", true },
   { "heijblok.com", true },
   { "helichat.de", true },
   { "help.simpletax.ca", false },
   { "helpium.de", true },
   { "hemlockhillscabinrentals.com", true },
@@ -424,16 +450,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "hostinginnederland.nl", true },
   { "hostix.de", true },
   { "howrandom.org", true },
   { "howsmyssl.com", true },
   { "howsmytls.com", true },
   { "hpac-portal.com", true },
   { "hrackydomino.cz", true },
   { "hsmr.cc", true },
+  { "hsr.gov", true },
   { "hstsfail.appspot.com", true },
   { "html5.org", true },
   { "httpswatch.com", true },
   { "i5y.co.uk", true },
   { "iamcarrico.com", true },
   { "ian.sh", true },
   { "iban.is", true },
   { "id-co.in", true },
@@ -444,23 +471,25 @@ static const nsSTSPreload kSTSPreloadLis
   { "ihrlotto.de", true },
   { "ikkatsu-satei.jp", true },
   { "ilikerainbows.co.uk", true },
   { "imaginary.ca", true },
   { "imouto.my", false },
   { "in.xero.com", false },
   { "inb4.us", true },
   { "inbox.google.com", true },
+  { "iniiter.com", true },
   { "inkbunny.net", true },
   { "inleaked.com", true },
   { "innophate-security.com", true },
   { "innophate-security.nl", true },
   { "insouciant.org", true },
   { "instasex.ch", true },
   { "interserved.com", true },
+  { "ionas-law.ro", true },
   { "iranianlawschool.com", true },
   { "iridiumbrowser.de", true },
   { "irische-segenswuensche.info", true },
   { "ironfistdesign.com", true },
   { "isitchristmas.com", true },
   { "it-schwerin.de", true },
   { "itdashboard.gov", true },
   { "itsamurai.ru", true },
@@ -489,16 +518,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "k-dev.de", true },
   { "kaheim.de", true },
   { "kardize24.pl", true },
   { "karmaspa.se", true },
   { "kartonmodellbau.org", true },
   { "kdex.de", true },
   { "kdyby.org", true },
   { "keeleysam.com", true },
+  { "keepa.com", false },
   { "keepclean.me", true },
   { "keeperapp.com", true },
   { "keepersecurity.com", true },
   { "kernel-error.de", true },
   { "kevincox.ca", true },
   { "keycdn.com", true },
   { "keyerror.com", true },
   { "khanovaskola.cz", true },
@@ -512,50 +542,54 @@ static const nsSTSPreload kSTSPreloadLis
   { "kirkforsenate.com", true },
   { "kitsta.com", true },
   { "klarmobil-empfehlen.de", true },
   { "klatschreime.de", true },
   { "klausbrinch.dk", true },
   { "klaxn.com", true },
   { "kleidertauschpartys.de", true },
   { "klingeletest.de", true },
+  { "knip.ch", true },
   { "knowledgehook.com", true },
   { "koenvdheuvel.me", true },
   { "komandakovalchuk.com", true },
   { "konklone.com", true },
   { "koop-bremen.de", true },
   { "koordinate.net", true },
   { "kosho.org", true },
   { "kpebetka.net", true },
   { "kraken.io", true },
   { "kuppingercole.com", true },
   { "kupschke.net", true },
   { "kura.io", true },
+  { "laf.in.net", true },
   { "lagerauftrag.info", true },
   { "lancejames.com", true },
   { "lasst-uns-beten.de", true },
   { "lastpass.com", false },
   { "launchkey.com", true },
   { "lavalite.de", true },
   { "lb-toner.de", true },
   { "leadbook.ru", true },
   { "ledgerscope.net", false },
   { "leibniz-remscheid.de", true },
+  { "leifdreizler.com", true },
   { "leonardcamacho.me", true },
   { "leonklingele.de", true },
   { "les-corsaires.net", true },
   { "libraryfreedomproject.org", true },
   { "liebel.org", true },
   { "lighting-centres.co.uk", true },
   { "lilpwny.com", true },
   { "limpid.nl", true },
   { "lingolia.com", true },
   { "linode.com", false },
   { "linux-admin-california.com", true },
   { "linx.net", true },
+  { "livej.am", true },
   { "ljs.io", true },
   { "lobste.rs", true },
   { "lockify.com", true },
   { "lodash.com", true },
   { "loenshotel.de", true },
   { "loftboard.eu", true },
   { "logentries.com", false },
   { "login.corp.google.com", true },
@@ -568,88 +602,98 @@ static const nsSTSPreload kSTSPreloadLis
   { "lolicore.ch", true },
   { "lookout.com", false },
   { "ludwig.im", true },
   { "luelistan.net", true },
   { "lukonet.com", true },
   { "lumi.do", false },
   { "luneta.nearbuysystems.com", false },
   { "m.facebook.com", false },
+  { "m0wef.uk", true },
   { "mach-politik.ch", true },
   { "madars.org", true },
   { "maff.scot", false },
   { "magneticanvil.com", true },
+  { "mahamed91.pw", true },
   { "mail.de", true },
   { "mail.google.com", true },
   { "mail.yahoo.com", false },
   { "mailbox.org", true },
   { "makeitdynamic.com", true },
   { "makeyourlaws.org", true },
   { "malnex.de", true },
   { "man3s.jp", true },
   { "manage.zenpayroll.com", false },
   { "manageprojects.com", true },
   { "manager.linode.com", false },
   { "mandala-ausmalbilder.de", true },
   { "manicode.com", true },
   { "market.android.com", true },
   { "markusueberallassetmanagement.de", true },
   { "marshut.net", true },
+  { "massivum.de", true },
   { "matatall.com", true },
   { "mathiasbynens.be", true },
   { "matteomarescotti.it", true },
   { "mattmccutchen.net", true },
   { "max.gov", true },
   { "mbasic.facebook.com", false },
   { "mbp.banking.co.at", false },
   { "md5file.com", true },
   { "mdfnet.se", true },
   { "meamod.com", true },
   { "mediacru.sh", true },
   { "medium.com", true },
   { "meetfinch.com", true },
   { "mega.co.nz", false },
+  { "megaplan.cz", true },
+  { "megaplan.ru", true },
   { "meinebo.it", true },
   { "members.mayfirst.org", false },
   { "members.nearlyfreespeech.net", false },
   { "miasarafina.de", true },
   { "michalspacek.cz", true },
   { "mig5.net", true },
   { "mike-bland.com", true },
+  { "miketabor.com", true },
   { "mikewest.org", true },
   { "miku.hatsune.my", false },
   { "mimeit.de", true },
   { "minecraftvoter.com", true },
+  { "mineover.es", true },
   { "minez-nightswatch.com", true },
   { "minikneet.com", true },
-  { "miniku.net", true },
   { "minnesotadata.com", true },
   { "mirrorx.com", true },
   { "miskatonic.org", true },
   { "mkcert.org", true },
   { "mkw.st", true },
   { "mnsure.org", true },
   { "mobilcom-debitel-empfehlen.de", true },
   { "mobile.usaa.com", false },
+  { "mokote.com", true },
   { "mondwandler.de", true },
   { "morethanadream.lv", true },
   { "moriz.de", true },
   { "mothereff.in", true },
   { "mountainmusicpromotions.com", true },
   { "mountainroseherbs.com", true },
   { "movlib.org", true },
   { "mpreserver.com", true },
   { "mqas.net", true },
+  { "mr-hosting.com", true },
+  { "msa-aesch.ch", true },
   { "msc-seereisen.net", true },
   { "mths.be", true },
   { "mtouch.facebook.com", false },
   { "mudcrab.us", true },
   { "mujadin.se", true },
   { "munich-rage.de", true },
   { "musicgamegalaxy.de", true },
+  { "mutamatic.com", true },
   { "mutantmonkey.in", true },
   { "mutantmonkey.info", true },
   { "mutantmonkey.sexy", true },
   { "mvno.io", true },
   { "mwe.st", true },
   { "my.onlime.ch", false },
   { "my.usa.gov", true },
   { "my.xero.com", false },
@@ -690,25 +734,29 @@ static const nsSTSPreload kSTSPreloadLis
   { "nu3.com", true },
   { "nu3.de", true },
   { "nu3.dk", true },
   { "nu3.fi", true },
   { "nu3.fr", true },
   { "nu3.no", true },
   { "nu3.se", true },
   { "null-sec.ru", true },
+  { "nutsandboltsmedia.com", false },
   { "nwgh.org", true },
+  { "nymphetomania.net", true },
   { "oakslighting.co.uk", true },
+  { "offshore-firma.org", true },
   { "okmx.de", true },
   { "omitech.co.uk", true },
   { "onedot.nl", true },
   { "onedrive.com", true },
   { "onedrive.live.com", false },
   { "onsitemassageco.com", true },
   { "ooonja.de", true },
+  { "openacademies.com", true },
   { "opendesk.cc", true },
   { "oplop.appspot.com", true },
   { "opsmate.com", false },
   { "optimus.io", true },
   { "orbograph-hrcm.com", true },
   { "oscarvk.ch", true },
   { "osterkraenzchen.de", true },
   { "otakuworld.de", true },
@@ -746,70 +794,80 @@ static const nsSTSPreload kSTSPreloadLis
   { "pdf.yt", true },
   { "peercraft.com", true },
   { "pentesterlab.com", true },
   { "perfectionis.me", true },
   { "personaldatabasen.no", true },
   { "pestici.de", true },
   { "petrolplus.ru", true },
   { "pharmaboard.de", true },
+  { "phoenix.dj", true },
   { "phoenixlogan.com", true },
   { "phryanjr.com", false },
   { "phurl.de", true },
   { "picksin.club", true },
   { "pieperhome.de", true },
   { "pierre-schmitz.com", true },
   { "pixel.facebook.com", false },
   { "pixi.me", true },
   { "play.google.com", true },
   { "plothost.com", true },
   { "plus.google.com", false },
   { "plus.sandbox.google.com", false },
+  { "pmg-offshore-company.com", true },
+  { "pmg-purchase.com", true },
+  { "pmg-purchase.net", true },
   { "polymathematician.com", true },
+  { "polypho.nyc", true },
   { "portal.tirol.gv.at", true },
   { "posteo.de", false },
   { "powerplannerapp.com", true },
   { "prakharprasad.com", true },
   { "prefontaine.name", true },
   { "privategiant.com", true },
   { "profiles.google.com", true },
   { "progressiveplanning.com", true },
   { "projektzentrisch.de", true },
   { "propagandism.org", true },
   { "prowhisky.de", true },
   { "proximato.com", true },
   { "puac.de", true },
   { "pubkey.is", true },
   { "publications.qld.gov.au", false },
+  { "puiterwijk.org", true },
   { "pult.co", false },
   { "pwd.ovh", true },
   { "pypa.io", true },
   { "pypi.python.org", true },
   { "python.org", false },
   { "qetesh.de", true },
   { "quuz.org", true },
   { "r3s1stanc3.me", true },
   { "rad-route.de", true },
   { "rafaelcz.de", true },
   { "ragingserenity.com", true },
   { "raiseyourflag.com", true },
   { "rasing.me", true },
   { "raspass.me", true },
   { "ravchat.com", true },
+  { "redletter.link", true },
   { "redports.org", true },
   { "redteam-pentesting.de", true },
   { "reedloden.com", true },
+  { "reg.ru", true },
   { "reishunger.de", true },
+  { "release-monitoring.org", true },
   { "reliable-mail.de", true },
   { "research.facebook.com", false },
   { "reserve-online.net", true },
   { "residentsinsurance.co.uk", true },
   { "reviews.anime.my", true },
   { "riccy.org", true },
   { "riesenmagnete.de", true },
+  { "rika.me", true },
   { "rippleunion.com", true },
   { "rlalique.com", true },
   { "roland.io", true },
   { "romab.com", true },
   { "romans-place.me.uk", true },
   { "romulusapp.com", true },
   { "room-checkin24.de", true },
   { "rosenkeller.org", true },
@@ -829,22 +887,23 @@ static const nsSTSPreload kSTSPreloadLis
   { "samuelkeeley.com", true },
   { "sanatfilan.com", false },
   { "sandbox.mydigipass.com", false },
   { "save.gov", true },
   { "saveaward.gov", true },
   { "savetheinternet.eu", true },
   { "savvytime.com", true },
   { "schachburg.de", true },
-  { "schokokeks.org", true },
+  { "schokokeks.org", false },
   { "schreiber-netzwerk.eu", true },
   { "schwarzer.it", true },
   { "sciencex.com", true },
   { "scotthelme.co.uk", true },
   { "scrambl.is", true },
+  { "scrambler.in", false },
   { "scribe.systems", true },
   { "script.google.com", true },
   { "sdsl-speedtest.de", true },
   { "secure.facebook.com", false },
   { "securesuisse.ch", true },
   { "securify.nl", true },
   { "security-carpet.com", true },
   { "security.google.com", true },
@@ -869,16 +928,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "simple.com", false },
   { "simpletax.ca", false },
   { "simplia.cz", true },
   { "simplystudio.com", true },
   { "siraweb.org", true },
   { "siriad.com", true },
   { "sites.google.com", true },
   { "sitesten.com", true },
+  { "sjoorm.com", true },
   { "skhosting.eu", true },
   { "skogsbruket.fi", true },
   { "skogskultur.fi", true },
   { "skydrive.live.com", false },
   { "slack.com", true },
   { "slattery.co", true },
   { "slevomat.cz", true },
   { "slidebatch.com", true },
@@ -890,30 +950,32 @@ static const nsSTSPreload kSTSPreloadLis
   { "sorz.org", true },
   { "sour.is", true },
   { "southside-crew.com", true },
   { "souvik.me", true },
   { "spartantheatre.org", true },
   { "spawn.cz", true },
   { "spencerbaer.com", true },
   { "spideroak.com", true },
+  { "spongepowered.org", true },
   { "spreadsheets.google.com", true },
   { "spreed.me", true },
   { "sprueche-zum-valentinstag.de", true },
   { "sprueche-zur-geburt.info", true },
   { "sprueche-zur-hochzeit.de", true },
   { "sprueche-zur-konfirmation.de", true },
   { "squareup.com", false },
   { "srevilak.net", true },
   { "sro.center", true },
   { "ssl.google-analytics.com", true },
   { "sslmate.com", true },
   { "stage.wepay.com", false },
   { "standardssuck.org", true },
   { "static.wepay.com", false },
+  { "staticanime.net", false },
   { "stationary-traveller.eu", true },
   { "steventress.com", true },
   { "stocktrade.de", false },
   { "strasweb.fr", false },
   { "stretchmyan.us", true },
   { "stripe.com", true },
   { "strongest-privacy.com", true },
   { "studienportal.eu", true },
@@ -921,17 +983,19 @@ static const nsSTSPreload kSTSPreloadLis
   { "stulda.cz", true },
   { "subrosa.io", true },
   { "suite73.org", true },
   { "sunjaydhama.com", true },
   { "supplies24.at", true },
   { "supplies24.es", true },
   { "support.mayfirst.org", false },
   { "surkatty.org", true },
+  { "survivalmonkey.com", true },
   { "swehack.org", false },
+  { "sychov.pro", true },
   { "sylaps.com", true },
   { "sysctl.se", true },
   { "sysdb.io", true },
   { "syss.de", true },
   { "tadigitalstore.com", true },
   { "tageau.com", true },
   { "talk.google.com", true },
   { "talkgadget.google.com", true },
@@ -940,19 +1004,21 @@ static const nsSTSPreload kSTSPreloadLis
   { "tas2580.net", true },
   { "tatort-fanpage.de", true },
   { "tauchkater.de", true },
   { "tbspace.de", true },
   { "techhipster.net", true },
   { "tegelsensanitaironline.nl", true },
   { "tekshrek.com", true },
   { "tent.io", true },
+  { "terrty.net", true },
   { "testsuite.org", true },
   { "texte-zur-taufe.de", true },
   { "the-sky-of-valkyries.com", true },
+  { "thebimhub.com", true },
   { "thecustomizewindows.com", true },
   { "thepaymentscompany.com", true },
   { "therapynotes.com", false },
   { "theshadestore.com", true },
   { "thetomharling.com", true },
   { "thomastimepieces.com.au", true },
   { "thorncreek.net", false },
   { "thusoy.com", true },
@@ -961,16 +1027,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "tid.jp", true },
   { "timtaubert.de", true },
   { "tinfoilsecurity.com", false },
   { "tinte24.de", true },
   { "tintenfix.net", true },
   { "tipps-fuer-den-haushalt.de", true },
   { "tittelbach.at", true },
   { "tls.li", true },
+  { "tmtopup.com", true },
   { "tno.io", true },
   { "tobias-kluge.de", true },
   { "todesschaf.org", true },
   { "tollmanz.com", true },
   { "tomfisher.eu", true },
   { "tomvote.com", true },
   { "toner24.at", true },
   { "toner24.co.uk", true },
@@ -1001,16 +1068,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "tribut.de", true },
   { "tucuxi.org", true },
   { "tunebitfm.de", true },
   { "twentymilliseconds.com", true },
   { "twisto.cz", true },
   { "twitter.com", false },
   { "twitteroauth.com", true },
   { "typingrevolution.com", true },
+  { "uae-company-service.com", true },
   { "ub3rk1tten.com", true },
   { "ubertt.org", true },
   { "ukdefencejournal.org.uk", true },
   { "ukhas.net", true },
   { "ukrainians.ch", true },
   { "unison.com", true },
   { "unterfrankenclan.de", true },
   { "uonstaffhub.com", true },
@@ -1039,17 +1107,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "webcollect.org.uk", true },
   { "webfilings-eu-mirror.appspot.com", true },
   { "webfilings-eu.appspot.com", true },
   { "webfilings-mirror-hrd.appspot.com", true },
   { "webfilings.appspot.com", true },
   { "weblogzwolle.nl", true },
   { "webmail.gigahost.dk", false },
   { "webmail.onlime.ch", false },
-  { "webmail.schokokeks.org", true },
+  { "webmail.schokokeks.org", false },
   { "websenat.de", true },
   { "webtalis.nl", true },
   { "webtiles.co.uk", true },
   { "webtrh.cz", true },
   { "weggeweest.nl", true },
   { "welches-kinderfahrrad.de", true },
   { "wepay.com", false },
   { "wepay.in.th", true },
@@ -1061,22 +1129,25 @@ static const nsSTSPreload kSTSPreloadLis
   { "wf-pentest.appspot.com", true },
   { "wf-staging-hr.appspot.com", true },
   { "wf-training-hrd.appspot.com", true },
   { "wf-training-master.appspot.com", true },
   { "wf-trial-hrd.appspot.com", true },
   { "whatwg.org", true },
   { "whd-guide.de", true },
   { "when-release.ru", true },
+  { "wherephoto.com", true },
   { "whocalld.com", true },
   { "wieninternational.at", true },
   { "wiki.python.org", true },
   { "wildbee.org", true },
   { "willnorris.com", true },
+  { "wills.co.tt", true },
   { "winhistory-forum.net", true },
+  { "wondershift.biz", true },
   { "wootton95.com", true },
   { "wpletter.de", true },
   { "writeapp.me", false },
   { "wubthecaptain.eu", true },
   { "wunderlist.com", true },
   { "wundi.net", true },
   { "www.aclu.org", false },
   { "www.airbnb.com", true },
@@ -1109,33 +1180,38 @@ static const nsSTSPreload kSTSPreloadLis
   { "www.makeyourlaws.org", true },
   { "www.mydigipass.com", false },
   { "www.mylookout.com", false },
   { "www.noisebridge.net", false },
   { "www.opsmate.com", true },
   { "www.paypal.com", false },
   { "www.python.org", true },
   { "www.roddis.net", true },
-  { "www.schokokeks.org", true },
+  { "www.schokokeks.org", false },
   { "www.simbolo.co.uk", false },
   { "www.simple.com", false },
   { "www.therapynotes.com", false },
   { "www.tinfoilsecurity.com", false },
   { "www.torproject.org", false },
   { "www.twitter.com", false },
   { "www.usaa.com", false },
   { "www.viasinc.com", true },
   { "www.wepay.com", false },
   { "www.zenpayroll.com", false },
+  { "wzrd.in", true },
   { "xbrlsuccess.appspot.com", true },
   { "xn--maraa-rta.org", true },
   { "xps2pdf.co.uk", true },
+  { "xuntier.ch", true },
   { "y-o-w.com", true },
   { "yahvehyireh.com", true },
+  { "yanovich.net", true },
+  { "yaporn.tv", false },
   { "yetii.net", true },
+  { "yorcom.nl", true },
   { "yoursecondphone.co", true },
   { "ypart.eu", true },
   { "z.ai", true },
   { "zenpayroll.com", false },
   { "zentralwolke.de", true },
   { "zeplin.io", false },
   { "zeropush.com", true },
   { "zhovner.com", false },
--- a/toolkit/components/reader/ReaderMode.jsm
+++ b/toolkit/components/reader/ReaderMode.jsm
@@ -12,17 +12,17 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 Cu.importGlobalProperties(["XMLHttpRequest"]);
 
 XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", "resource://services-common/utils.js");
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
 
-let ReaderMode = {
+this.ReaderMode = {
   // Version of the cache schema.
   CACHE_VERSION: 1,
 
   DEBUG: 0,
 
   // Don't try to parse the page if it has too many elements (for memory and
   // performance reasons)
   MAX_ELEMS_TO_PARSE: 3000,
--- a/toolkit/components/viewconfig/content/config.js
+++ b/toolkit/components/viewconfig/content/config.js
@@ -90,17 +90,17 @@ var view = {
         index = gPrefView.length - index - 1;
     }
     else {
       var pref = null;
       if (index >= 0)
         pref = gPrefView[index];
 
       var old = document.getElementById(gSortedColumn);
-      old.setAttribute("sortDirection", "");
+      old.removeAttribute("sortDirection");
       gPrefArray.sort(gSortFunction = gSortFunctions[col.id]);
       if (gPrefView != gPrefArray)
         gPrefView.sort(gSortFunction);
       gSortedColumn = col.id;
       if (pref)
         index = getViewIndexOfPref(pref);
     }
     col.element.setAttribute("sortDirection", gSortDirection > 0 ? "ascending" : "descending");
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/AddonWatcher.jsm
@@ -0,0 +1,83 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["AddonWatcher"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+                                  "resource://gre/modules/Preferences.jsm");
+
+let AddonWatcher = {
+  _lastAddonTime: {},
+  _timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
+  _callback: null,
+  _interval: 1500,
+  _ignoreList: null,
+  init: function(callback) {
+    if (!callback) {
+      return;
+    }
+
+    if (this._callback) {
+      return;
+    }
+
+    this._callback = callback;
+    try {
+      this._ignoreList = new Set(JSON.parse(Preferences.get("browser.addon-watch.ignore", null)));
+    } catch (ex) {
+      // probably some malformed JSON, ignore and carry on
+      this._ignoreList = new Set();
+    }
+    this._interval = Preferences.get("browser.addon-watch.interval", 15000);
+    this._timer.initWithCallback(this._checkAddons.bind(this), this._interval, Ci.nsITimer.TYPE_REPEATING_SLACK);
+  },
+  _checkAddons: function() {
+    let compartmentInfo = Cc["@mozilla.org/compartment-info;1"]
+      .getService(Ci.nsICompartmentInfo);
+    let compartments = compartmentInfo.getCompartments();
+    let count = compartments.length;
+    let addons = {};
+    for (let i = 0; i < count; i++) {
+      let compartment = compartments.queryElementAt(i, Ci.nsICompartment);
+      if (compartment.addonId) {
+        if (addons[compartment.addonId]) {
+          addons[compartment.addonId] += compartment.time;
+        } else {
+          addons[compartment.addonId] = compartment.time;
+        }
+      }
+    }
+    let limit = this._interval * Preferences.get("browser.addon-watch.percentage-limit", 75) * 10;
+    for (let addonId in addons) {
+      if (!this._ignoreList.has(addonId)) {
+        if (this._lastAddonTime[addonId] && (addons[addonId] - this._lastAddonTime[addonId]) > limit) {
+          this._callback(addonId);
+        }
+        this._lastAddonTime[addonId] = addons[addonId];
+      }
+    }
+  },
+  ignoreAddonForSession: function(addonid) {
+    this._ignoreList.add(addonid);
+  },
+  ignoreAddonPermanently: function(addonid) {
+    this._ignoreList.add(addonid);
+    try {
+      let ignoreList = JSON.parse(Preferences.get("browser.addon-watch.ignore", "[]"))
+      if (!ignoreList.includes(addonid)) {
+        ignoreList.push(addonid);
+        Preferences.set("browser.addon-watch.ignore", JSON.stringify(ignoreList));
+      }
+    } catch (ex) {
+      Preferences.set("browser.addon-watch.ignore", JSON.stringify([addonid]));
+    }
+  }
+};
--- a/toolkit/modules/ResetProfile.jsm
+++ b/toolkit/modules/ResetProfile.jsm
@@ -29,42 +29,16 @@ this.ResetProfile = {
         ("@mozilla.org/profile/migrator;1?app=" + MOZ_BUILD_APP + "&type=" + MOZ_APP_NAME in Cc);
     } catch (e) {
       // Catch exception when there is no selected profile.
       Cu.reportError(e);
     }
     return false;
   },
 
-  getMigratedData: function() {
-    Cu.import("resource:///modules/MigrationUtils.jsm");
-
-    // From migration.properties
-    const MIGRATED_TYPES = [
-      128,// Windows/Tabs
-      4,  // History and Bookmarks
-      16, // Passwords
-      8,  // Form History
-      2,  // Cookies
-    ];
-
-    // Loop over possible data to migrate to give the user a list of what will be preserved.
-    let dataTypes = [];
-    for (let itemID of MIGRATED_TYPES) {
-      try {
-        let typeName = MigrationUtils.getLocalizedString(itemID + "_" + MOZ_APP_NAME);
-        dataTypes.push(typeName);
-      } catch (x) {
-        // Catch exceptions when the string for a data type doesn't exist.
-        Cu.reportError(x);
-      }
-    }
-    return dataTypes;
-  },
-
   /**
    * Ask the user if they wish to restart the application to reset the profile.
    */
   openConfirmationDialog: function(window) {
     // Prompt the user to confirm.
     let params = {
       reset: false,
     };
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -7,16 +7,17 @@
 XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']
 MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
 
 SPHINX_TREES['toolkit_modules'] = 'docs'
 
 EXTRA_JS_MODULES += [
+    'AddonWatcher.jsm',
     'Battery.jsm',
     'BinarySearch.jsm',
     'BrowserUtils.jsm',
     'CharsetMenu.jsm',
     'debug.js',
     'DeferredTask.jsm',
     'Deprecated.jsm',
     'Dict.jsm',
--- a/toolkit/mozapps/downloads/nsHelperAppDlg.js
+++ b/toolkit/mozapps/downloads/nsHelperAppDlg.js
@@ -1,17 +1,18 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /*
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+const {utils: Cu, interfaces: Ci, classes: Cc, results: Cr} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
 
 ///////////////////////////////////////////////////////////////////////////////
 //// Helper Functions
 
 /**
  * Determines if a given directory is able to be used to download to.
  *
  * @param aDirectory
@@ -137,16 +138,24 @@ nsUnknownContentTypeDialog.prototype = {
   // show: Open XUL dialog using window watcher.  Since the dialog is not
   //       modal, it needs to be a top level window and the way to open
   //       one of those is via that route).
   show: function(aLauncher, aContext, aReason)  {
     this.mLauncher = aLauncher;
     this.mContext  = aContext;
     this.mReason   = aReason;
 
+    // Cache some information in case this context goes away:
+    try {
+      let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+      this._mDownloadDir = new downloadModule.DownloadLastDir(parent);
+    } catch (ex) {
+      Cu.reportError("Missing window information when showing nsIHelperAppLauncherDialog: " + ex);
+    }
+
     const nsITimer = Components.interfaces.nsITimer;
     this._showTimer = Components.classes["@mozilla.org/timer;1"]
                                 .createInstance(nsITimer);
     this._showTimer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT);
   },
 
   // When opening from new tab, if tab closes while dialog is opening,
   // (which is a race condition on the XUL file being cached and the timer
@@ -201,16 +210,32 @@ nsUnknownContentTypeDialog.prototype = {
     this.mLauncher = aLauncher;
 
     let prefs = Components.classes["@mozilla.org/preferences-service;1"]
                           .getService(Components.interfaces.nsIPrefBranch);
     let bundle =
       Services.strings
               .createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
 
+    let parent;
+    let gDownloadLastDir;
+    try {
+      parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+    } catch (ex) {}
+
+    if (parent) {
+      gDownloadLastDir = new downloadModule.DownloadLastDir(parent);
+    } else {
+      // Use the cached download info, but pick an arbitrary parent window
+      // because the original one is definitely gone (and nsIFilePicker doesn't like
+      // a null parent):
+      gDownloadLastDir = this._mDownloadDir;
+      parent = Services.wm.getMostRecentWindow("");
+    }
+
     Task.spawn(function() {
       if (!aForcePrompt) {
         // Check to see if the user wishes to auto save to the default download
         // folder without prompting. Note that preference might not be set.
         let autodownload = false;
         try {
           autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR);
         } catch (e) { }
@@ -236,22 +261,19 @@ nsUnknownContentTypeDialog.prototype = {
           }
         }
       }
 
       // Use file picker to show dialog.
       var nsIFilePicker = Components.interfaces.nsIFilePicker;
       var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
       var windowTitle = bundle.GetStringFromName("saveDialogTitle");
-      var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);
       picker.init(parent, windowTitle, nsIFilePicker.modeSave);
       picker.defaultString = aDefaultFile;
 
-      let gDownloadLastDir = new downloadModule.DownloadLastDir(parent);
-
       if (aSuggestedFileExtension) {
         // aSuggestedFileExtension includes the period, so strip it
         picker.defaultExtension = aSuggestedFileExtension.substring(1);
       }
       else {
         try {
           picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
         }
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -200,17 +200,17 @@ xul|colorpicker[type="button"] {
 }
 
 xul|button > xul|*.button-box,
 xul|menulist > xul|*.menulist-label-box {
   padding-right: 10px !important;
   padding-left: 10px !important;
 }
 
-xul|menulist > xul|*.menulist-label-box > xul|*.menulist-icon {
+xul|menulist > xul|*.menulist-label-box > xul|*.menulist-icon[src] {
   -moz-margin-end: 5px;
 }
 
 xul|button[type="menu"] > xul|*.button-box > xul|*.button-menu-dropmarker {
   -moz-appearance: none;
   margin: 1px 0;
   -moz-margin-start: 10px;
   padding: 0;
--- a/toolkit/themes/windows/global/in-content/common.css
+++ b/toolkit/themes/windows/global/in-content/common.css
@@ -58,14 +58,34 @@ xul|*.checkbox-icon {
 }
 
 html|a:-moz-focusring,
 xul|*.text-link:-moz-focusring,
 xul|*.inline-link:-moz-focusring {
   border: 1px dotted -moz-DialogText;
 }
 
+/* Don't draw a transparent border for the focusring because when page
+   colors are disabled, the border is drawn in -moz-DialogText */
+xul|*.text-link:not(:-moz-focusring),
+xul|button:not(:-moz-focusring) > xul|*.button-box,
+xul|menulist:not(:-moz-focusring) > xul|*.menulist-label-box,
 xul|radio:not([focused="true"]) > xul|*.radio-label-box,
 xul|checkbox:not(:-moz-focusring) > xul|*.checkbox-label-box {
   border-width: 0;
   margin: 1px;
+}
+
+xul|*.text-link:not(:-moz-focusring) {
+  margin-top: 2px;
+  margin-right: 1px !important;
+  margin-left: 1px !important;
+  margin-bottom: 3px;
+}
+
+xul|menulist:not(:-moz-focusring) > xul|*.menulist-label-box {
+  margin: 2px;
+}
+
+xul|radio:not([focused="true"]) > xul|*.radio-label-box,
+xul|checkbox:not(:-moz-focusring) > xul|*.checkbox-label-box {
   -moz-margin-start: 0;
 }