Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 16 Feb 2015 16:14:51 +0100
changeset 256465 12189d1c13af8dea7ebfe91391dee8b1186aeaca
parent 256464 c4141f98a5df598af3d22f81682de7cdf23a8564 (current diff)
parent 256394 09f4968d5f429e48e8d53a8a21408cb9674e996c (diff)
child 256466 3341a0bc3296f3c6096eadf378da2db912a58c7e
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
--- 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
zc$|G#c{r478y_QMmx@dw(~y&8%oxTp)-jAFBaNj=)QmCAU>0MBL6oJDvPN{AY@I};
zlNxPG_9ZG6PE8~fM`TIbEPbP|&i7s4)pwrjdf(@{@8|cspWk!e_aE<@=IP<AD7RG(
z001bux;PTVGe!J_%7Dc0O4Hc@@wA2OMC5w0g1OOT4i$h2Vg*tmt_*Srl|Us2#YA*b
ztpNZ@9lAG>OT@dQDJ+H&c@1O4XRrZl!`hC|CR4(wTu2}_gwC{q&eq?BLg+y@(7h-;
z0?)=$X>^xZ4%I8x!<!NtMzIWn+Sx*^`Dk$f29-;O@EPIENHpID`YkS6yk2v|p^$G7
zZkP@9-=c_kPY9O9p+ZnbW-toE*c4)JX=IEtGq*H1gdoK>9AN=RB47whG|B{xKtjF`
zsQ5HaP%xU{i2Ht)*s_7rxLh_G4v&tGHi|YeVsS#?#+H_ra0C*LM8d=fSY!;7OXkCv
zk=j2ZI8q}i96FmzXE7mbHaU>R<Jv%>Kd!)F<MDq7W=4KblsIK@KA8<SHbTG|4EPUp
zB$q%9{%3TgcMO{fCr~3<JPt)%k6`WpI_jUPYe2CYv?GhcV^En~S4SJD*kcq#4?-i2
zjZGX(5EzUl4r^@eWQxIHO>w3;2Xj*t!W3hQ`N4H%Msmqa3iSt<{vWR8KXTDn4wcMh
zalBcq@E_Ilq_McHNE(X`!FriNcH+qtI&;mTx0c-RTB#g*6g3FPVKE@zvV*4oM1eUH
zgR(ToVZ{m@ey-r(+@PP_{5#k2|8n7CG4TH>^$(Z0f7X_NH?G+DyY;C|aldoKjh$RY
zQvm>E@47l-y!oQ(qPTFcKesf_-brXpV+02NlC2i*DWgN)|0muc`Bb8x?0NdxLO<Oq
z-YEi4q!msJuQIEt22~zqI#C**K5chX^DPbAMbLG;M}a+5bOWSj8%DT~T5JAtZT@*f
zR(ywLLF^vyXU0)8V}~Ah+=<W_9$T^+o*Il)5<+BDHqd3TOo>EHN+Seu?3|v6`@-?b
z`;{k!cM9Z%ZP(zQ8dgzDH_M0SL@NrUXL|}RvIOy1o@n;I(`arQ6@iK~f(<;^XBx-0
zP|x@F?fV)XvTH~^dSJ8N7rY|S=}S}ssr2Kdux;R=r|H$d8k%UrEXlmw1+&?(KUmYN
zG0i5I0kLX{C#Athf!)6Nw;iY3gqKM54}6~~4sh@;tp~=nn@+77{PKRC#}|i+u_Z|G
zUn8V|xp%c%Tfz$No=&7F%2`L}j(+yFxYi;c0PL`o7?>+k+ops~-=O<B{MHB3b3=<F
z=P;gXryRZTLK%4cYVR=uEmbw4qAB8@G-G`;>Wf}6wzfJqOYp~ayRSDhyGjfSPUje<
z11<92bXOG^z2MCRTn2g#?A@G#SXP%-+^!~5l`6kwKE|xX3ixt=3jh6E&x!v0`af*d
zcdKRk11;9yYahB~f1`SFI(ZVr-B2kd+$c1RDTW<Odsrk{J6*5bU3kCb>lgd@-znGq
z1NZ47&XzXKKgp<gHI_m*sCmLlA`a|1e4`>~@m2vq1t*ILER`5;JFoPKp!91SPP4T%
zL-dSlKD9cq7~7lBKs=s*(n#SIWK(J9!zFExCF-xbhOw17X1~4JJLn%<%0pe*sE6rl
z4RYG6Iv2p7#l}oz_Z(u>)|Ah9cI-MTpHD9b>I{XuS>uC!O69}8iW1ztf{b$Gm~fp@
zR-DIbIr-C^K~rCu=7sgCkdapVVlc1H;B4>X$&xEIVf?#iM^xoeEs_;a5_2vp1|}=j
z2kIXzO7Q7=qlk|*U+uBZwMX8$4DS3rXIq?AIYa_-@M`?;BgYzqav5g5oFfs#=-U&S
zQJ%nu2J$XVqSUBjQRa!=erAZLjr{B^flXesBjwEESfNe9fY&ApN!Uk*>}@*>&6bxI
z?0hX-bJb_M`h~CpIXq9fg~;aVe4^3L5TJ@UiizFrFa3w=t0$XXlr-ehPG=D+Y3Xh1
zo0(P(Nrik<{4P}iF(cAieLdlxYCGa?l(Z3dha#ofXWl=ZfXqm0hQkKG%oHBl=WHh7
z2AHV&bhAvVe^<h3_*7>*5&){<gA1Vxg;iS9eh%f9HT3g_$${@do5~NBW$?O7x}1Yg
zDJm2WTmXW~i+rvqDS`ZD+>}5??j4JQXQ4-$R8(L_aU8F@JTfA^tTK;~cKYs(@ml}-
zDnl~3x*v!&kDK{jrO+d<W&6p=1Oz2u$=|Z((bj%ovL5nJw+*<*`ssDuT4%j=l+CM*
z6*Brrw7T(=Zm6cxx?3Buyo^1eh~8lu<UrmY{Y@<)Z-RF{!qaI2J#Te|qPoG9*)6ag
zV1+hXdT4T)v0o2CsDOL9?*IcqoOSUw<nQnak*RedG7zK1iDwcf=vg98PU%4gBJk3K
zY#p*ry8zd>E-A}7zP4=JtMlvhlj)^51&M)DS~Is^RO^E`nUF1(S9VagbU9yY$a)bW
z8}5LLNM5`dM<BV}LlP4yW3sa@6@DbgC3NrLXo?C<+cFJiJ00#NuP}V{<0Ufqj1GAn
zb9w6cR(rO@9|r7&7EEX$sNo#R^9IKFVVWevztip_7o77Vs&BHj{PAra@(}^lV@c}h
zRpr|u!=V`Wb8=Y!m8}|9F>~i0=Yy6H_P>o9`VbKQra3>S(?|D|&!bfJ%?;6SC7r_?
zBk;9}OQZ9;7K1JMO|ReiJ!>RIu;$}dKUYxlfYHS%eM6R<S9J^Qt&PR@ClReT5(UyT
z871*kzUMw(dyF(_T3FQ@wr@`)w?<B$y>fK>OVC+C=6X$0tc?D<%0T4Kij`r~{N9ni
z6E=X~row`kN>f@C7X>1?)p9{$mY&2qEqQ6s`i7K?Kv?bqMQw+RrhulovoYht?C@h1
zb#7<9e(F<m(e1AfN4_Sce{jYaTy=P=A0(07qPR_s|6u6))Y4L^a^BnK?$VWI>6ydJ
zn;9J=?ekE$?>vNb43$dLoT;pSG9s#vQPVn+@(Khs>m3?cni62y$BX;Z$!a}avH}^M
zr2DZJ>=j}BU-xU`q%!fH=WnRLjkdTRA7ZYRr4(2_H*st9fvQ67jD6TL4kO*M+x&n8
z<nE@8#$6%A`5fESg}uA)DDk9XNoFZlKBgs*?4VoK9%%L>G|2Q>HBcye*Uz#cd~SY#
zcQ5NQbW(oQV0Hb>bMu*ymE!t2ZPAVxaK%&!`u+B!0X$oud`(^&#n*PQ<x6})p<P4$
zSe#X(qD6T=YN$T;jrYeBQ&EyKeb*AEi<3UzE9S^)??`Zrs7+aB9jqL)V+8CUm{#z&
zi^R#b==xi{)CwK^u=HZs`Y;&TXdAvk+d|g$)pMKIt@yOKvazE3$~2*y5Ug^)8zx;u
ziJ5-hJ<j|z`2@Y6`r^;1#*B%i5Atf#t8=+yY4anP>5~HXxJu8uR`;2(-!`PX+V`5q
znmYFjNDzb&7X~~0p`hB(pyZH0<qC0O<RrvwaGUKrKNDhz`K60P#I}*lbU_DIN?S?}
zn3Vr^bD|$YMY!A}=Xrd>`p#>EeATJFlWx`HHf&HhblVK`&PYPY$6sI8WSz0dk72i)
z3nWpT3mT$hL?bKP-RijGr)Za-Bgs2`FLq}9ieEISb}QzOcRQWfcA@`N4=nYBhe0t$
zUo%Y+9Sf|PVDddQXjI9u1P%Gy;6{tpeL~QI!3}<@2*Q!xr)~;hH)Bw;%G<=u;A`rT
z;+`uP{qI(?69P^>@Fn;A-E%`Z1#cp56qT6WwAgn1D)`N3|Hdh)ud`|OOSt3@3rd~Y
z{+{8uhz(m^Bt~{8DsQSG6V&@9D94FV2fb!e4-x;V4yeK6KCNoKf8(z_hun@9Rl}Lr
z#?w&_U`K%TIApQQBwVTDrB@vTM}BokUZ8hmhqT(riG@$P$8DaT7*PpCkzkGXLOuJ|
z&zCd=${i3W7^m~vI)5}%XXWAnD=)RpWh8{`p)u1>?Yn&+Ay2zv5xFpJpMA?#3Aer;
zO%5fwOMr%~H#fHGMezE50f0l_IO#)`ZKd2FhrR+r#!h1#8(OwI$|wMnWfCzY<?RR=
w1?W1^w=W+Dk}~+`JBVXH-O-tNXD0yy2wLz;(W8=+Yd<)yP9Bbz97sw30xQG8wEzGB
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;
 }