Merge m-c to inbound, a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Tue, 08 Sep 2015 17:06:42 -0700
changeset 291234 5686d29038b2eaf9786fb38d7d2ff95865c904e6
parent 291233 aa9951b7efebc4202c1b2817313ec14bcec93f03 (current diff)
parent 291202 01ae99b53561a2c3b40533d8c1c92bd3efc42d00 (diff)
child 291235 8a798c03d76c42b15025e94fb3cf36f1e20e1ff5
push id5231
push useratolfsen@mozilla.com
push dateWed, 09 Sep 2015 14:01:39 +0000
reviewersmerge
milestone43.0a1
Merge m-c to inbound, a=merge CLOSED TREE
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/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="e935894ef5f27e2f04b9e929a45a958e6288a223">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b81185d30e548f782770b852473ffb53c641a490"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e69f2c86b931b723dd405115e33a1d02e6c5f5e6"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <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="c6cace53426b5be7e56c0fd202118009689bc707"/>
--- 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="e935894ef5f27e2f04b9e929a45a958e6288a223">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b81185d30e548f782770b852473ffb53c641a490"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e69f2c86b931b723dd405115e33a1d02e6c5f5e6"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <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="c6cace53426b5be7e56c0fd202118009689bc707"/>
--- 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="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <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="b81185d30e548f782770b852473ffb53c641a490"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e69f2c86b931b723dd405115e33a1d02e6c5f5e6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="27eb2f04e149fc2c9976d881b1b5984bbe7ee089"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="067c08fb3e5744b42b68d1f861245f7d507109bc"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <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="b81185d30e548f782770b852473ffb53c641a490"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e69f2c86b931b723dd405115e33a1d02e6c5f5e6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
   <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="e935894ef5f27e2f04b9e929a45a958e6288a223">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b81185d30e548f782770b852473ffb53c641a490"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e69f2c86b931b723dd405115e33a1d02e6c5f5e6"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <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="c6cace53426b5be7e56c0fd202118009689bc707"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b81185d30e548f782770b852473ffb53c641a490"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e69f2c86b931b723dd405115e33a1d02e6c5f5e6"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <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="c6cace53426b5be7e56c0fd202118009689bc707"/>
--- 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="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <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="b81185d30e548f782770b852473ffb53c641a490"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e69f2c86b931b723dd405115e33a1d02e6c5f5e6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="27eb2f04e149fc2c9976d881b1b5984bbe7ee089"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="067c08fb3e5744b42b68d1f861245f7d507109bc"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b81185d30e548f782770b852473ffb53c641a490"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e69f2c86b931b723dd405115e33a1d02e6c5f5e6"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <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="c6cace53426b5be7e56c0fd202118009689bc707"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "b81185d30e548f782770b852473ffb53c641a490", 
+        "git_revision": "e69f2c86b931b723dd405115e33a1d02e6c5f5e6", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "d0a1f4d22e1a40cb89f9d592334e7506ee1317bd", 
+    "revision": "410ec98908ee38c636b018d3ba92e17fb338db79", 
     "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="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <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="b81185d30e548f782770b852473ffb53c641a490"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e69f2c86b931b723dd405115e33a1d02e6c5f5e6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
   <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="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b81185d30e548f782770b852473ffb53c641a490"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e69f2c86b931b723dd405115e33a1d02e6c5f5e6"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <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="c6cace53426b5be7e56c0fd202118009689bc707"/>
--- a/browser/base/content/browser-fullScreen.js
+++ b/browser/base/content/browser-fullScreen.js
@@ -90,16 +90,21 @@ var FullScreen = {
       this.cleanup();
       // In TabsInTitlebar._update(), we cancel the appearance update on
       // resize event for exiting fullscreen, since that happens before we
       // change the UI here in the "fullscreen" event. Hence we need to
       // call it here to ensure the appearance is properly updated. See
       // TabsInTitlebar._update() and bug 1173768.
       TabsInTitlebar.updateAppearance(true);
     }
+
+    if (enterFS) {
+      Services.telemetry.getHistogramById("FX_BROWSER_FULLSCREEN_USED")
+                        .add(1);
+    }
   },
 
   exitDomFullScreen : function() {
     document.mozCancelFullScreen();
   },
 
   handleEvent: function (event) {
     switch (event.type) {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7528,18 +7528,16 @@ function switchToTabHavingURI(aURI, aOpe
     for (let i = 0; i < browsers.length; i++) {
       let browser = browsers[i];
       if (ignoreFragment ? browser.currentURI.equalsExceptRef(aURI) :
                            browser.currentURI.equals(aURI)) {
         // Focus the matching window & tab
         aWindow.focus();
         if (ignoreFragment) {
           let spec = aURI.spec;
-          if (!aURI.ref)
-            spec += "#";
           browser.loadURI(spec);
         }
         aWindow.gBrowser.tabContainer.selectedIndex = i;
         return true;
       }
       if (ignoreQueryString || replaceQueryString) {
         if (browser.currentURI.spec.split("?")[0] == aURI.spec.split("?")[0]) {
           // Focus the matching window & tab
--- a/browser/base/content/test/general/browser_bug1025195_switchToTabHavingURI_aOpenParams.js
+++ b/browser/base/content/test/general/browser_bug1025195_switchToTabHavingURI_aOpenParams.js
@@ -20,16 +20,22 @@ add_task(function test_ignoreFragment() 
   switchTab("about:home#2", true, { ignoreFragment: true });
   is(tabRefAboutHome, gBrowser.selectedTab, "The same about:home tab should be switched to");
   yield hashChangePromise;
   is(gBrowser.currentURI.ref, "2", "The ref should be updated to the new ref");
   switchTab("about:mozilla", true);
   switchTab("about:home#1", false);
   isnot(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should not be initial about:blank tab");
   is(gBrowser.tabs.length, numTabsAtStart + 1, "Should have one new tab opened");
+  switchTab("about:mozilla", true);
+  switchTab("about:home", true, {ignoreFragment: true});
+  yield promiseWaitForCondition(function() {
+    return tabRefAboutHome.linkedBrowser.currentURI.spec == "about:home";
+  });
+  is(tabRefAboutHome.linkedBrowser.currentURI.spec, "about:home", "about:home shouldn't have hash");
   switchTab("about:about", false, { ignoreFragment: true });
   cleanupTestTabs();
 });
 
 add_task(function test_ignoreQueryString() {
   let tabRefAboutHome = gBrowser.addTab("about:home?hello=firefox");
   yield promiseTabLoaded(tabRefAboutHome);
   let tabRefAboutMozilla = gBrowser.addTab("about:mozilla");
--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -1,30 +1,39 @@
 /* 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 Cu.import("resource:///modules/MSMigrationUtils.jsm");
 
 function EdgeProfileMigrator() {
 }
 
 EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
 
 EdgeProfileMigrator.prototype.getResources = function() {
   let resources = [
     MSMigrationUtils.getBookmarksMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
     MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
   ];
   return resources.filter(r => r.exists);
 };
 
+/* Somewhat counterintuitively, this returns:
+ * - |null| to indicate "There is only 1 (default) profile" (on win10+)
+ * - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid using this migrator.
+ * See MigrationUtils.jsm for slightly more info on how sourceProfiles is used.
+ */
+EdgeProfileMigrator.prototype.__defineGetter__("sourceProfiles", function() {
+  let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10.0");
+  return isWin10OrHigher ? null : [];
+});
+
 EdgeProfileMigrator.prototype.classDescription = "Edge Profile Migrator";
 EdgeProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=edge";
 EdgeProfileMigrator.prototype.classID = Components.ID("{62e8834b-2d17-49f5-96ff-56344903a2ae}");
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([EdgeProfileMigrator]);
--- a/browser/components/migration/nsEdgeReadingListExtractor.cpp
+++ b/browser/components/migration/nsEdgeReadingListExtractor.cpp
@@ -27,16 +27,20 @@
 NS_IMPL_ISUPPORTS(nsEdgeReadingListExtractor, nsIEdgeReadingListExtractor)
 
 NS_IMETHODIMP
 nsEdgeReadingListExtractor::Extract(const nsAString& aDBPath, nsIArray** aItems)
 {
   nsresult rv = NS_OK;
   *aItems = nullptr;
 
+  if (!aDBPath.Length()) {
+    return NS_ERROR_FAILURE;
+  }
+
   JET_ERR err;
   JET_INSTANCE instance;
   JET_SESID sesid;
   JET_DBID dbid;
   JET_TABLEID tableid;
   JET_COLUMNDEF urlColumnInfo = { 0 };
   JET_COLUMNDEF titleColumnInfo = { 0 };
   JET_COLUMNDEF addedDateColumnInfo = { 0 };
@@ -45,21 +49,19 @@ nsEdgeReadingListExtractor::Extract(cons
   // otherwise the compiler complains.
   nsCOMPtr<nsIMutableArray> items = do_CreateInstance(NS_ARRAY_CONTRACTID);
 
   // JET does not throw exceptions, and so error handling and ensuring we close
   // the DB is a bit finnicky. Keep track of how far we got so we guarantee closing
   // the right things
   bool instanceCreated, sessionCreated, dbOpened, tableOpened;
 
-  char16ptr_t dbPath = ToNewUnicode(aDBPath);
-
   // Check for the right page size and initialize with that
   unsigned long pageSize;
-  err = JetGetDatabaseFileInfoW(dbPath, &pageSize, sizeof(pageSize), JET_DbInfoPageSize);
+  err = JetGetDatabaseFileInfoW(aDBPath.BeginReading(), &pageSize, sizeof(pageSize), JET_DbInfoPageSize);
   NS_HANDLE_JET_ERROR(err)
   err = JetSetSystemParameter(&instance, NULL, JET_paramDatabasePageSize, pageSize, NULL);
   NS_HANDLE_JET_ERROR(err)
 
   // Turn off recovery, because otherwise we will create log files in either the cwd or
   // overwrite Edge's own logfiles, which is useless at best and at worst might mess with
   // Edge actually using the DB
   err = JetSetSystemParameter(&instance, NULL, JET_paramRecovery, NULL, "Off");
@@ -72,20 +74,20 @@ nsEdgeReadingListExtractor::Extract(cons
 
   err = JetInit(&instance);
   NS_HANDLE_JET_ERROR(err)
   err = JetBeginSession(instance, &sesid, 0, 0);
   NS_HANDLE_JET_ERROR(err)
   sessionCreated = true;
 
   // Actually open the DB, and make sure to do so readonly:
-  err = JetAttachDatabaseW(sesid, dbPath, JET_bitDbReadOnly);
+  err = JetAttachDatabaseW(sesid, aDBPath.BeginReading(), JET_bitDbReadOnly);
   NS_HANDLE_JET_ERROR(err)
   dbOpened = true;
-  err = JetOpenDatabaseW(sesid, dbPath, NULL, &dbid, JET_bitDbReadOnly);
+  err = JetOpenDatabaseW(sesid, aDBPath.BeginReading(), NULL, &dbid, JET_bitDbReadOnly);
   NS_HANDLE_JET_ERROR(err)
 
   // Open the readinglist table and get information on the columns we are interested in:
   err = JetOpenTable(sesid, dbid, "ReadingList", NULL, 0, JET_bitTableReadOnly, &tableid);
   NS_HANDLE_JET_ERROR(err)
   tableOpened = true;
   err = JetGetColumnInfo(sesid, dbid, "ReadingList", "URL", &urlColumnInfo,
                          sizeof(urlColumnInfo), JET_ColInfo);
--- a/browser/devtools/webconsole/console-output.js
+++ b/browser/devtools/webconsole/console-output.js
@@ -670,38 +670,42 @@ Messages.NavigationMarker.prototype = He
  *        Defaults to |Date.now()|.
  *        - link: (string) if provided, the message will be wrapped in an anchor
  *        pointing to the given URL here.
  *        - linkCallback: (function) if provided, the message will be wrapped in
  *        an anchor. The |linkCallback| function will be added as click event
  *        handler.
  *        - location: object that tells the message source: url, line, column
  *        and lineText.
+ *        - stack: array that tells the message source stack.
  *        - className: (string) additional element class names for styling
  *        purposes.
  *        - private: (boolean) mark this as a private message.
  *        - filterDuplicates: (boolean) true if you do want this message to be
  *        filtered as a potential duplicate message, false otherwise.
  */
 Messages.Simple = function(message, options = {})
 {
   Messages.BaseMessage.call(this);
 
   this.category = options.category;
   this.severity = options.severity;
   this.location = options.location;
+  this.stack    = options.stack;
   this.timestamp = options.timestamp || Date.now();
   this.prefix = options.prefix;
   this.private = !!options.private;
 
   this._message = message;
   this._className = options.className;
   this._link = options.link;
   this._linkCallback = options.linkCallback;
   this._filterDuplicates = options.filterDuplicates;
+
+  this._onClickCollapsible = this._onClickCollapsible.bind(this);
 };
 
 Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype,
 {
   /**
    * Message category.
    * @type string
    */
@@ -715,16 +719,24 @@ Messages.Simple.prototype = Heritage.ext
 
   /**
    * Message source location. Properties: url, line, column, lineText.
    * @type object
    */
   location: null,
 
   /**
+   * Holds the stackframes received from the server.
+   *
+   * @private
+   * @type array
+   */
+  stack: null,
+
+  /**
    * Message prefix
    * @type string|null
    */
   prefix: null,
 
   /**
    * Tells if this message comes from a private browsing context.
    * @type boolean
@@ -805,16 +817,30 @@ Messages.Simple.prototype = Heritage.ext
   init: function()
   {
     Messages.BaseMessage.prototype.init.apply(this, arguments);
     this._groupDepthCompat = this.output.owner.groupDepth;
     this._initRepeatID();
     return this;
   },
 
+  /**
+   * Tells if the message can be expanded/collapsed.
+   * @type boolean
+   */
+  collapsible: false,
+
+  /**
+   * Getter that tells if this message is collapsed - no details are shown.
+   * @type boolean
+   */
+  get collapsed() {
+    return this.collapsible && this.element && !this.element.hasAttribute("open");
+  },
+
   _initRepeatID: function()
   {
     if (!this._filterDuplicates) {
       return;
     }
 
     // Add the properties we care about for identifying duplicate messages.
     let rid = this._repeatID;
@@ -849,16 +875,19 @@ Messages.Simple.prototype = Heritage.ext
       return this;
     }
 
     let timestamp = new Widgets.MessageTimestamp(this, this.timestamp).render();
 
     let icon = this.document.createElementNS(XHTML_NS, "span");
     icon.className = "icon";
     icon.title = l10n.getStr("severity." + this._severityNameCompat);
+    if (this.stack) {
+      icon.addEventListener("click", this._onClickCollapsible);
+    }
 
     let prefixNode;
     if (this.prefix) {
       prefixNode = this.document.createElementNS(XHTML_NS, "span");
       prefixNode.className = "prefix devtools-monospace";
       prefixNode.textContent = this.prefix + ":";
     }
 
@@ -881,23 +910,36 @@ Messages.Simple.prototype = Heritage.ext
     }
 
     this.element.appendChild(timestamp.element);
     this.element.appendChild(indentNode);
     this.element.appendChild(icon);
     if (prefixNode) {
       this.element.appendChild(prefixNode);
     }
+
+    if (this.stack) {
+      let twisty = this.document.createElementNS(XHTML_NS, "a");
+      twisty.className = "theme-twisty";
+      twisty.href = "#";
+      twisty.title = l10n.getStr("messageToggleDetails");
+      twisty.addEventListener("click", this._onClickCollapsible);
+      this.element.appendChild(twisty);
+      this.collapsible = true;
+      this.element.setAttribute("collapsible", true);
+    }
+
     this.element.appendChild(body);
     if (repeatNode) {
       this.element.appendChild(repeatNode);
     }
     if (location) {
       this.element.appendChild(location);
     }
+
     this.element.appendChild(this.document.createTextNode("\n"));
 
     this.element.clipboardText = this.element.textContent;
 
     if (this.private) {
       this.element.setAttribute("private", true);
     }
 
@@ -939,16 +981,22 @@ Messages.Simple.prototype = Heritage.ext
     if (typeof this._message == "function") {
       container.appendChild(this._message(this));
     } else if (this._message instanceof Ci.nsIDOMNode) {
       container.appendChild(this._message);
     } else {
       container.textContent = this._message;
     }
 
+    if (this.stack) {
+      let stack = new Widgets.Stacktrace(this, this.stack).render().element;
+      body.appendChild(this.document.createTextNode("\n"));
+      body.appendChild(stack);
+    }
+
     return body;
   },
 
   /**
    * Render the repeat bubble DOM element part of the message.
    * @private
    * @return Element
    */
@@ -983,16 +1031,46 @@ Messages.Simple.prototype = Heritage.ext
     }
 
     // The ConsoleOutput owner is a WebConsoleFrame instance from webconsole.js.
     // TODO: move createLocationNode() into this file when bug 778766 is fixed.
     return this.output.owner.createLocationNode({url: url,
                                                  line: line,
                                                  column: column});
   },
+
+  /**
+   * The click event handler for the message expander arrow element. This method
+   * toggles the display of message details.
+   *
+   * @private
+   * @param nsIDOMEvent ev
+   *        The DOM event object.
+   * @see this.toggleDetails()
+   */
+  _onClickCollapsible: function(ev)
+  {
+    ev.preventDefault();
+    this.toggleDetails();
+  },
+
+  /**
+   * Expand/collapse message details.
+   */
+  toggleDetails: function()
+  {
+    let twisty = this.element.querySelector(".theme-twisty");
+    if (this.element.hasAttribute("open")) {
+      this.element.removeAttribute("open");
+      twisty.removeAttribute("open");
+    } else {
+      this.element.setAttribute("open", true);
+      twisty.setAttribute("open", true);
+    }
+  },
 }); // Messages.Simple.prototype
 
 
 /**
  * The Extended message.
  *
  * @constructor
  * @extends Messages.Simple
@@ -1325,40 +1403,23 @@ Messages.ConsoleGeneric = function(packe
     }
     default:
       Messages.Extended.call(this, packet.arguments, options);
       break;
   }
 
   this._repeatID.consoleApiLevel = packet.level;
   this._repeatID.styles = packet.styles;
-  this._stacktrace = this._repeatID.stacktrace = packet.stacktrace;
+  this.stack = this._repeatID.stacktrace = packet.stacktrace;
   this._styles = packet.styles || [];
-
-  this._onClickCollapsible = this._onClickCollapsible.bind(this);
 };
 
 Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype,
 {
   _styles: null,
-  _stacktrace: null,
-
-  /**
-   * Tells if the message can be expanded/collapsed.
-   * @type boolean
-   */
-  collapsible: false,
-
-  /**
-   * Getter that tells if this message is collapsed - no details are shown.
-   * @type boolean
-   */
-  get collapsed() {
-    return this.collapsible && this.element && !this.element.hasAttribute("open");
-  },
 
   _renderBodyPieceSeparator: function()
   {
     return this.document.createTextNode(" ");
   },
 
   render: function()
   {
@@ -1368,65 +1429,36 @@ Messages.ConsoleGeneric.prototype = Heri
     this._renderBodyPieces(msg);
 
     let repeatNode = Messages.Simple.prototype._renderRepeatNode.call(this);
     let location = Messages.Simple.prototype._renderLocation.call(this);
     if (location) {
       location.target = "jsdebugger";
     }
 
-    let stack = null;
-    let twisty = null;
-    if (this._stacktrace && this._stacktrace.length > 0) {
-      stack = new Widgets.Stacktrace(this, this._stacktrace).render().element;
-
-      twisty = this.document.createElementNS(XHTML_NS, "a");
-      twisty.className = "theme-twisty";
-      twisty.href = "#";
-      twisty.title = l10n.getStr("messageToggleDetails");
-      twisty.addEventListener("click", this._onClickCollapsible);
-    }
-
     let flex = this.document.createElementNS(XHTML_NS, "span");
     flex.className = "message-flex-body";
 
-    if (twisty) {
-      flex.appendChild(twisty);
-    }
-
     flex.appendChild(msg);
 
     if (repeatNode) {
       flex.appendChild(repeatNode);
     }
     if (location) {
       flex.appendChild(location);
     }
 
     let result = this.document.createDocumentFragment();
     result.appendChild(flex);
 
-    if (stack) {
-      result.appendChild(this.document.createTextNode("\n"));
-      result.appendChild(stack);
-    }
-
     this._message = result;
     this._stacktrace = null;
 
     Messages.Simple.prototype.render.call(this);
 
-    if (stack) {
-      this.collapsible = true;
-      this.element.setAttribute("collapsible", true);
-
-      let icon = this.element.querySelector(".icon");
-      icon.addEventListener("click", this._onClickCollapsible);
-    }
-
     return this;
   },
 
   _renderBody: function()
   {
     let body = Messages.Simple.prototype._renderBody.apply(this, arguments);
     body.classList.remove("devtools-monospace", "message-body");
     return body;
@@ -1480,46 +1512,16 @@ Messages.ConsoleGeneric.prototype = Heri
   },
 
   // no-op for the message location and .repeats elements.
   // |this.render()| handles customized message output.
   _renderLocation: function() { },
   _renderRepeatNode: function() { },
 
   /**
-   * Expand/collapse message details.
-   */
-  toggleDetails: function()
-  {
-    let twisty = this.element.querySelector(".theme-twisty");
-    if (this.element.hasAttribute("open")) {
-      this.element.removeAttribute("open");
-      twisty.removeAttribute("open");
-    } else {
-      this.element.setAttribute("open", true);
-      twisty.setAttribute("open", true);
-    }
-  },
-
-  /**
-   * The click event handler for the message expander arrow element. This method
-   * toggles the display of message details.
-   *
-   * @private
-   * @param nsIDOMEvent ev
-   *        The DOM event object.
-   * @see this.toggleDetails()
-   */
-  _onClickCollapsible: function(ev)
-  {
-    ev.preventDefault();
-    this.toggleDetails();
-  },
-
-  /**
    * Given a style attribute value, return a cleaned up version of the string
    * such that:
    *
    * - no external URL is allowed to load. See RE_CLEANUP_STYLES.
    * - only some of the properties are allowed, based on a whitelist. See
    *   RE_ALLOWED_STYLES.
    *
    * @param string style
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -124,16 +124,17 @@ support-files =
   test-bug_939783_console_trace_duplicates.html
   test-bug-952277-highlight-nodes-in-vview.html
   test-bug-609872-cd-iframe-parent.html
   test-bug-609872-cd-iframe-child.html
   test-bug-989025-iframe-parent.html
   test-bug_1050691_click_function_to_source.html
   test-bug_1050691_click_function_to_source.js
   test-console-api-stackframe.html
+  test-exception-stackframe.html
   test_bug_1010953_cspro.html^headers^
   test_bug_1010953_cspro.html
   test_bug1045902_console_csp_ignore_reflected_xss_message.html^headers^
   test_bug1045902_console_csp_ignore_reflected_xss_message.html
   test_bug1092055_shouldwarn.js^headers^
   test_bug1092055_shouldwarn.js
   test_bug1092055_shouldwarn.html
 
@@ -378,15 +379,16 @@ skip-if = e10s # Bug 1042253 - webconsol
 [browser_webconsole_output_table.js]
 [browser_console_variables_view_highlighter.js]
 [browser_webconsole_start_netmon_first.js]
 [browser_webconsole_console_trace_duplicates.js]
 [browser_webconsole_cd_iframe.js]
 [browser_webconsole_autocomplete_crossdomain_iframe.js]
 [browser_webconsole_console_custom_styles.js]
 [browser_webconsole_console_api_stackframe.js]
+[browser_webconsole_exception_stackframe.js]
 [browser_webconsole_column_numbers.js]
 [browser_console_open_or_focus.js]
 [browser_webconsole_bug_922212_console_dirxml.js]
 [browser_webconsole_shows_reqs_in_netmonitor.js]
 [browser_netmonitor_shows_reqs_in_webconsole.js]
 [browser_webconsole_bug_1050691_click_function_to_source.js]
 [browser_webconsole_context_menu_open_in_var_view.js]
--- a/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js
+++ b/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js
@@ -52,17 +52,17 @@ function test() {
         severity: SEVERITY_ERROR,
       }],
     });
 
     fixToolbox();
 
     let msg = [...result.matched][0];
     ok(msg, "message element found");
-    let locationNode = msg.querySelector(".message-location");
+    let locationNode = msg.querySelector(".message > .message-location");
     ok(locationNode, "message location element found");
 
     let title = locationNode.getAttribute("title");
     info("location node title: " + title);
     isnot(title.indexOf(" -> "), -1, "error comes from a subscript");
 
     let viewSource = browserconsole.viewSource;
     let URL = null;
@@ -73,17 +73,17 @@ function test() {
       clickPromise.resolve(null);
     };
 
     msg.scrollIntoView();
     EventUtils.synthesizeMouse(locationNode, 2, 2, {},
                                browserconsole.iframeWindow);
 
     info("wait for click on locationNode");
-    yield clickPromise;
+    yield clickPromise.promise;
 
     info("view-source url: " + URL);
     ok(URL, "we have some source URL after the click");
     isnot(URL.indexOf("toolbox.js"), -1, "we have the expected view source URL");
     is(URL.indexOf("->"), -1, "no -> in the URL given to view-source");
 
     browserconsole.viewSourceInDebugger = viewSource;
   }
--- a/browser/devtools/webconsole/test/browser_console_error_source_click.js
+++ b/browser/devtools/webconsole/test/browser_console_error_source_click.js
@@ -53,17 +53,17 @@ function test() {
     let viewSourceCalled = false;
     hud.viewSourceInDebugger = () => viewSourceCalled = true;
 
     for (let result of results) {
       viewSourceCalled = false;
 
       let msg = [...results[0].matched][0];
       ok(msg, "message element found for: " + result.text);
-      let locationNode = msg.querySelector(".message-location");
+      let locationNode = msg.querySelector(".message > .message-location");
       ok(locationNode, "message location element found");
 
       EventUtils.synthesizeMouse(locationNode, 2, 2, {}, hud.iframeWindow);
 
       ok(viewSourceCalled, "view source opened");
     }
 
     hud.viewSourceInDebugger = viewSource;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_exception_stackframe.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the console receive exceptions include a stackframe.
+// See bug 1184172.
+
+// On e10s, the exception is triggered in child process
+// and is ignored by test harness
+if (!Services.appinfo.browserTabsRemoteAutostart) {
+  expectUncaughtException();
+}
+
+function test() {
+  let hud;
+
+  const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" +
+                   "test/test-exception-stackframe.html";
+  const TEST_FILE = TEST_URI.substr(TEST_URI.lastIndexOf("/"));
+
+  Task.spawn(runner).then(finishTest);
+
+  function* runner() {
+    const {tab} = yield loadTab(TEST_URI);
+    hud = yield openConsole(tab);
+
+    const stack = [{
+      file: TEST_FILE,
+      fn: "thirdCall",
+      line: 21,
+    }, {
+      file: TEST_FILE,
+      fn: "secondCall",
+      line: 17,
+    }, {
+      file: TEST_FILE,
+      fn: "firstCall",
+      line: 12,
+    }];
+
+    let results = yield waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "nonExistingMethodCall is not defined",
+        category: CATEGORY_JS,
+        severity: SEVERITY_ERROR,
+        collapsible: true,
+        stacktrace: stack,
+      }],
+    });
+
+    let elem = [...results[0].matched][0];
+    ok(elem, "message element");
+
+    let msg = elem._messageObject;
+    ok(msg, "message object");
+
+    ok(msg.collapsed, "message is collapsed");
+
+    msg.toggleDetails();
+
+    ok(!msg.collapsed, "message is not collapsed");
+
+    msg.toggleDetails();
+
+    ok(msg.collapsed, "message is collapsed");
+
+    yield closeConsole(tab);
+  }
+}
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -1074,37 +1074,37 @@ function waitForMessages(options) {
         ok(false, "expected frame #" + i + " but didnt find it");
         return false;
       }
 
       if (expected.file) {
         let file = frame.querySelector(".message-location").title;
         if (!checkText(expected.file, file)) {
           ok(false, "frame #" + i + " does not match file name: " +
-                    expected.file);
+                    expected.file + " != " + file);
           displayErrorContext(rule, element);
           return false;
         }
       }
 
       if (expected.fn) {
         let fn = frame.querySelector(".function").textContent;
         if (!checkText(expected.fn, fn)) {
           ok(false, "frame #" + i + " does not match the function name: " +
-                    expected.fn);
+                    expected.fn + " != " + fn);
           displayErrorContext(rule, element);
           return false;
         }
       }
 
       if (expected.line) {
         let line = frame.querySelector(".message-location").sourceLine;
         if (!checkText(expected.line, line)) {
           ok(false, "frame #" + i + " does not match the line number: " +
-                    expected.line);
+                    expected.line + " != " + line);
           displayErrorContext(rule, element);
           return false;
         }
       }
     }
 
     return true;
   }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-exception-stackframe.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html dir="ltr" lang="en">
+  <head>
+    <meta charset="utf8">
+    <!--
+    - Any copyright is dedicated to the Public Domain.
+    - http://creativecommons.org/publicdomain/zero/1.0/
+    -->
+    <title>Test for bug 1184172 - stacktraces for exceptions</title>
+    <script>
+      function firstCall() {
+        secondCall();
+      }
+
+      // Check anonymous functions
+      var secondCall = function () {
+        thirdCall();
+      }
+
+      function thirdCall() {
+        nonExistingMethodCall();
+      }
+
+      window.onload = firstCall;
+    </script>
+  </head>
+  <body>
+    <p>Hello world!</p>
+  </body>
+</html>
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -1523,16 +1523,17 @@ WebConsoleFrame.prototype = {
 
     // Create a new message
     let msg = new Messages.Simple(errorMessage, {
       location: {
         url: displayOrigin,
         line: aScriptError.lineNumber,
         column: aScriptError.columnNumber
       },
+      stack: aScriptError.stacktrace,
       category: category,
       severity: severity,
       timestamp: aScriptError.timeStamp,
       private: aScriptError.private,
       filterDuplicates: true
     });
 
     let node = msg.init(this.output).render().element;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1895,16 +1895,17 @@ richlistitem[type~="action"][actiontype=
 
 #urlbar-go-button,
 #urlbar-reload-button,
 #urlbar-stop-button {
   margin: 0;
   list-style-image: url("chrome://browser/skin/reload-stop-go.png");
   padding: 0 9px;
   margin-inline-start: 2px;
+  border-inline-end-style: none;
   border-inline-start: 1px solid var(--urlbar-separator-color);
   border-image: linear-gradient(transparent 15%,
                                 var(--urlbar-separator-color) 15%,
                                 var(--urlbar-separator-color) 85%,
                                 transparent 85%);
   border-image-slice: 1;
 }
 
--- a/dom/mobileconnection/tests/marionette/manifest.ini
+++ b/dom/mobileconnection/tests/marionette/manifest.ini
@@ -21,17 +21,16 @@ qemu = true
 [test_call_barring_change_password.js]
 [test_call_waiting.js]
 [test_mobile_set_radio.js]
 [test_mobile_last_known_network.js]
 [test_mobile_icc_change.js]
 [test_mobile_connections_array_uninitialized.js]
 [test_mobile_signal_strength.js]
 [test_mobile_data_ipv6.js]
-disabled = Bug 979137
 [test_mobile_supported_network_types.js]
 [test_mobile_call_forwarding.js]
 [test_mobile_call_forwarding_set_error.js]
 [test_mobile_call_forwarding_get_error.js]
 [test_mobile_voice_privacy.js]
 [test_dsds_mobile_data_connection.js]
 [test_mobile_clir.js]
 [test_mobile_clir_radio_off.js]
--- a/dom/mobileconnection/tests/marionette/test_mobile_data_ipv6.js
+++ b/dom/mobileconnection/tests/marionette/test_mobile_data_ipv6.js
@@ -6,17 +6,17 @@ MARIONETTE_HEAD_JS = "head.js";
 
 /**
  * Test resulting IP address format with given APN settings.
  *
  * This test utility function performs following steps:
  *
  *   1) set "ril.data.apnSettings" to a given settings object,
  *   2) enable data connection and wait for a "datachange" event,
- *   3) check the IP address type of the active network interface,
+ *   3) check the IP address type of the active network info,
  *   4) disable data connection.
  *
  * Fulfill params: (none)
  * Reject params: (none)
  *
  * @param aApnSettings
  *        An APN settings value.
  * @param aIsIPv6
@@ -25,22 +25,22 @@ MARIONETTE_HEAD_JS = "head.js";
  * @return A deferred promise.
  */
 function doTest(aApnSettings, aHaveV4Address, aHaveV6Address) {
   return setDataApnSettings([])
     .then(() => setDataApnSettings(aApnSettings))
     .then(() => setDataEnabledAndWait(true))
     .then(function() {
       let nm = getNetworkManager();
-      let active = nm.active;
-      ok(active, "Active network interface");
-      log("  Interface: " + active.name);
+      let networkInfo = nm.activeNetworkInfo;
+      ok(networkInfo, "Active network info");
+      log("  Interface: " + networkInfo.name);
 
       let ips = {}, prefixLengths = {};
-      let num = active.getAddresses(ips, prefixLengths);
+      let num = networkInfo.getAddresses(ips, prefixLengths);
       log("  Num addresses: " + num);
       log("  Addresses: " + JSON.stringify(ips.value));
       log("  PrefixLengths: " + JSON.stringify(prefixLengths.value));
 
       if (aHaveV4Address) {
         ok(ips.value.reduce(function(aFound, aAddress) {
           return aFound || aAddress.indexOf(":") < 0;
         }), "IPv4 address");
@@ -101,24 +101,26 @@ startTestCommon(function() {
                  "types": ["default", "supl", "mms"] }]];
     })
 
     .then(function(aApnSettings) {
       return Promise.resolve()
 
         .then(() => doTestHome(aApnSettings, "NoSuchProtocol"))
         .then(() => doTestHome(aApnSettings, "IP"))
-        .then(() => doTestHome(aApnSettings, "IPV4V6"))
+        // TODO: Bug 979137 - B2G Emulator: Support the IPV4V6
+        //.then(() => doTestHome(aApnSettings, "IPV4V6"))
         .then(() => doTestHome(aApnSettings, "IPV6"))
 
         .then(() => setEmulatorRoamingAndWait(true))
 
         .then(() => doTestRoaming(aApnSettings, "NoSuchProtocol"))
         .then(() => doTestRoaming(aApnSettings, "IP"))
-        .then(() => doTestRoaming(aApnSettings, "IPV4V6"))
+        // TODO: Bug 979137 - B2G Emulator: Support the IPV4V6
+        //.then(() => doTestRoaming(aApnSettings, "IPV4V6"))
         .then(() => doTestRoaming(aApnSettings, "IPV6"))
 
         .then(() => setEmulatorRoamingAndWait(false));
     })
 
     .then(() => setDataRoamingEnabled(false))
     .then(function() {
       if (origApnSettings) {
--- a/mobile/android/base/home/SearchEngineRow.java
+++ b/mobile/android/base/home/SearchEngineRow.java
@@ -1,15 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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.home;
 
+import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.db.BrowserContract.SearchHistory;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.home.BrowserSearch.OnEditSuggestionListener;
 import org.mozilla.gecko.home.BrowserSearch.OnSearchListener;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.util.StringUtils;
@@ -269,17 +270,19 @@ class SearchEngineRow extends AnimatedHe
         return suggestionCounter;
     }
 
     public void updateSuggestions(boolean suggestionsEnabled, SearchEngine searchEngine, String searchTerm, boolean animate) {
         // This can be called before the opt-in permission prompt is shown or set. Check first.
         if (suggestionsEnabled) {
             final int recycledSuggestionCount = mSuggestionView.getChildCount();
             final int suggestionViewCount = updateFromSearchEngine(searchEngine, animate, recycledSuggestionCount);
-            updateFromSavedSearches(searchTerm, animate, suggestionViewCount, recycledSuggestionCount);
+            if (AppConstants.NIGHTLY_BUILD) {
+                updateFromSavedSearches(searchTerm, animate, suggestionViewCount, recycledSuggestionCount);
+            }
         }
     }
 
     @Override
     public boolean onKeyDown(int keyCode, android.view.KeyEvent event) {
         final View suggestion = mSuggestionView.getChildAt(mSelectedView);
 
         if (event.getAction() != android.view.KeyEvent.ACTION_DOWN) {
--- a/mobile/android/base/util/DrawableUtil.java
+++ b/mobile/android/base/util/DrawableUtil.java
@@ -13,31 +13,32 @@ import android.support.annotation.ColorR
 import android.support.annotation.DrawableRes;
 import android.support.annotation.NonNull;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.graphics.drawable.DrawableCompat;
 
 public class DrawableUtil {
 
     /**
-     * Tints the given drawable with the given color and returns it. Note that this
-     * transformation does not occur in place on pre-Lollipop devices (bug 1193950).
+     * Tints the given drawable with the given color and returns it.
      */
     @CheckResult
     public static Drawable tintDrawable(@NonNull final Context context, @DrawableRes final int drawableID,
                 @ColorRes final int colorID) {
         final Drawable icon = DrawableCompat.wrap(
                 ContextCompat.getDrawable(context, drawableID).mutate());
         DrawableCompat.setTint(icon, ColorUtils.getColor(context, colorID));
         return icon;
     }
 
     /**
-     * Tints the given drawable with the given tint list and returns it. Note that this
-     * transformation does not occur in place on pre-Lollipop devices (bug 1193950).
+     * Tints the given drawable with the given tint list and returns it. Note that you
+     * should no longer use the argument Drawable because the argument is not mutated
+     * on pre-Lollipop devices but is mutated on L+ due to differences in the Support
+     * Library implementation (bug 1193950).
      */
     @CheckResult
     public static Drawable tintDrawableWithStateList(@NonNull final Drawable drawable,
             @NonNull final ColorStateList colorList) {
         final Drawable wrappedDrawable = DrawableCompat.wrap(drawable.mutate());
         DrawableCompat.setTintList(wrappedDrawable, colorList);
         return wrappedDrawable;
     }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -320,20 +320,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 function dump(msg) {
   Log.d("Browser", msg);
 }
 
 const kStateActive = 0x00000001; // :active pseudoclass for elements
 
 const kXLinkNamespace = "http://www.w3.org/1999/xlink";
 
-const kDefaultCSSViewportWidth = 980;
-
-const kViewportRemeasureThrottle = 500;
-
 function fuzzyEquals(a, b) {
   return (Math.abs(a - b) < 1e-6);
 }
 
 XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() {
   let ContentAreaUtils = {};
   Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils);
   return ContentAreaUtils;
@@ -1054,19 +1050,16 @@ var BrowserApp = {
       this._selectedTab.setActive(false);
     }
 
     this._selectedTab = aTab;
     if (!aTab)
       return;
 
     aTab.setActive(true);
-    if (!AppConstants.MOZ_ANDROID_APZ) {
-      aTab.setResolution(aTab._zoom, true);
-    }
     this.contentDocumentChanged();
     this.deck.selectedPanel = aTab.browser;
     // Focus the browser so that things like selection will be styled correctly.
     aTab.browser.focus();
   },
 
   get selectedBrowser() {
     if (this._selectedTab)
@@ -3717,28 +3710,21 @@ Tab.prototype = {
     let scrolly = this.browser.contentWindow.scrollY * zoom;
     let displayPortMargins = {
       left: scrollx - aDisplayPort.left,
       top: scrolly - aDisplayPort.top,
       right: aDisplayPort.right - (scrollx + gScreenWidth),
       bottom: aDisplayPort.bottom - (scrolly + gScreenHeight)
     };
 
-    if (this._oldDisplayPortMargins == null ||
-        !fuzzyEquals(displayPortMargins.left, this._oldDisplayPortMargins.left) ||
-        !fuzzyEquals(displayPortMargins.top, this._oldDisplayPortMargins.top) ||
-        !fuzzyEquals(displayPortMargins.right, this._oldDisplayPortMargins.right) ||
-        !fuzzyEquals(displayPortMargins.bottom, this._oldDisplayPortMargins.bottom)) {
-      cwu.setDisplayPortMarginsForElement(displayPortMargins.left,
-                                          displayPortMargins.top,
-                                          displayPortMargins.right,
-                                          displayPortMargins.bottom,
-                                          element, 0);
-    }
-    this._oldDisplayPortMargins = displayPortMargins;
+    cwu.setDisplayPortMarginsForElement(displayPortMargins.left,
+                                        displayPortMargins.top,
+                                        displayPortMargins.right,
+                                        displayPortMargins.bottom,
+                                        element, 0);
   },
 
   setScrollClampingSize: function(zoom) {
     let viewportWidth = gScreenWidth / zoom;
     let viewportHeight = gScreenHeight / zoom;
     let screenWidth = gScreenWidth;
     let screenHeight = gScreenHeight;
 
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4057,16 +4057,21 @@
     "expires_in_version": "never",
     "kind": "exponential",
     "low": 50,
     "high": "5000",
     "n_buckets": 10,
     "extended_statistics_ok": true,
     "description": "Firefox: Time to initialize the bookmarks toolbar view (ms)"
   },
+  "FX_BROWSER_FULLSCREEN_USED": {
+    "expires_in_version": "46",
+    "kind": "count",
+    "description": "The number of times that a session enters browser fullscreen (f11-fullscreen)"
+  },
   "FX_NEW_WINDOW_MS": {
     "expires_in_version": "default",
     "kind": "exponential",
     "high": "10000",
     "n_buckets": 20,
     "extended_statistics_ok": true,
     "description": "Firefox: Time taken to open a new browser window (ms)"
   },
--- a/toolkit/devtools/Loader.jsm
+++ b/toolkit/devtools/Loader.jsm
@@ -136,43 +136,45 @@ SrcdirProvider.prototype = {
     let devtoolsDir = OS.Path.join(srcdir, "browser", "devtools");
     let toolkitDir = OS.Path.join(srcdir, "toolkit", "devtools");
     let modulesDir = OS.Path.join(srcdir, "toolkit", "modules");
     let mainURI = this.fileURI(OS.Path.join(devtoolsDir, "main.js"));
     let definitionsURI = this.fileURI(OS.Path.join(devtoolsDir, "definitions.js"));
     let devtoolsURI = this.fileURI(devtoolsDir);
     let toolkitURI = this.fileURI(toolkitDir);
     let serverURI = this.fileURI(OS.Path.join(toolkitDir, "server"));
+    let webideURI = this.fileURI(OS.Path.join(devtoolsDir, "webide", "modules"));
     let webconsoleURI = this.fileURI(OS.Path.join(toolkitDir, "webconsole"));
     let appActorURI = this.fileURI(OS.Path.join(toolkitDir, "apps", "app-actor-front.js"));
     let cssLogicURI = this.fileURI(OS.Path.join(toolkitDir, "styleinspector", "css-logic"));
     let cssColorURI = this.fileURI(OS.Path.join(toolkitDir, "css-color"));
     let outputParserURI = this.fileURI(OS.Path.join(toolkitDir, "output-parser"));
     let clientURI = this.fileURI(OS.Path.join(toolkitDir, "client"));
     let prettyFastURI = this.fileURI(OS.Path.join(toolkitDir, "pretty-fast.js"));
     let jsBeautifyURI = this.fileURI(OS.Path.join(toolkitDir, "jsbeautify", "beautify.js"));
     let asyncUtilsURI = this.fileURI(OS.Path.join(toolkitDir, "async-utils.js"));
     let contentObserverURI = this.fileURI(OS.Path.join(toolkitDir), "content-observer.js");
     let gcliURI = this.fileURI(OS.Path.join(toolkitDir, "gcli", "source", "lib", "gcli"));
-    let projecteditorURI = this.fileURI(OS.Path.join(devtoolsDir, "projecteditor"));
+    let projecteditorURI = this.fileURI(OS.Path.join(devtoolsDir, "projecteditor", "lib"));
     let promiseURI = this.fileURI(OS.Path.join(modulesDir, "Promise-backend.js"));
     let acornURI = this.fileURI(OS.Path.join(toolkitDir, "acorn"));
     let acornWalkURI = OS.Path.join(acornURI, "walk.js");
     let ternURI = OS.Path.join(toolkitDir, "tern");
     let sourceMapURI = this.fileURI(OS.Path.join(toolkitDir, "sourcemap", "source-map.js"));
     this.loader = new Loader.Loader({
       id: "fx-devtools",
       modules: loaderModules,
       paths: {
         "": "resource://gre/modules/commonjs/",
         "main": mainURI,
         "definitions": definitionsURI,
         "devtools": devtoolsURI,
         "devtools/toolkit": toolkitURI,
         "devtools/server": serverURI,
+        "devtools/webide": webideURI,
         "devtools/toolkit/webconsole": webconsoleURI,
         "devtools/app-actor-front": appActorURI,
         "devtools/styleinspector/css-logic": cssLogicURI,
         "devtools/css-color": cssColorURI,
         "devtools/output-parser": outputParserURI,
         "devtools/client": clientURI,
         "devtools/pretty-fast": prettyFastURI,
         "devtools/jsbeautify": jsBeautifyURI,
--- a/toolkit/devtools/gcli/commands/calllog.js
+++ b/toolkit/devtools/gcli/commands/calllog.js
@@ -4,16 +4,18 @@
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 const {TargetFactory} = require("devtools/framework/target");
 const l10n = require("gcli/l10n");
 const gcli = require("gcli/index");
 
+loader.lazyRequireGetter(this, "TargetFactory", "devtools/framework/target", true);
+
 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
 
 loader.lazyGetter(this, "Debugger", () => {
   let global = Cu.getGlobalForObject({});
   let JsDebugger = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
   JsDebugger.addDebuggerToGlobal(global);
   return global.Debugger;
 });
--- a/toolkit/devtools/gcli/commands/paintflashing.js
+++ b/toolkit/devtools/gcli/commands/paintflashing.js
@@ -1,17 +1,17 @@
 /* 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 { Ci } = require("chrome");
-const { getOuterId } = require("sdk/window/utils");
-const { getBrowserForTab } = require("sdk/tabs/utils");
+loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
+loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
 
 let telemetry;
 try {
   const Telemetry = require("devtools/shared/telemetry");
   telemetry = new Telemetry();
 } catch(e) {
   // DevTools Telemetry module only available in Firefox
 }
--- a/toolkit/devtools/gcli/commands/rulers.js
+++ b/toolkit/devtools/gcli/commands/rulers.js
@@ -2,18 +2,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const EventEmitter = require("devtools/toolkit/event-emitter");
 const eventEmitter = new EventEmitter();
 const events = require("sdk/event/core");
-const { getOuterId } = require("sdk/window/utils");
-const { getBrowserForTab } = require("sdk/tabs/utils");
+loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
+loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
 
 const l10n = require("gcli/l10n");
 require("devtools/server/actors/inspector");
 const { RulersHighlighter, HighlighterEnvironment } =
   require("devtools/server/actors/highlighter");
 
 const highlighters = new WeakMap();
 const visibleHighlighters = new Set();
--- a/toolkit/devtools/gcli/source/lib/gcli/util/filesystem.js
+++ b/toolkit/devtools/gcli/source/lib/gcli/util/filesystem.js
@@ -27,19 +27,24 @@ var OS = Cu.import('resource://gre/modul
  * really universal in scope) but also kind of do (because they're not specific
  * to GCLI
  */
 
 exports.join = OS.Path.join;
 exports.sep = OS.Path.sep;
 exports.dirname = OS.Path.dirname;
 
-var dirService = Cc['@mozilla.org/file/directory_service;1']
-                           .getService(Ci.nsIProperties);
-exports.home = dirService.get('Home', Ci.nsIFile).path;
+// On B2G, there is no home folder
+var home = null;
+try {
+  var dirService = Cc['@mozilla.org/file/directory_service;1']
+                     .getService(Ci.nsIProperties);
+  home = dirService.get('Home', Ci.nsIFile).path;
+} catch(e) {}
+exports.home = home;
 
 if ('winGetDrive' in OS.Path) {
   exports.sep = '\\';
 }
 else {
   exports.sep = '/';
 }
 
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -1283,16 +1283,31 @@ WebConsoleActor.prototype =
    *
    * @param nsIScriptError aPageError
    *        The page error we need to send to the client.
    * @return object
    *         The object you can send to the remote client.
    */
   preparePageErrorForRemote: function WCA_preparePageErrorForRemote(aPageError)
   {
+    let stack = null;
+    // Convert stack objects to the JSON attributes expected by client code
+    if (aPageError.stack) {
+      stack = [];
+      let s = aPageError.stack;
+      while (s !== null) {
+        stack.push({
+          filename: s.source,
+          lineNumber: s.line,
+          columnNumber: s.column,
+          functionName: s.functionDisplayName
+        });
+        s = s.parent;
+      }
+    }
     let lineText = aPageError.sourceLine;
     if (lineText && lineText.length > DebuggerServer.LONG_STRING_INITIAL_LENGTH) {
       lineText = lineText.substr(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
     }
 
     return {
       errorMessage: this._createStringGrip(aPageError.errorMessage),
       sourceName: aPageError.sourceName,
@@ -1302,16 +1317,17 @@ WebConsoleActor.prototype =
       category: aPageError.category,
       timeStamp: aPageError.timeStamp,
       warning: !!(aPageError.flags & aPageError.warningFlag),
       error: !!(aPageError.flags & aPageError.errorFlag),
       exception: !!(aPageError.flags & aPageError.exceptionFlag),
       strict: !!(aPageError.flags & aPageError.strictFlag),
       info: !!(aPageError.flags & aPageError.infoFlag),
       private: aPageError.isFromPrivateWindow,
+      stacktrace: stack
     };
   },
 
   /**
    * Handler for window.console API calls received from the ConsoleAPIListener.
    * This method sends the object to the remote Web Console client.
    *
    * @see ConsoleAPIListener
--- a/toolkit/devtools/server/content-server.jsm
+++ b/toolkit/devtools/server/content-server.jsm
@@ -26,30 +26,32 @@ function init(msg) {
   // in the same process.
   let devtools = new DevToolsLoader();
   devtools.invisibleToDebugger = true;
   devtools.main("devtools/server/main");
   let { DebuggerServer, ActorPool } = devtools;
 
   if (!DebuggerServer.initialized) {
     DebuggerServer.init();
+    DebuggerServer.isInChildProcess = true;
   }
 
   // In case of apps being loaded in parent process, DebuggerServer is already
   // initialized, but child specific actors are not registered.
   // Otherwise, for child process, we need to load actors the first
   // time we load child.js
   DebuggerServer.addChildActors();
 
   let mm = msg.target;
   mm.QueryInterface(Ci.nsISyncMessageSender);
   let prefix = msg.data.prefix;
 
   // Connect both parent/child processes debugger servers RDP via message managers
   let conn = DebuggerServer.connectToParent(prefix, mm);
+  conn.parentMessageManager = mm;
 
   let { ChildProcessActor } = devtools.require("devtools/server/actors/child-process");
   let actor = new ChildProcessActor(conn);
   let actorPool = new ActorPool(conn);
   actorPool.addActor(actor);
   conn.addActorPool(actorPool);
 
   let response = {actor: actor.form()};
--- a/toolkit/devtools/tests/mochitest/test_loader_paths.html
+++ b/toolkit/devtools/tests/mochitest/test_loader_paths.html
@@ -38,15 +38,15 @@
         Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 
       let builtin = new BuiltinProvider();
       builtin.load();
       let srcdir = new SrcdirProvider();
       srcdir.load();
 
       is(builtin.loader.mapping.length,
-         srcdir.loader.mapping.length + 1,
-         "The built-in loader should have only one more mapping for testing.");
+         srcdir.loader.mapping.length,
+         "The built-in loader should have the same number of mapping than testing.");
 
       Services.prefs.clearUserPref(SRCDIR_PREF);
     </script>
   </body>
 </html>
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -1641,16 +1641,17 @@ var AddonManagerInternal = {
                                  Cr.NS_ERROR_INVALID_ARG);
 
     if (!aID || typeof aID != "string")
       throw Components.Exception("aID must be a non-empty string",
                                  Cr.NS_ERROR_INVALID_ARG);
 
     if (gStartupComplete)
       return;
+    logger.debug("Registering startup change '" + aType + "' for " + aID);
 
     // Ensure that an ID is only listed in one type of change
     for (let type in this.startupChanges)
       this.removeStartupChange(type, aID);
 
     if (!(aType in this.startupChanges))
       this.startupChanges[aType] = [];
     this.startupChanges[aType].push(aID);
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -77,33 +77,33 @@ const PREF_EM_DSS_ENABLED             = 
 const PREF_DSS_SWITCHPENDING          = "extensions.dss.switchPending";
 const PREF_DSS_SKIN_TO_SELECT         = "extensions.lastSelectedSkin";
 const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
 const PREF_EM_UPDATE_URL              = "extensions.update.url";
 const PREF_EM_UPDATE_BACKGROUND_URL   = "extensions.update.background.url";
 const PREF_EM_ENABLED_ADDONS          = "extensions.enabledAddons";
 const PREF_EM_EXTENSION_FORMAT        = "extensions.";
 const PREF_EM_ENABLED_SCOPES          = "extensions.enabledScopes";
-const PREF_EM_AUTO_DISABLED_SCOPES    = "extensions.autoDisableScopes";
 const PREF_EM_SHOW_MISMATCH_UI        = "extensions.showMismatchUI";
 const PREF_XPI_ENABLED                = "xpinstall.enabled";
 const PREF_XPI_WHITELIST_REQUIRED     = "xpinstall.whitelist.required";
 const PREF_XPI_DIRECT_WHITELISTED     = "xpinstall.whitelist.directRequest";
 const PREF_XPI_FILE_WHITELISTED       = "xpinstall.whitelist.fileRequest";
 // xpinstall.signatures.required only supported in dev builds
 const PREF_XPI_SIGNATURES_REQUIRED    = "xpinstall.signatures.required";
 const PREF_XPI_SIGNATURES_DEV_ROOT    = "xpinstall.signatures.dev-root";
 const PREF_XPI_PERMISSIONS_BRANCH     = "xpinstall.";
 const PREF_XPI_UNPACK                 = "extensions.alwaysUnpack";
 const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
 const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin";
 const PREF_INSTALL_DISTRO_ADDONS      = "extensions.installDistroAddons";
 const PREF_BRANCH_INSTALLED_ADDON     = "extensions.installedDistroAddon.";
 const PREF_SHOWN_SELECTION_UI         = "extensions.shownSelectionUI";
 const PREF_INTERPOSITION_ENABLED      = "extensions.interposition.enabled";
+const PREF_SYSTEM_ADDON_SET           = "extensions.systemAddonSet";
 
 const PREF_EM_MIN_COMPAT_APP_VERSION      = "extensions.minCompatibleAppVersion";
 const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
 
 const PREF_CHECKCOMAT_THEMEOVERRIDE   = "extensions.checkCompatibility.temporaryThemeOverride_minAppVersion";
 
 const PREF_EM_HOTFIX_ID               = "extensions.hotfix.id";
 const PREF_EM_CERT_CHECKATTRIBUTES    = "extensions.hotfix.cert.checkAttributes";
@@ -111,31 +111,34 @@ const PREF_EM_HOTFIX_CERTS            = 
 
 const URI_EXTENSION_SELECT_DIALOG     = "chrome://mozapps/content/extensions/selectAddons.xul";
 const URI_EXTENSION_UPDATE_DIALOG     = "chrome://mozapps/content/extensions/update.xul";
 const URI_EXTENSION_STRINGS           = "chrome://mozapps/locale/extensions/extensions.properties";
 
 const STRING_TYPE_NAME                = "type.%ID%.name";
 
 const DIR_EXTENSIONS                  = "extensions";
+const DIR_SYSTEM_ADDONS               = "features";
 const DIR_STAGE                       = "staged";
 const DIR_TRASH                       = "trash";
 
 const FILE_DATABASE                   = "extensions.json";
 const FILE_OLD_CACHE                  = "extensions.cache";
 const FILE_RDF_MANIFEST               = "install.rdf";
 const FILE_WEB_MANIFEST               = "manifest.json";
 const FILE_XPI_ADDONS_LIST            = "extensions.ini";
 
 const KEY_PROFILEDIR                  = "ProfD";
 const KEY_APPDIR                      = "XCurProcD";
 const KEY_TEMPDIR                     = "TmpD";
 const KEY_APP_DISTRIBUTION            = "XREAppDist";
 
 const KEY_APP_PROFILE                 = "app-profile";
+const KEY_APP_SYSTEM_ADDONS           = "app-system-addons";
+const KEY_APP_SYSTEM_DEFAULTS         = "app-system-defaults";
 const KEY_APP_GLOBAL                  = "app-global";
 const KEY_APP_SYSTEM_LOCAL            = "app-system-local";
 const KEY_APP_SYSTEM_SHARE            = "app-system-share";
 const KEY_APP_SYSTEM_USER             = "app-system-user";
 
 const NOTIFICATION_FLUSH_PERMISSIONS  = "flush-pending-permissions";
 const XPI_PERMISSION                  = "install";
 
@@ -153,22 +156,16 @@ const NOTIFICATION_TOOLBOXPROCESS_LOADED
 // Properties that exist in the install manifest
 const PROP_METADATA      = ["id", "version", "type", "internalName", "updateURL",
                             "updateKey", "optionsURL", "optionsType", "aboutURL",
                             "iconURL", "icon64URL"];
 const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
 const PROP_LOCALE_MULTI  = ["developers", "translators", "contributors"];
 const PROP_TARGETAPP     = ["id", "minVersion", "maxVersion"];
 
-// Properties that should be migrated where possible from an old database. These
-// shouldn't include properties that can be read directly from install.rdf files
-// or calculated
-const DB_MIGRATE_METADATA= ["installDate", "userDisabled", "softDisabled",
-                            "sourceURI", "applyBackgroundUpdates",
-                            "releaseNotesURI", "foreignInstall", "syncGUID"];
 // Properties to cache and reload when an addon installation is pending
 const PENDING_INSTALL_METADATA =
     ["syncGUID", "targetApplications", "userDisabled", "softDisabled",
      "existingAddonID", "sourceURI", "releaseNotesURI", "installDate",
      "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"];
 
 // Note: When adding/changing/removing items here, remember to change the
 // DB schema version to ensure changes are picked up ASAP.
@@ -259,45 +256,63 @@ var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{
 
 Cu.import("resource://gre/modules/Log.jsm");
 const LOGGER_ID = "addons.xpi";
 
 // Create a new logger for use by all objects in this Addons XPI Provider module
 // (Requires AddonManager.jsm)
 let logger = Log.repository.getLogger(LOGGER_ID);
 
-const LAZY_OBJECTS = ["XPIDatabase"];
+const LAZY_OBJECTS = ["XPIDatabase", "XPIDatabaseReconcile"];
 
 var gLazyObjectsLoaded = false;
 
 function loadLazyObjects() {
-  let scope = {};
-  scope.AddonInternal = AddonInternal;
-  scope.XPIProvider = XPIProvider;
-  scope.XPIStates = XPIStates;
-  Services.scriptloader.loadSubScript("resource://gre/modules/addons/XPIProviderUtils.js",
-                                      scope);
+  let uri = "resource://gre/modules/addons/XPIProviderUtils.js";
+  let scope = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), {
+    sandboxName: uri,
+    wantGlobalProperties: ["TextDecoder"],
+  });
+
+  let shared = {
+    ADDON_SIGNING,
+    SIGNED_TYPES,
+    BOOTSTRAP_REASONS,
+    AddonInternal,
+    XPIProvider,
+    XPIStates,
+    syncLoadManifestFromFile,
+    isUsableAddon,
+    recordAddonTelemetry,
+    applyBlocklistChanges,
+    flushStartupCache,
+  }
+
+  for (let key of Object.keys(shared))
+    scope[key] = shared[key];
+
+  Services.scriptloader.loadSubScript(uri, scope);
 
   for (let name of LAZY_OBJECTS) {
     delete gGlobalScope[name];
     gGlobalScope[name] = scope[name];
   }
   gLazyObjectsLoaded = true;
   return scope;
 }
 
-for (let name of LAZY_OBJECTS) {
+LAZY_OBJECTS.forEach(name => {
   Object.defineProperty(gGlobalScope, name, {
     get: function lazyObjectGetter() {
       let objs = loadLazyObjects();
       return objs[name];
     },
     configurable: true
   });
-}
+});
 
 
 function findMatchingStaticBlocklistItem(aAddon) {
   for (let item of STATIC_BLOCKLIST_PATTERNS) {
     if ("creator" in item && typeof item.creator == "string") {
       if ((aAddon.defaultLocale && aAddon.defaultLocale.creator == item.creator) ||
           (aAddon.selectedLocale && aAddon.selectedLocale.creator == item.creator)) {
         return item;
@@ -1952,31 +1967,29 @@ this.XPIStates = {
    */
   getInstallState() {
     let oldState = this.loadExtensionState();
     let changed = false;
     this.db = new SerializableMap();
 
     for (let location of XPIProvider.installLocations) {
       // The list of add-on like file/directory names in the install location.
-      let addons = location.addonLocations;
+      let addons = location.getAddonLocations();
       // The results of scanning this location.
       let foundAddons = new SerializableMap();
 
       // What our old state thinks should be in this location.
       let locState = {};
       if (location.name in oldState) {
         locState = oldState[location.name];
         // We've seen this location.
         delete oldState[location.name];
       }
 
-      for (let file of addons) {
-        let id = location.getIDForLocation(file);
-
+      for (let [id, file] of addons) {
         if (!(id in locState)) {
           logger.debug("New add-on ${id} in ${location}", {id: id, location: location.name});
           let xpiState = new XPIState({d: file.persistentDescriptor});
           changed = xpiState.getModTime(file, id) || changed;
           foundAddons.set(id, xpiState);
         } else {
           let xpiState = new XPIState(locState[id]);
           // We found this add-on in the file system
@@ -1986,16 +1999,19 @@ this.XPIStates = {
 
           if (file.persistentDescriptor != xpiState.descriptor) {
             xpiState.descriptor = file.persistentDescriptor;
             changed = true;
           }
           if (changed) {
             logger.debug("Changed add-on ${id} in ${location}", {id: id, location: location.name});
           }
+          else {
+            logger.debug("Existing add-on ${id} in ${location}", {id: id, location: location.name});
+          }
           foundAddons.set(id, xpiState);
         }
         XPIProvider.setTelemetry(id, "location", location.name);
       }
 
       // Anything left behind in oldState was removed from the file system.
       for (let id in locState) {
         changed = true;
@@ -2295,27 +2311,50 @@ this.XPIProvider = {
       }
       catch (e) {
         // Some directories aren't defined on some platforms, ignore them
         logger.debug("Skipping unavailable install location " + aName);
         return;
       }
 
       try {
-        var location = new DirectoryInstallLocation(aName, dir, aScope, aLocked);
+        var location = aLocked ? new DirectoryInstallLocation(aName, dir, aScope)
+                               : new MutableDirectoryInstallLocation(aName, dir, aScope);
       }
       catch (e) {
         logger.warn("Failed to add directory install location " + aName, e);
         return;
       }
 
       XPIProvider.installLocations.push(location);
       XPIProvider.installLocationsByName[location.name] = location;
     }
 
+    function addSystemAddonInstallLocation(aName, aKey, aPaths, aScope) {
+      try {
+        var dir = FileUtils.getDir(aKey, aPaths);
+      }
+      catch (e) {
+        // Some directories aren't defined on some platforms, ignore them
+        logger.debug("Skipping unavailable install location " + aName);
+        return;
+      }
+
+      try {
+        var location = new SystemAddonInstallLocation(aName, dir, aScope, aAppChanged !== false);
+      }
+      catch (e) {
+        logger.warn("Failed to add system add-on install location " + aName, e);
+        return;
+      }
+
+      XPIProvider.installLocations.push(location);
+      XPIProvider.installLocationsByName[location.name] = location;
+    }
+
     function addRegistryInstallLocation(aName, aRootkey, aScope) {
       try {
         var location = new WinRegInstallLocation(aName, aRootkey, aScope);
       }
       catch (e) {
         logger.warn("Failed to add registry install location " + aName, e);
         return;
       }
@@ -2348,16 +2387,24 @@ this.XPIProvider = {
 
       // These must be in order of priority, highest to lowest,
       // for processFileChanges etc. to work
       // The profile location is always enabled
       addDirectoryInstallLocation(KEY_APP_PROFILE, KEY_PROFILEDIR,
                                   [DIR_EXTENSIONS],
                                   AddonManager.SCOPE_PROFILE, false);
 
+      addSystemAddonInstallLocation(KEY_APP_SYSTEM_ADDONS, KEY_PROFILEDIR,
+                                    [DIR_SYSTEM_ADDONS],
+                                    AddonManager.SCOPE_PROFILE);
+
+      addDirectoryInstallLocation(KEY_APP_SYSTEM_DEFAULTS, KEY_APP_DISTRIBUTION,
+                                  [DIR_SYSTEM_ADDONS],
+                                  AddonManager.SCOPE_PROFILE, true);
+
       if (enabledScopes & AddonManager.SCOPE_USER) {
         addDirectoryInstallLocation(KEY_APP_SYSTEM_USER, "XREUSysExt",
                                     [Services.appinfo.ID],
                                     AddonManager.SCOPE_USER, true);
         if (hasRegistry) {
           addRegistryInstallLocation("winreg-app-user",
                                      Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
                                      AddonManager.SCOPE_USER);
@@ -2925,19 +2972,19 @@ this.XPIProvider = {
         seenFiles.push(jsonfile.leafName);
 
         existingAddonID = addon.existingAddonID || id;
 
         var oldBootstrap = null;
         logger.debug("Processing install of " + id + " in " + aLocation.name);
         if (existingAddonID in this.bootstrappedAddons) {
           try {
-            var existingAddon = aLocation.getLocationForID(existingAddonID);
-            if (this.bootstrappedAddons[existingAddonID].descriptor ==
-                existingAddon.persistentDescriptor) {
+            var existingAddon = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+            existingAddon.persistentDescriptor = this.bootstrappedAddons[existingAddonID].descriptor;
+            if (existingAddon.exists()) {
               oldBootstrap = this.bootstrappedAddons[existingAddonID];
 
               // We'll be replacing a currently active bootstrapped add-on so
               // call its uninstall method
               let newVersion = addon.version;
               let oldVersion = oldBootstrap.version;
               let uninstallReason = Services.vc.compare(oldVersion, newVersion) < 0 ?
                                     BOOTSTRAP_REASONS.ADDON_UPGRADE :
@@ -3084,17 +3131,17 @@ this.XPIProvider = {
         }
       }
       else if (Preferences.get(PREF_BRANCH_INSTALLED_ADDON + id, false)) {
         continue;
       }
 
       // Install the add-on
       try {
-        profileLocation.installAddon(id, entry, null, true);
+        addon._sourceBundle = profileLocation.installAddon(id, entry, null, true);
         logger.debug("Installed distribution add-on " + id);
 
         Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true)
 
         // aManifests may contain a copy of a newly installed add-on's manifest
         // and we'll have overwritten that so instead cache our install manifest
         // which will later be put into the database in processFileChanges
         if (!(KEY_APP_PROFILE in aManifests))
@@ -3108,691 +3155,16 @@ this.XPIProvider = {
     }
 
     entries.close();
 
     return changed;
   },
 
   /**
-   * Compares the add-ons that are currently installed to those that were
-   * known to be installed when the application last ran and applies any
-   * changes found to the database. Also sends "startupcache-invalidate" signal to
-   * observerservice if it detects that data may have changed.
-   * Always called after XPIProviderUtils.js and extensions.json have been loaded.
-   *
-   * @param  aManifests
-   *         A dictionary of cached AddonInstalls for add-ons that have been
-   *         installed
-   * @param  aUpdateCompatibility
-   *         true to update add-ons appDisabled property when the application
-   *         version has changed
-   * @param  aOldAppVersion
-   *         The version of the application last run with this profile or null
-   *         if it is a new profile or the version is unknown
-   * @param  aOldPlatformVersion
-   *         The version of the platform last run with this profile or null
-   *         if it is a new profile or the version is unknown
-   * @return a boolean indicating if a change requiring flushing the caches was
-   *         detected
-   */
-  processFileChanges: function XPI_processFileChanges(aManifests,
-                                                      aUpdateCompatibility,
-                                                      aOldAppVersion,
-                                                      aOldPlatformVersion) {
-    let visibleAddons = {};
-    let oldBootstrappedAddons = this.bootstrappedAddons;
-    this.bootstrappedAddons = {};
-
-    /**
-     * Updates an add-on's metadata and determines if a restart of the
-     * application is necessary. This is called when either the add-on's
-     * install directory path or last modified time has changed.
-     *
-     * @param  aInstallLocation
-     *         The install location containing the add-on
-     * @param  aOldAddon
-     *         The AddonInternal as it appeared the last time the application
-     *         ran
-     * @param  aAddonState
-     *         The new state of the add-on
-     * @return a boolean indicating if flushing caches is required to complete
-     *         changing this add-on
-     */
-    function updateMetadata(aInstallLocation, aOldAddon, aAddonState) {
-      logger.debug("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name);
-
-      // Check if there is an updated install manifest for this add-on
-      let newAddon = aManifests[aInstallLocation.name][aOldAddon.id];
-
-      try {
-        // If not load it
-        if (!newAddon) {
-          let file = aInstallLocation.getLocationForID(aOldAddon.id);
-          newAddon = syncLoadManifestFromFile(file);
-          applyBlocklistChanges(aOldAddon, newAddon);
-
-          // Carry over any pendingUninstall state to add-ons modified directly
-          // in the profile. This is important when the attempt to remove the
-          // add-on in processPendingFileChanges failed and caused an mtime
-          // change to the add-ons files.
-          newAddon.pendingUninstall = aOldAddon.pendingUninstall;
-        }
-
-        // The ID in the manifest that was loaded must match the ID of the old
-        // add-on.
-        if (newAddon.id != aOldAddon.id)
-          throw new Error("Incorrect id in install manifest for existing add-on " + aOldAddon.id);
-      }
-      catch (e) {
-        logger.warn("updateMetadata: Add-on " + aOldAddon.id + " is invalid", e);
-        XPIDatabase.removeAddonMetadata(aOldAddon);
-        XPIStates.removeAddon(aOldAddon.location, aOldAddon.id);
-        if (!aInstallLocation.locked)
-          aInstallLocation.uninstallAddon(aOldAddon.id);
-        else
-          logger.warn("Could not uninstall invalid item from locked install location");
-        // If this was an active add-on then we must force a restart
-        if (aOldAddon.active)
-          return true;
-
-        return false;
-      }
-
-      // Set the additional properties on the new AddonInternal
-      newAddon._installLocation = aInstallLocation;
-      newAddon.updateDate = aAddonState.mtime;
-      newAddon.visible = !(newAddon.id in visibleAddons);
-
-      // Update the database
-      let newDBAddon = XPIDatabase.updateAddonMetadata(aOldAddon, newAddon,
-                                                       aAddonState.descriptor);
-      if (newDBAddon.visible) {
-        visibleAddons[newDBAddon.id] = newDBAddon;
-        // Remember add-ons that were changed during startup
-        AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
-                                             newDBAddon.id);
-        if (aOldAddon.active == newDBAddon.disabled) {
-          let change = aOldAddon.active ? AddonManager.STARTUP_CHANGE_DISABLED
-                                        : AddonManager.STARTUP_CHANGE_ENABLED;
-          AddonManagerPrivate.addStartupChange(change, newDBAddon.id);
-        }
-
-        // If this was the active theme and it is now disabled then enable the
-        // default theme
-        if (aOldAddon.active && newDBAddon.disabled)
-          XPIProvider.enableDefaultTheme();
-
-        // If the new add-on is bootstrapped and active then call its install method
-        if (newDBAddon.active && newDBAddon.bootstrap) {
-          // Startup cache must be flushed before calling the bootstrap script
-          flushStartupCache();
-
-          let installReason = Services.vc.compare(aOldAddon.version, newDBAddon.version) < 0 ?
-                              BOOTSTRAP_REASONS.ADDON_UPGRADE :
-                              BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
-
-          let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
-          file.persistentDescriptor = aAddonState.descriptor;
-          XPIProvider.callBootstrapMethod(newDBAddon, file, "install",
-                                          installReason, { oldVersion: aOldAddon.version });
-          return false;
-        }
-
-        return true;
-      }
-
-      return false;
-    }
-
-    /**
-     * Updates an add-on's descriptor for when the add-on has moved in the
-     * filesystem but hasn't changed in any other way.
-     *
-     * @param  aInstallLocation
-     *         The install location containing the add-on
-     * @param  aOldAddon
-     *         The AddonInternal as it appeared the last time the application
-     *         ran
-     * @param  aAddonState
-     *         The new state of the add-on
-     * @return a boolean indicating if flushing caches is required to complete
-     *         changing this add-on
-     */
-    function updateDescriptor(aInstallLocation, aOldAddon, aAddonState) {
-      logger.debug("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor);
-
-      aOldAddon.descriptor = aAddonState.descriptor;
-      aOldAddon.visible = !(aOldAddon.id in visibleAddons);
-      XPIDatabase.saveChanges();
-
-      if (aOldAddon.visible) {
-        visibleAddons[aOldAddon.id] = aOldAddon;
-
-        if (aOldAddon.bootstrap && aOldAddon.active) {
-          let bootstrap = oldBootstrappedAddons[aOldAddon.id];
-          bootstrap.descriptor = aAddonState.descriptor;
-          XPIProvider.bootstrappedAddons[aOldAddon.id] = bootstrap;
-        }
-
-        return true;
-      }
-
-      return false;
-    }
-
-    /**
-     * Called when no change has been detected for an add-on's metadata. The
-     * add-on may have become visible due to other add-ons being removed or
-     * the add-on may need to be updated when the application version has
-     * changed.
-     *
-     * @param  aInstallLocation
-     *         The install location containing the add-on
-     * @param  aOldAddon
-     *         The AddonInternal as it appeared the last time the application
-     *         ran
-     * @param  aAddonState
-     *         The new state of the add-on
-     * @return a boolean indicating if flushing caches is required to complete
-     *         changing this add-on
-     */
-    function updateVisibilityAndCompatibility(aInstallLocation, aOldAddon,
-                                              aAddonState) {
-      let changed = false;
-
-      // This add-ons metadata has not changed but it may have become visible
-      if (!(aOldAddon.id in visibleAddons)) {
-        visibleAddons[aOldAddon.id] = aOldAddon;
-
-        if (!aOldAddon.visible) {
-          // Remember add-ons that were changed during startup.
-          AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
-                                               aOldAddon.id);
-          XPIDatabase.makeAddonVisible(aOldAddon);
-
-          if (aOldAddon.bootstrap) {
-            // The add-on is bootstrappable so call its install script
-            let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
-            file.persistentDescriptor = aAddonState.descriptor;
-            XPIProvider.callBootstrapMethod(aOldAddon, file,
-                                            "install",
-                                            BOOTSTRAP_REASONS.ADDON_INSTALL);
-
-            // If it should be active then mark it as active otherwise unload
-            // its scope
-            if (!aOldAddon.disabled) {
-              XPIDatabase.updateAddonActive(aOldAddon, true);
-            }
-            else {
-              XPIProvider.unloadBootstrapScope(newAddon.id);
-            }
-          }
-          else {
-            // Otherwise a restart is necessary
-            changed = true;
-          }
-        }
-      }
-
-      // App version changed, we may need to update the appDisabled property.
-      if (aUpdateCompatibility) {
-        let wasDisabled = aOldAddon.disabled;
-        let wasAppDisabled = aOldAddon.appDisabled;
-        let wasUserDisabled = aOldAddon.userDisabled;
-        let wasSoftDisabled = aOldAddon.softDisabled;
-        let updateDB = false;
-
-        // If updating from a version of the app that didn't support signedState
-        // then fetch that property now
-        if (aOldAddon.signedState === undefined && ADDON_SIGNING &&
-            SIGNED_TYPES.has(aOldAddon.type)) {
-          let file = aInstallLocation.getLocationForID(aOldAddon.id);
-          let manifest = syncLoadManifestFromFile(file);
-          aOldAddon.signedState = manifest.signedState;
-          updateDB = true;
-        }
-        // This updates the addon's JSON cached data in place
-        applyBlocklistChanges(aOldAddon, aOldAddon, aOldAppVersion,
-                              aOldPlatformVersion);
-        aOldAddon.appDisabled = !isUsableAddon(aOldAddon);
-
-        let isDisabled = aOldAddon.disabled;
-
-        // If either property has changed update the database.
-        if (updateDB || wasAppDisabled != aOldAddon.appDisabled ||
-            wasUserDisabled != aOldAddon.userDisabled ||
-            wasSoftDisabled != aOldAddon.softDisabled) {
-          logger.debug("Add-on " + aOldAddon.id + " changed appDisabled state to " +
-              aOldAddon.appDisabled + ", userDisabled state to " +
-              aOldAddon.userDisabled + " and softDisabled state to " +
-              aOldAddon.softDisabled);
-          XPIDatabase.saveChanges();
-        }
-
-        // If this is a visible add-on and it has changed disabled state then we
-        // may need a restart or to update the bootstrap list.
-        if (aOldAddon.visible && wasDisabled != isDisabled) {
-          // Remember add-ons that became disabled or enabled by the application
-          // change
-          let change = isDisabled ? AddonManager.STARTUP_CHANGE_DISABLED
-                                  : AddonManager.STARTUP_CHANGE_ENABLED;
-          AddonManagerPrivate.addStartupChange(change, aOldAddon.id);
-          if (aOldAddon.bootstrap) {
-            // Update the add-ons active state
-            XPIDatabase.updateAddonActive(aOldAddon, !isDisabled);
-          }
-          else {
-            changed = true;
-          }
-        }
-      }
-
-      if (aOldAddon.visible && aOldAddon.active && aOldAddon.bootstrap) {
-        XPIProvider.bootstrappedAddons[aOldAddon.id] = {
-          version: aOldAddon.version,
-          type: aOldAddon.type,
-          descriptor: aAddonState.descriptor,
-          multiprocessCompatible: aOldAddon.multiprocessCompatible
-        };
-      }
-
-      return changed;
-    }
-
-    /**
-     * Called when an add-on has been removed.
-     *
-     * @param  aOldAddon
-     *         The AddonInternal as it appeared the last time the application
-     *         ran
-     * @return a boolean indicating if flushing caches is required to complete
-     *         changing this add-on
-     */
-    function removeMetadata(aOldAddon) {
-      // This add-on has disappeared
-      logger.debug("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location);
-      XPIDatabase.removeAddonMetadata(aOldAddon);
-
-      // Remember add-ons that were uninstalled during startup
-      if (aOldAddon.visible) {
-        AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED,
-                                             aOldAddon.id);
-      }
-      else if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
-                           .indexOf(aOldAddon.id) != -1) {
-        AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
-                                             aOldAddon.id);
-      }
-
-      if (aOldAddon.active) {
-        // Enable the default theme if the previously active theme has been
-        // removed
-        if (aOldAddon.type == "theme")
-          XPIProvider.enableDefaultTheme();
-
-        return true;
-      }
-
-      return false;
-    }
-
-    /**
-     * Called to add the metadata for an add-on in one of the install locations
-     * to the database. This can be called in three different cases. Either an
-     * add-on has been dropped into the location from outside of Firefox, or
-     * an add-on has been installed through the application, or the database
-     * has been upgraded or become corrupt and add-on data has to be reloaded
-     * into it.
-     *
-     * @param  aInstallLocation
-     *         The install location containing the add-on
-     * @param  aId
-     *         The ID of the add-on
-     * @param  aAddonState
-     *         The new state of the add-on
-     * @param  aMigrateData
-     *         If during startup the database had to be upgraded this will
-     *         contain data that used to be held about this add-on
-     * @return a boolean indicating if flushing caches is required to complete
-     *         changing this add-on
-     */
-    function addMetadata(aInstallLocation, aId, aAddonState, aMigrateData) {
-      logger.debug("New add-on " + aId + " installed in " + aInstallLocation.name);
-
-      let newAddon = null;
-      let sameVersion = false;
-      // Check the updated manifests lists for the install location, If there
-      // is no manifest for the add-on ID then newAddon will be undefined
-      if (aInstallLocation.name in aManifests)
-        newAddon = aManifests[aInstallLocation.name][aId];
-
-      // If we had staged data for this add-on or we aren't recovering from a
-      // corrupt database and we don't have migration data for this add-on then
-      // this must be a new install.
-      let isNewInstall = (!!newAddon) || (!XPIDatabase.activeBundles && !aMigrateData);
-
-      // If it's a new install and we haven't yet loaded the manifest then it
-      // must be something dropped directly into the install location
-      let isDetectedInstall = isNewInstall && !newAddon;
-
-      // Load the manifest if necessary and sanity check the add-on ID
-      try {
-        if (!newAddon) {
-          // Load the manifest from the add-on.
-          let file = aInstallLocation.getLocationForID(aId);
-          newAddon = syncLoadManifestFromFile(file);
-        }
-        // The add-on in the manifest should match the add-on ID.
-        if (newAddon.id != aId) {
-          throw new Error("Invalid addon ID: expected addon ID " + aId +
-                          ", found " + newAddon.id + " in manifest");
-        }
-      }
-      catch (e) {
-        logger.warn("addMetadata: Add-on " + aId + " is invalid", e);
-
-        // Remove the invalid add-on from the install location if the install
-        // location isn't locked, no restart will be necessary
-        if (!aInstallLocation.locked)
-          aInstallLocation.uninstallAddon(aId);
-        else
-          logger.warn("Could not uninstall invalid item from locked install location");
-        return false;
-      }
-
-      // Update the AddonInternal properties.
-      newAddon._installLocation = aInstallLocation;
-      newAddon.visible = !(newAddon.id in visibleAddons);
-      newAddon.installDate = aAddonState.mtime;
-      newAddon.updateDate = aAddonState.mtime;
-      newAddon.foreignInstall = isDetectedInstall;
-
-      // appDisabled depends on whether the add-on is a foreignInstall so update
-      newAddon.appDisabled = !isUsableAddon(newAddon);
-
-      if (aMigrateData) {
-        // If there is migration data then apply it.
-        logger.debug("Migrating data from old database");
-
-        DB_MIGRATE_METADATA.forEach(function(aProp) {
-          // A theme's disabled state is determined by the selected theme
-          // preference which is read in loadManifestFromRDF
-          if (aProp == "userDisabled" && newAddon.type == "theme")
-            return;
-
-          if (aProp in aMigrateData)
-            newAddon[aProp] = aMigrateData[aProp];
-        });
-
-        // Force all non-profile add-ons to be foreignInstalls since they can't
-        // have been installed through the API
-        newAddon.foreignInstall |= aInstallLocation.name != KEY_APP_PROFILE;
-
-        // Some properties should only be migrated if the add-on hasn't changed.
-        // The version property isn't a perfect check for this but covers the
-        // vast majority of cases.
-        if (aMigrateData.version == newAddon.version) {
-          logger.debug("Migrating compatibility info");
-          sameVersion = true;
-          if ("targetApplications" in aMigrateData)
-            newAddon.applyCompatibilityUpdate(aMigrateData, true);
-        }
-
-        // Since the DB schema has changed make sure softDisabled is correct
-        applyBlocklistChanges(newAddon, newAddon, aOldAppVersion,
-                              aOldPlatformVersion);
-      }
-
-      // The default theme is never a foreign install
-      if (newAddon.type == "theme" && newAddon.internalName == XPIProvider.defaultSkin)
-        newAddon.foreignInstall = false;
-
-      if (isDetectedInstall && newAddon.foreignInstall) {
-        // If the add-on is a foreign install and is in a scope where add-ons
-        // that were dropped in should default to disabled then disable it
-        let disablingScopes = Preferences.get(PREF_EM_AUTO_DISABLED_SCOPES, 0);
-        if (aInstallLocation.scope & disablingScopes) {
-          logger.warn("Disabling foreign installed add-on " + newAddon.id + " in "
-              + aInstallLocation.name);
-          newAddon.userDisabled = true;
-        }
-      }
-
-      // If we have a list of what add-ons should be marked as active then use
-      // it to guess at migration data.
-      if (!isNewInstall && XPIDatabase.activeBundles) {
-        // For themes we know which is active by the current skin setting
-        if (newAddon.type == "theme")
-          newAddon.active = newAddon.internalName == XPIProvider.currentSkin;
-        else
-          newAddon.active = XPIDatabase.activeBundles.indexOf(aAddonState.descriptor) != -1;
-
-        // If the add-on wasn't active and it isn't already disabled in some way
-        // then it was probably either softDisabled or userDisabled
-        if (!newAddon.active && newAddon.visible && !newAddon.disabled) {
-          // If the add-on is softblocked then assume it is softDisabled
-          if (newAddon.blocklistState == Blocklist.STATE_SOFTBLOCKED)
-            newAddon.softDisabled = true;
-          else
-            newAddon.userDisabled = true;
-        }
-      }
-      else {
-        newAddon.active = (newAddon.visible && !newAddon.disabled);
-      }
-
-      let newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor);
-
-      if (newDBAddon.visible) {
-        // Remember add-ons that were first detected during startup.
-        if (isDetectedInstall) {
-          // If a copy from a higher priority location was removed then this
-          // add-on has changed
-          if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_UNINSTALLED)
-                          .indexOf(newDBAddon.id) != -1) {
-            AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
-                                                 newDBAddon.id);
-          }
-          else {
-            AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED,
-                                                 newDBAddon.id);
-          }
-        }
-
-        // Note if any visible add-on is not in the application install location
-        if (newDBAddon._installLocation.name != KEY_APP_GLOBAL)
-          XPIProvider.allAppGlobal = false;
-
-        visibleAddons[newDBAddon.id] = newDBAddon;
-
-        let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
-        let extraParams = {};
-
-        // Copy add-on details (enabled, bootstrap, version, etc) to XPIState.
-        aAddonState.syncWithDB(newDBAddon);
-
-        // If we're hiding a bootstrapped add-on then call its uninstall method
-        if (newDBAddon.id in oldBootstrappedAddons) {
-          let oldBootstrap = oldBootstrappedAddons[newDBAddon.id];
-          extraParams.oldVersion = oldBootstrap.version;
-          XPIProvider.bootstrappedAddons[newDBAddon.id] = oldBootstrap;
-
-          // If the old version is the same as the new version, or we're
-          // recovering from a corrupt DB, don't call uninstall and install
-          // methods.
-          if (sameVersion || !isNewInstall) {
-            logger.debug("addMetadata: early return, sameVersion " + sameVersion
-                + ", isNewInstall " + isNewInstall);
-            return false;
-          }
-
-          installReason = Services.vc.compare(oldBootstrap.version, newDBAddon.version) < 0 ?
-                          BOOTSTRAP_REASONS.ADDON_UPGRADE :
-                          BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
-
-          let oldAddonFile = Cc["@mozilla.org/file/local;1"].
-                             createInstance(Ci.nsIFile);
-          oldAddonFile.persistentDescriptor = oldBootstrap.descriptor;
-
-          XPIProvider.callBootstrapMethod(createAddonDetails(newDBAddon.id, oldBootstrap),
-                                          oldAddonFile, "uninstall", installReason,
-                                          { newVersion: newDBAddon.version });
-
-          XPIProvider.unloadBootstrapScope(newDBAddon.id);
-
-          // If the new add-on is bootstrapped then we must flush the caches
-          // before calling the new bootstrap script
-          if (newDBAddon.bootstrap)
-            flushStartupCache();
-        }
-
-        if (!newDBAddon.bootstrap)
-          return true;
-
-        // Visible bootstrapped add-ons need to have their install method called
-        let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
-        file.persistentDescriptor = aAddonState.descriptor;
-        XPIProvider.callBootstrapMethod(newDBAddon, file,
-                                        "install", installReason, extraParams);
-        if (!newDBAddon.active)
-          XPIProvider.unloadBootstrapScope(newDBAddon.id);
-      }
-
-      return false;
-    }
-
-    let changed = false;
-
-    // Get all the add-ons in the existing DB and Map them into Sets by install location
-    let allDBAddons = new Map();
-    for (let a of XPIDatabase.getAddons()) {
-      let locationSet = allDBAddons.get(a.location);
-      if (!locationSet) {
-        locationSet = new Set();
-        allDBAddons.set(a.location, locationSet);
-      }
-      locationSet.add(a);
-    }
-
-    for (let installLocation of this.installLocations) {
-      // Get all the on-disk XPI states for this location, and keep track of which
-      // ones we see in the database.
-      let states = XPIStates.getLocation(installLocation.name);
-      let seen = new Set();
-      // Iterate through the add-ons installed the last time the application
-      // ran
-      let dbAddons = allDBAddons.get(installLocation.name);
-      if (dbAddons) {
-        // we've processed this location
-        allDBAddons.delete(installLocation.name);
-
-        logger.debug("processFileChanges reconciling DB for location ${l} state ${s} db ${d}",
-            {l: installLocation.name, s: states, d: [for (a of dbAddons) a.id]});
-        for (let aOldAddon of dbAddons) {
-          // If a version of this add-on has been installed in an higher
-          // priority install location then count it as changed
-          if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
-                          .indexOf(aOldAddon.id) != -1) {
-            AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
-                                                 aOldAddon.id);
-          }
-
-          // Check if the add-on is still installed
-          let xpiState = states && states.get(aOldAddon.id);
-          if (xpiState) {
-            // in this block, the add-on is in both XPIStates and the DB
-            seen.add(xpiState);
-
-            recordAddonTelemetry(aOldAddon);
-
-            // Check if the add-on has been changed outside the XPI provider
-            if (aOldAddon.updateDate != xpiState.mtime) {
-              // Did time change in the wrong direction?
-              if (xpiState.mtime < aOldAddon.updateDate) {
-                this.setTelemetry(aOldAddon.id, "olderFile", {
-                  name: this._mostRecentlyModifiedFile[aOldAddon.id],
-                  mtime: xpiState.mtime,
-                  oldtime: aOldAddon.updateDate
-                });
-              } else {
-                  this.setTelemetry(aOldAddon.id, "modifiedFile",
-                                    this._mostRecentlyModifiedFile[aOldAddon.id]);
-              }
-            }
-
-            // The add-on has changed if the modification time has changed, or
-            // we have an updated manifest for it. Also reload the metadata for
-            // add-ons in the application directory when the application version
-            // has changed
-            if (aOldAddon.id in aManifests[installLocation.name] ||
-                aOldAddon.updateDate != xpiState.mtime ||
-                (aUpdateCompatibility && installLocation.name == KEY_APP_GLOBAL)) {
-              changed = updateMetadata(installLocation, aOldAddon, xpiState) ||
-                        changed;
-            }
-            else if (aOldAddon.descriptor != xpiState.descriptor) {
-              changed = updateDescriptor(installLocation, aOldAddon, xpiState) ||
-                        changed;
-            }
-            else {
-              changed = updateVisibilityAndCompatibility(installLocation,
-                                                         aOldAddon, xpiState) ||
-                        changed;
-            }
-            if (aOldAddon.visible && aOldAddon._installLocation.name != KEY_APP_GLOBAL)
-              XPIProvider.allAppGlobal = false;
-            // Copy add-on details (enabled, bootstrap, version, etc) to XPIState.
-            xpiState.syncWithDB(aOldAddon);
-          }
-          else {
-            // The add-on is in the DB, but not in xpiState (and thus not on disk).
-            changed = removeMetadata(aOldAddon) || changed;
-          }
-        }
-      }
-
-      // Any add-on in our current location that we haven't seen needs to
-      // be added to the database.
-      // Get the migration data for this install location so we can include that as
-      // we add, in case this is a database upgrade or rebuild.
-      let locMigrateData = {};
-      if (XPIDatabase.migrateData && installLocation.name in XPIDatabase.migrateData)
-        locMigrateData = XPIDatabase.migrateData[installLocation.name];
-      if (states) {
-        for (let [id, xpiState] of states) {
-          if (!seen.has(xpiState)) {
-            changed = addMetadata(installLocation, id, xpiState,
-                                  (locMigrateData[id] || null)) || changed;
-          }
-        }
-      }
-    }
-
-    // Anything left in allDBAddons is a location where the database contains add-ons,
-    // but the browser is no longer configured to use that location. The metadata for those
-    // add-ons must be removed from the database.
-    for (let [locationName, addons] of allDBAddons) {
-      logger.debug("Removing orphaned DB add-on entries from " + locationName);
-      for (let a of addons) {
-        logger.debug("Remove ${location}:${id}", a);
-        changed = removeMetadata(a) || changed;
-      }
-    }
-
-    XPIStates.save();
-    this.persistBootstrappedAddons();
-
-    // Clear out any cached migration data.
-    XPIDatabase.migrateData = null;
-
-    return changed;
-  },
-
-  /**
    * Imports the xpinstall permissions from preferences into the permissions
    * manager for the user to change later.
    */
   importPermissions: function XPI_importPermissions() {
     PermissionsUtils.importFromPrefs(PREF_XPI_PERMISSIONS_BRANCH,
                                      XPI_PERMISSION);
   },
 
@@ -3916,20 +3288,20 @@ this.XPIProvider = {
     try {
       let extensionListChanged = false;
       // If the database needs to be updated then open it and then update it
       // from the filesystem
       if (updateReasons.length > 0) {
         AddonManagerPrivate.recordSimpleMeasure("XPIDB_startup_load_reasons", updateReasons);
         XPIDatabase.syncLoadDB(false);
         try {
-          extensionListChanged = this.processFileChanges(manifests,
-                                                         aAppChanged,
-                                                         aOldAppVersion,
-                                                         aOldPlatformVersion);
+          extensionListChanged = XPIDatabaseReconcile.processFileChanges(manifests,
+                                                                         aAppChanged,
+                                                                         aOldAppVersion,
+                                                                         aOldPlatformVersion);
         }
         catch (e) {
           logger.error("Failed to process extension changes at startup", e);
         }
       }
 
       if (aAppChanged) {
         // When upgrading the app and using a custom skin make sure it is still
@@ -4896,27 +4268,25 @@ this.XPIProvider = {
         AddonManagerPrivate.callAddonListeners("onEnabling", wrapper,
                                                needsRestart);
       }
 
       if (!needsRestart) {
         XPIDatabase.updateAddonActive(aAddon, !isDisabled);
         if (isDisabled) {
           if (aAddon.bootstrap) {
-            let file = aAddon._installLocation.getLocationForID(aAddon.id);
-            this.callBootstrapMethod(aAddon, file, "shutdown",
+            this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
                                      BOOTSTRAP_REASONS.ADDON_DISABLE);
             this.unloadBootstrapScope(aAddon.id);
           }
           AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
         }
         else {
           if (aAddon.bootstrap) {
-            let file = aAddon._installLocation.getLocationForID(aAddon.id);
-            this.callBootstrapMethod(aAddon, file, "startup",
+            this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup",
                                      BOOTSTRAP_REASONS.ADDON_ENABLE);
           }
           AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
         }
       }
     }
 
     // Sync with XPIStates.
@@ -5000,22 +4370,21 @@ this.XPIProvider = {
       let wrappedAddon = createWrapper(aAddon);
       AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false);
 
       if (!aAddon.disabled && !XPIProvider.enableRequiresRestart(aAddon)) {
         XPIDatabase.updateAddonActive(aAddon, true);
       }
 
       if (aAddon.bootstrap) {
-        let file = aAddon._installLocation.getLocationForID(aAddon.id);
-        XPIProvider.callBootstrapMethod(aAddon, file,
+        XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle,
                                         "install", BOOTSTRAP_REASONS.ADDON_INSTALL);
 
         if (aAddon.active) {
-          XPIProvider.callBootstrapMethod(aAddon, file,
+          XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle,
                                           "startup", BOOTSTRAP_REASONS.ADDON_INSTALL);
         }
         else {
           XPIProvider.unloadBootstrapScope(aAddon.id);
         }
       }
 
       // We always send onInstalled even if a restart is required to enable
@@ -5027,23 +4396,22 @@ this.XPIProvider = {
       let [locationName, ] = XPIStates.findAddon(aId);
       if (locationName) {
         XPIDatabase.getAddonInLocation(aId, locationName, revealAddon);
       }
     }
 
     if (!requiresRestart) {
       if (aAddon.bootstrap) {
-        let file = aAddon._installLocation.getLocationForID(aAddon.id);
         if (aAddon.active) {
-          this.callBootstrapMethod(aAddon, file, "shutdown",
+          this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
                                    BOOTSTRAP_REASONS.ADDON_UNINSTALL);
         }
 
-        this.callBootstrapMethod(aAddon, file, "uninstall",
+        this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "uninstall",
                                  BOOTSTRAP_REASONS.ADDON_UNINSTALL);
         this.unloadBootstrapScope(aAddon.id);
         flushStartupCache();
       }
       aAddon._installLocation.uninstallAddon(aAddon.id);
       XPIDatabase.removeAddonMetadata(aAddon);
       XPIStates.removeAddon(aAddon.location, aAddon.id);
       AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
@@ -6107,18 +5475,17 @@ AddonInstall.prototype = {
         let reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
         if (this.existingAddon) {
           if (Services.vc.compare(this.existingAddon.version, this.addon.version) < 0)
             reason = BOOTSTRAP_REASONS.ADDON_UPGRADE;
           else
             reason = BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
 
           if (this.existingAddon.bootstrap) {
-            let file = this.existingAddon._installLocation
-                           .getLocationForID(this.existingAddon.id);
+            let file = this.existingAddon._sourceBundle;
             if (this.existingAddon.active) {
               XPIProvider.callBootstrapMethod(this.existingAddon, file,
                                               "shutdown", reason,
                                               { newVersion: this.addon.version });
             }
 
             XPIProvider.callBootstrapMethod(this.existingAddon, file,
                                             "uninstall", reason,
@@ -7415,55 +6782,43 @@ function AddonWrapper(aAddon) {
  * location consists of a directory which contains the add-ons installed in the
  * location.
  *
  * Each add-on installed in the location is either a directory containing the
  * add-on's files or a text file containing an absolute path to the directory
  * containing the add-ons files. The directory or text file must have the same
  * name as the add-on's ID.
  *
- * There may also a special directory named "staged" which can contain
- * directories with the same name as an add-on ID. If the directory is empty
- * then it means the add-on will be uninstalled from this location during the
- * next startup. If the directory contains the add-on's files then they will be
- * installed during the next startup.
- *
  * @param  aName
  *         The string identifier for the install location
  * @param  aDirectory
  *         The nsIFile directory for the install location
  * @param  aScope
  *         The scope of add-ons installed in this location
- * @param  aLocked
- *         true if add-ons cannot be installed, uninstalled or upgraded in the
- *         install location
  */
-function DirectoryInstallLocation(aName, aDirectory, aScope, aLocked) {
+function DirectoryInstallLocation(aName, aDirectory, aScope) {
   this._name = aName;
-  this.locked = aLocked;
+  this.locked = true;
   this._directory = aDirectory;
   this._scope = aScope
   this._IDToFileMap = {};
-  this._FileToIDMap = {};
   this._linkedAddons = [];
-  this._stagingDirLock = 0;
-
-  if (!aDirectory.exists())
+
+  if (!aDirectory || !aDirectory.exists())
     return;
   if (!aDirectory.isDirectory())
     throw new Error("Location must be a directory.");
 
   this._readAddons();
 }
 
 DirectoryInstallLocation.prototype = {
   _name       : "",
   _directory   : null,
   _IDToFileMap : null,  // mapping from add-on ID to nsIFile
-  _FileToIDMap : null,  // mapping from add-on path to add-on ID
 
   /**
    * Reads a directory linked to in a file.
    *
    * @param   file
    *          The file containing the directory path
    * @return  An nsIFile object representing the linked directory.
    */
@@ -7546,17 +6901,16 @@ DirectoryInstallLocation.prototype = {
           continue;
         }
 
         entry = newEntry;
         this._linkedAddons.push(id);
       }
 
       this._IDToFileMap[id] = entry;
-      this._FileToIDMap[entry.path] = id;
       XPIProvider._addURIMapping(id, entry);
     }
   },
 
   /**
    * Gets the name of this install location.
    */
   get name() {
@@ -7568,25 +6922,70 @@ DirectoryInstallLocation.prototype = {
    */
   get scope() {
     return this._scope;
   },
 
   /**
    * Gets an array of nsIFiles for add-ons installed in this location.
    */
-  get addonLocations() {
-    let locations = [];
+  getAddonLocations: function() {
+    let locations = new Map();
     for (let id in this._IDToFileMap) {
-      locations.push(this._IDToFileMap[id].clone());
+      locations.set(id, this._IDToFileMap[id].clone());
     }
     return locations;
   },
 
   /**
+   * Gets the directory that the add-on with the given ID is installed in.
+   *
+   * @param  aId
+   *         The ID of the add-on
+   * @return The nsIFile
+   * @throws if the ID does not match any of the add-ons installed
+   */
+  getLocationForID: function DirInstallLocation_getLocationForID(aId) {
+    if (aId in this._IDToFileMap)
+      return this._IDToFileMap[aId].clone();
+    throw new Error("Unknown add-on ID " + aId);
+  },
+
+  /**
+   * Returns true if the given addon was installed in this location by a text
+   * file pointing to its real path.
+   *
+   * @param aId
+   *        The ID of the addon
+   */
+  isLinkedAddon: function DirInstallLocation__isLinkedAddon(aId) {
+    return this._linkedAddons.indexOf(aId) != -1;
+  }
+};
+
+/**
+ * An extension of DirectoryInstallLocation which adds methods to installing
+ * and removing add-ons from the directory at runtime.
+ *
+ * @param  aName
+ *         The string identifier for the install location
+ * @param  aDirectory
+ *         The nsIFile directory for the install location
+ * @param  aScope
+ *         The scope of add-ons installed in this location
+ */
+function MutableDirectoryInstallLocation(aName, aDirectory, aScope) {
+  DirectoryInstallLocation.call(this, aName, aDirectory, aScope);
+  this.locked = false;
+  this._stagingDirLock = 0;
+}
+
+MutableDirectoryInstallLocation.prototype = Object.create(DirectoryInstallLocation.prototype);
+Object.assign(MutableDirectoryInstallLocation.prototype, {
+  /**
    * Gets the staging directory to put add-ons that are pending install and
    * uninstall into.
    *
    * @return an nsIFile
    */
   getStagingDir: function DirInstallLocation_getStagingDir() {
     let dir = this._directory.clone();
     dir.append(DIR_STAGE);
@@ -7776,23 +7175,21 @@ DirectoryInstallLocation.prototype = {
 
     let newFile = this._directory.clone();
     newFile.append(aSource.leafName);
     try {
       newFile.lastModifiedTime = Date.now();
     } catch (e)  {
       logger.warn("failed to set lastModifiedTime on " + newFile.path, e);
     }
-    this._FileToIDMap[newFile.path] = aId;
     this._IDToFileMap[aId] = newFile;
     XPIProvider._addURIMapping(aId, newFile);
 
     if (aExistingAddonID && aExistingAddonID != aId &&
         aExistingAddonID in this._IDToFileMap) {
-      delete this._FileToIDMap[this._IDToFileMap[aExistingAddonID]];
       delete this._IDToFileMap[aExistingAddonID];
     }
 
     return newFile;
   },
 
   /**
    * Uninstalls an add-on from this location.
@@ -7813,17 +7210,16 @@ DirectoryInstallLocation.prototype = {
     file.append(aId);
     if (!file.exists())
       file.leafName += ".xpi";
 
     if (!file.exists()) {
       logger.warn("Attempted to remove " + aId + " from " +
            this._name + " but it was already gone");
 
-      delete this._FileToIDMap[file.path];
       delete this._IDToFileMap[aId];
       return;
     }
 
     let trashDir = this.getTrashDir();
 
     if (file.leafName != aId) {
       logger.debug("uninstallAddon: flushing jar cache " + file.path + " for addon " + aId);
@@ -7841,59 +7237,141 @@ DirectoryInstallLocation.prototype = {
       try {
         recursiveRemove(trashDir);
       }
       catch (e) {
         logger.warn("Failed to remove trash directory when uninstalling " + aId, e);
       }
     }
 
-    delete this._FileToIDMap[file.path];
     delete this._IDToFileMap[aId];
   },
-
+});
+
+/**
+ * An object which identifies a directory install location for system add-ons.
+ * The location consists of a directory which contains the add-ons installed in
+ * the location.
+ *
+ * @param  aName
+ *         The string identifier for the install location
+ * @param  aDirectory
+ *         The nsIFile directory for the install location
+ * @param  aScope
+ *         The scope of add-ons installed in this location
+ * @param  aResetSet
+ *         True to throw away the current add-on set
+ */
+function SystemAddonInstallLocation(aName, aDirectory, aScope, aResetSet) {
+  this._baseDir = aDirectory;
+
+  if (aResetSet) {
+    this._addonSet = { schema: 1, addons: {} };
+    this._saveAddonSet(this._addonSet);
+  }
+  else {
+    this._addonSet = this._loadAddonSet();
+  }
+
+  this._directory = null;
+  if (this._addonSet.directory) {
+    this._directory = aDirectory.clone();
+    this._directory.append(this._addonSet.directory);
+    logger.info("SystemAddonInstallLocation scanning directory " + this._directory.path);
+  }
+  else {
+    logger.info("SystemAddonInstallLocation directory is missing");
+  }
+
+  DirectoryInstallLocation.call(this, aName, this._directory, aScope);
+  this.locked = true;
+}
+
+SystemAddonInstallLocation.prototype = Object.create(DirectoryInstallLocation.prototype);
+Object.assign(SystemAddonInstallLocation.prototype, {
   /**
-   * Gets the ID of the add-on installed in the given nsIFile.
-   *
-   * @param  aFile
-   *         The nsIFile to look in
-   * @return the ID
-   * @throws if the file does not represent an installed add-on
+   * Reads the current set of system add-ons
    */
-  getIDForLocation: function DirInstallLocation_getIDForLocation(aFile) {
-    if (aFile.path in this._FileToIDMap)
-      return this._FileToIDMap[aFile.path];
-    throw new Error("Unknown add-on location " + aFile.path);
+  _loadAddonSet: function() {
+    try {
+      let setStr = Preferences.get(PREF_SYSTEM_ADDON_SET, null);
+      if (setStr) {
+        let addonSet = JSON.parse(setStr);
+        if ((typeof addonSet == "object") && addonSet.schema == 1)
+          return addonSet;
+      }
+    }
+    catch (e) {
+      logger.error("Malformed system add-on set, resetting.");
+    }
+
+    return { schema: 1, addons: {} };
   },
 
   /**
-   * Gets the directory that the add-on with the given ID is installed in.
-   *
-   * @param  aId
-   *         The ID of the add-on
-   * @return The nsIFile
-   * @throws if the ID does not match any of the add-ons installed
+   * Saves the current set of system add-ons
    */
-  getLocationForID: function DirInstallLocation_getLocationForID(aId) {
-    if (aId in this._IDToFileMap)
-      return this._IDToFileMap[aId].clone();
-    throw new Error("Unknown add-on ID " + aId);
+  _saveAddonSet: function(aAddonSet) {
+    Preferences.set(PREF_SYSTEM_ADDON_SET, JSON.stringify(aAddonSet));
+  },
+
+  getAddonLocations: function() {
+    let addons = DirectoryInstallLocation.prototype.getAddonLocations.call(this);
+
+    // Strip out any unexpected add-ons from the list
+    for (let id of addons.keys()) {
+      if (!(id in this._addonSet.addons))
+        addons.delete(id);
+    }
+
+    return addons;
+  },
+
+  /**
+   * Tests whether updated system add-ons are expected.
+   */
+  isActive: function() {
+    return this._directory != null;
   },
 
   /**
-   * Returns true if the given addon was installed in this location by a text
-   * file pointing to its real path.
-   *
-   * @param aId
-   *        The ID of the addon
+   * Tests whether the loaded add-on information matches what is expected.
    */
-  isLinkedAddon: function DirInstallLocation__isLinkedAddon(aId) {
-    return this._linkedAddons.indexOf(aId) != -1;
-  }
-};
+  isValid: function(aAddons) {
+    for (let id of Object.keys(this._addonSet.addons)) {
+      if (!aAddons.has(id)) {
+        logger.warn("Expected add-on " + id + " is missing from the system add-on location.");
+        return false;
+      }
+
+      let addon = aAddons.get(id);
+      if (addon.appDisabled) {
+        logger.warn("System add-on " + id + " isn't compatible with the application.");
+        return false;
+      }
+
+      if (addon.unpack) {
+        logger.warn("System add-on " + id + " isn't a packed add-on.");
+        return false;
+      }
+
+      if (!addon.bootstrap) {
+        logger.warn("System add-on " + id + " isn't restartless.");
+        return false;
+      }
+
+      if (addon.version != this._addonSet.addons[id].version) {
+        logger.warn("System add-on " + id + " wasn't the correct version.");
+        return false;
+      }
+    }
+
+    return true;
+  },
+});
 
 #ifdef XP_WIN
 /**
  * An object that identifies a registry install location for add-ons. The location
  * consists of a registry key which contains string values mapping ID to the
  * path where an add-on is installed
  *
  * @param  aName
@@ -7904,17 +7382,16 @@ DirectoryInstallLocation.prototype = {
  *         The scope of add-ons installed in this location
  */
 function WinRegInstallLocation(aName, aRootKey, aScope) {
   this.locked = true;
   this._name = aName;
   this._rootKey = aRootKey;
   this._scope = aScope;
   this._IDToFileMap = {};
-  this._FileToIDMap = {};
 
   let path = this._appKeyPath + "\\Extensions";
   let key = Cc["@mozilla.org/windows-registry-key;1"].
             createInstance(Ci.nsIWindowsRegKey);
 
   // Reading the registry may throw an exception, and that's ok.  In error
   // cases, we just leave ourselves in the empty state.
   try {
@@ -7928,17 +7405,16 @@ function WinRegInstallLocation(aName, aR
   key.close();
 }
 
 WinRegInstallLocation.prototype = {
   _name       : "",
   _rootKey    : null,
   _scope      : null,
   _IDToFileMap : null,  // mapping from ID to nsIFile
-  _FileToIDMap : null,  // mapping from path to ID
 
   /**
    * Retrieves the path of this Application's data key in the registry.
    */
   get _appKeyPath() {
     let appVendor = Services.appinfo.vendor;
     let appName = Services.appinfo.name;
 
@@ -7972,17 +7448,16 @@ WinRegInstallLocation.prototype = {
       file.initWithPath(aKey.readStringValue(id));
 
       if (!file.exists()) {
         logger.warn("Ignoring missing add-on in " + file.path);
         continue;
       }
 
       this._IDToFileMap[id] = file;
-      this._FileToIDMap[file.path] = id;
       XPIProvider._addURIMapping(id, file);
     }
   },
 
   /**
    * Gets the name of this install location.
    */
   get name() {
@@ -7994,52 +7469,25 @@ WinRegInstallLocation.prototype = {
    */
   get scope() {
     return this._scope;
   },
 
   /**
    * Gets an array of nsIFiles for add-ons installed in this location.
    */
-  get addonLocations() {
-    let locations = [];
+  getAddonLocations: function() {
+    let locations = new Map();
     for (let id in this._IDToFileMap) {
-      locations.push(this._IDToFileMap[id].clone());
+      locations.set(id, this._IDToFileMap[id].clone());
     }
     return locations;
   },
 
   /**
-   * Gets the ID of the add-on installed in the given nsIFile.
-   *
-   * @param  aFile
-   *         The nsIFile to look in
-   * @return the ID
-   * @throws if the file does not represent an installed add-on
-   */
-  getIDForLocation: function RegInstallLocation_getIDForLocation(aFile) {
-    if (aFile.path in this._FileToIDMap)
-      return this._FileToIDMap[aFile.path];
-    throw new Error("Unknown add-on location");
-  },
-
-  /**
-   * Gets the nsIFile that the add-on with the given ID is installed in.
-   *
-   * @param  aId
-   *         The ID of the add-on
-   * @return the nsIFile
-   */
-  getLocationForID: function RegInstallLocation_getLocationForID(aId) {
-    if (aId in this._IDToFileMap)
-      return this._IDToFileMap[aId].clone();
-    throw new Error("Unknown add-on ID");
-  },
-
-  /**
    * @see DirectoryInstallLocation
    */
   isLinkedAddon: function RegInstallLocation_isLinkedAddon(aId) {
     return true;
   }
 };
 #endif
 
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -7,27 +7,31 @@
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
                                   "resource://gre/modules/addons/AddonRepository.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave",
                                   "resource://gre/modules/DeferredSave.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "Blocklist",
+                                   "@mozilla.org/extensions/blocklist;1",
+                                   Ci.nsIBlocklistService);
 
 Cu.import("resource://gre/modules/Log.jsm");
 const LOGGER_ID = "addons.xpi-utils";
 
 // Create a new logger for use by the Addons XPI Provider Utils
 // (Requires AddonManager.jsm)
 let logger = Log.repository.getLogger(LOGGER_ID);
 
@@ -41,16 +45,22 @@ const FILE_XPI_ADDONS_LIST            = 
 #expand const DB_SCHEMA               = __MOZ_EXTENSIONS_DB_SCHEMA__;
 
 // The last version of DB_SCHEMA implemented in SQLITE
 const LAST_SQLITE_DB_SCHEMA           = 14;
 const PREF_DB_SCHEMA                  = "extensions.databaseSchema";
 const PREF_PENDING_OPERATIONS         = "extensions.pendingOperations";
 const PREF_EM_ENABLED_ADDONS          = "extensions.enabledAddons";
 const PREF_EM_DSS_ENABLED             = "extensions.dss.enabled";
+const PREF_EM_AUTO_DISABLED_SCOPES    = "extensions.autoDisableScopes";
+
+const KEY_APP_PROFILE                 = "app-profile";
+const KEY_APP_SYSTEM_ADDONS           = "app-system-addons";
+const KEY_APP_SYSTEM_DEFAULTS         = "app-system-defaults";
+const KEY_APP_GLOBAL                  = "app-global";
 
 // Properties that only exist in the database
 const DB_METADATA        = ["syncGUID",
                             "installDate",
                             "updateDate",
                             "size",
                             "sourceURI",
                             "releaseNotesURI",
@@ -67,16 +77,23 @@ const PROP_JSON_FIELDS = ["id", "syncGUI
                           "defaultLocale", "visible", "active", "userDisabled",
                           "appDisabled", "pendingUninstall", "descriptor", "installDate",
                           "updateDate", "applyBackgroundUpdates", "bootstrap",
                           "skinnable", "size", "sourceURI", "releaseNotesURI",
                           "softDisabled", "foreignInstall", "hasBinaryComponents",
                           "strictCompatibility", "locales", "targetApplications",
                           "targetPlatforms", "multiprocessCompatible", "signedState"];
 
+// Properties that should be migrated where possible from an old database. These
+// shouldn't include properties that can be read directly from install.rdf files
+// or calculated
+const DB_MIGRATE_METADATA= ["installDate", "userDisabled", "softDisabled",
+                            "sourceURI", "applyBackgroundUpdates",
+                            "releaseNotesURI", "foreignInstall", "syncGUID"];
+
 // Time to wait before async save of XPI JSON database, in milliseconds
 const ASYNC_SAVE_DELAY_MS = 20;
 
 const PREFIX_ITEM_URI                 = "urn:mozilla:item:";
 const RDFURI_ITEM_ROOT                = "urn:mozilla:item:root"
 const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
 
 XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1",
@@ -309,23 +326,25 @@ function DBAddonInternal(aLoaded) {
     this.location = aLoaded._installLocation._name;
   }
   else if (aLoaded.location) {
     this._installLocation = XPIProvider.installLocationsByName[this.location];
   }
 
   this._key = this.location + ":" + this.id;
 
-  try {
-    this._sourceBundle = this._installLocation.getLocationForID(this.id);
+  if (aLoaded._sourceBundle) {
+    this._sourceBundle = aLoaded._sourceBundle;
   }
-  catch (e) {
-    // An exception will be thrown if the add-on appears in the database but
-    // not on disk. In general this should only happen during startup as
-    // this change is being detected.
+  else if (aLoaded.descriptor) {
+    this._sourceBundle = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+    this._sourceBundle.persistentDescriptor = aLoaded.descriptor;
+  }
+  else {
+    throw new Error("Expected passed argument to contain a descriptor");
   }
 
   XPCOMUtils.defineLazyGetter(this, "pendingUpgrade",
     function DBA_pendingUpgradeGetter() {
       for (let install of XPIProvider.installs) {
         if (install.state == AddonManager.STATE_INSTALLED &&
             !(install.addon.inDatabase) &&
             install.addon.id == this.id &&
@@ -780,17 +799,17 @@ this.XPIDatabase = {
     // If there is no migration data then load the list of add-on directories
     // that were active during the last run
     if (!this.migrateData)
       this.activeBundles = this.getActiveBundles();
 
     if (aRebuildOnError) {
       logger.warn("Rebuilding add-ons database from installed extensions.");
       try {
-        XPIProvider.processFileChanges({}, false);
+        XPIDatabaseReconcile.processFileChanges({}, false);
       }
       catch (e) {
         logger.error("Failed to rebuild XPI database from installed extensions", e);
       }
       // Make sure to update the active add-ons and add-ons list on shutdown
       Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
     }
   },
@@ -1079,17 +1098,17 @@ this.XPIDatabase = {
    */
   getAddon: function(aFilter, aCallback) {
     return this.asyncLoadDB().then(
       addonDB => {
         getRepositoryAddon(_findAddon(addonDB, aFilter), makeSafe(aCallback));
       })
     .then(null,
         error => {
-          logger.error("getAddon failed", e);
+          logger.error("getAddon failed", error);
           makeSafe(aCallback)(null);
         });
   },
 
   /**
    * Asynchronously gets an add-on with a particular ID in a particular
    * install location.
    *
@@ -1296,16 +1315,17 @@ this.XPIDatabase = {
    *         The DBAddonInternal to make visible
    */
   makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) {
     logger.debug("Make addon " + aAddon._key + " visible");
     for (let [, otherAddon] of this.addonDB) {
       if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) {
         logger.debug("Hide addon " + otherAddon._key);
         otherAddon.visible = false;
+        otherAddon.active = false;
       }
     }
     aAddon.visible = true;
     this.saveChanges();
   },
 
   /**
    * Synchronously sets properties for an add-on.
@@ -1483,8 +1503,643 @@ this.XPIDatabase = {
         }
       }
 
       Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS);
     }
     return true;
   }
 };
+
+this.XPIDatabaseReconcile = {
+  /**
+   * Returns a map of ID -> add-on. When the same add-on ID exists in multiple
+   * install locations the highest priority location is chosen.
+   */
+  flattenByID(addonMap, hideLocation) {
+    let map = new Map();
+
+    for (let installLocation of XPIProvider.installLocations) {
+      if (installLocation.name == hideLocation)
+        continue;
+
+      let locationMap = addonMap.get(installLocation.name);
+      if (!locationMap)
+        continue;
+
+      for (let [id, addon] of locationMap) {
+        if (!map.has(id))
+          map.set(id, addon);
+      }
+    }
+
+    return map;
+  },
+
+  /**
+   * Finds the visible add-ons from the map.
+   */
+  getVisibleAddons(addonMap) {
+    let map = new Map();
+
+    for (let [location, addons] of addonMap) {
+      for (let [id, addon] of addons) {
+        if (!addon.visible)
+          continue;
+
+        if (map.has(id)) {
+          logger.warn("Previous database listed more than one visible add-on with id " + id);
+          continue;
+        }
+
+        map.set(id, addon);
+      }
+    }
+
+    return map;
+  },
+
+  /**
+   * Called to add the metadata for an add-on in one of the install locations
+   * to the database. This can be called in three different cases. Either an
+   * add-on has been dropped into the location from outside of Firefox, or
+   * an add-on has been installed through the application, or the database
+   * has been upgraded or become corrupt and add-on data has to be reloaded
+   * into it.
+   *
+   * @param  aInstallLocation
+   *         The install location containing the add-on
+   * @param  aId
+   *         The ID of the add-on
+   * @param  aAddonState
+   *         The new state of the add-on
+   * @param  aNewAddon
+   *         The manifest for the new add-on if it has already been loaded
+   * @param  aOldAppVersion
+   *         The version of the application last run with this profile or null
+   *         if it is a new profile or the version is unknown
+   * @param  aOldPlatformVersion
+   *         The version of the platform last run with this profile or null
+   *         if it is a new profile or the version is unknown
+   * @param  aMigrateData
+   *         If during startup the database had to be upgraded this will
+   *         contain data that used to be held about this add-on
+   * @return a boolean indicating if flushing caches is required to complete
+   *         changing this add-on
+   */
+  addMetadata(aInstallLocation, aId, aAddonState, aNewAddon, aOldAppVersion,
+              aOldPlatformVersion, aMigrateData) {
+    logger.debug("New add-on " + aId + " installed in " + aInstallLocation.name);
+
+    // If we had staged data for this add-on or we aren't recovering from a
+    // corrupt database and we don't have migration data for this add-on then
+    // this must be a new install.
+    let isNewInstall = (!!aNewAddon) || (!XPIDatabase.activeBundles && !aMigrateData);
+
+    // If it's a new install and we haven't yet loaded the manifest then it
+    // must be something dropped directly into the install location
+    let isDetectedInstall = isNewInstall && !aNewAddon;
+
+    // Load the manifest if necessary and sanity check the add-on ID
+    try {
+      if (!aNewAddon) {
+        // Load the manifest from the add-on.
+        let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+        file.persistentDescriptor = aAddonState.descriptor;
+        aNewAddon = syncLoadManifestFromFile(file);
+      }
+      // The add-on in the manifest should match the add-on ID.
+      if (aNewAddon.id != aId) {
+        throw new Error("Invalid addon ID: expected addon ID " + aId +
+                        ", found " + aNewAddon.id + " in manifest");
+      }
+    }
+    catch (e) {
+      logger.warn("addMetadata: Add-on " + aId + " is invalid", e);
+
+      // Remove the invalid add-on from the install location if the install
+      // location isn't locked, no restart will be necessary
+      if (!aInstallLocation.locked)
+        aInstallLocation.uninstallAddon(aId);
+      else
+        logger.warn("Could not uninstall invalid item from locked install location");
+      return null;
+    }
+
+    // Update the AddonInternal properties.
+    aNewAddon._installLocation = aInstallLocation;
+    aNewAddon.installDate = aAddonState.mtime;
+    aNewAddon.updateDate = aAddonState.mtime;
+
+    // Assume that add-ons in the system add-ons install location aren't
+    // foreign and should default to enabled.
+    aNewAddon.foreignInstall = isDetectedInstall &&
+                               aInstallLocation.name != KEY_APP_SYSTEM_ADDONS &&
+                               aInstallLocation.name != KEY_APP_SYSTEM_DEFAULTS;
+
+    // appDisabled depends on whether the add-on is a foreignInstall so update
+    aNewAddon.appDisabled = !isUsableAddon(aNewAddon);
+
+    if (aMigrateData) {
+      // If there is migration data then apply it.
+      logger.debug("Migrating data from old database");
+
+      DB_MIGRATE_METADATA.forEach(function(aProp) {
+        // A theme's disabled state is determined by the selected theme
+        // preference which is read in loadManifestFromRDF
+        if (aProp == "userDisabled" && aNewAddon.type == "theme")
+          return;
+
+        if (aProp in aMigrateData)
+          aNewAddon[aProp] = aMigrateData[aProp];
+      });
+
+      // Force all non-profile add-ons to be foreignInstalls since they can't
+      // have been installed through the API
+      aNewAddon.foreignInstall |= aInstallLocation.name != KEY_APP_PROFILE;
+
+      // Some properties should only be migrated if the add-on hasn't changed.
+      // The version property isn't a perfect check for this but covers the
+      // vast majority of cases.
+      if (aMigrateData.version == aNewAddon.version) {
+        logger.debug("Migrating compatibility info");
+        if ("targetApplications" in aMigrateData)
+          aNewAddon.applyCompatibilityUpdate(aMigrateData, true);
+      }
+
+      // Since the DB schema has changed make sure softDisabled is correct
+      applyBlocklistChanges(aNewAddon, aNewAddon, aOldAppVersion,
+                            aOldPlatformVersion);
+    }
+
+    // The default theme is never a foreign install
+    if (aNewAddon.type == "theme" && aNewAddon.internalName == XPIProvider.defaultSkin)
+      aNewAddon.foreignInstall = false;
+
+    if (isDetectedInstall && aNewAddon.foreignInstall) {
+      // If the add-on is a foreign install and is in a scope where add-ons
+      // that were dropped in should default to disabled then disable it
+      let disablingScopes = Preferences.get(PREF_EM_AUTO_DISABLED_SCOPES, 0);
+      if (aInstallLocation.scope & disablingScopes) {
+        logger.warn("Disabling foreign installed add-on " + aNewAddon.id + " in "
+            + aInstallLocation.name);
+        aNewAddon.userDisabled = true;
+      }
+    }
+
+    return XPIDatabase.addAddonMetadata(aNewAddon, aAddonState.descriptor);
+  },
+
+  /**
+   * Called when an add-on has been removed.
+   *
+   * @param  aOldAddon
+   *         The AddonInternal as it appeared the last time the application
+   *         ran
+   * @return a boolean indicating if flushing caches is required to complete
+   *         changing this add-on
+   */
+  removeMetadata(aOldAddon) {
+    // This add-on has disappeared
+    logger.debug("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location);
+    XPIDatabase.removeAddonMetadata(aOldAddon);
+  },
+
+  /**
+   * Updates an add-on's metadata and determines if a restart of the
+   * application is necessary. This is called when either the add-on's
+   * install directory path or last modified time has changed.
+   *
+   * @param  aInstallLocation
+   *         The install location containing the add-on
+   * @param  aOldAddon
+   *         The AddonInternal as it appeared the last time the application
+   *         ran
+   * @param  aAddonState
+   *         The new state of the add-on
+   * @param  aNewAddon
+   *         The manifest for the new add-on if it has already been loaded
+   * @return a boolean indicating if flushing caches is required to complete
+   *         changing this add-on
+   */
+  updateMetadata(aInstallLocation, aOldAddon, aAddonState, aNewAddon) {
+    logger.debug("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name);
+
+    try {
+      // If there isn't an updated install manifest for this add-on then load it.
+      if (!aNewAddon) {
+        let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+        file.persistentDescriptor = aAddonState.descriptor;
+        aNewAddon = syncLoadManifestFromFile(file);
+        applyBlocklistChanges(aOldAddon, aNewAddon);
+
+        // Carry over any pendingUninstall state to add-ons modified directly
+        // in the profile. This is important when the attempt to remove the
+        // add-on in processPendingFileChanges failed and caused an mtime
+        // change to the add-ons files.
+        aNewAddon.pendingUninstall = aOldAddon.pendingUninstall;
+      }
+
+      // The ID in the manifest that was loaded must match the ID of the old
+      // add-on.
+      if (aNewAddon.id != aOldAddon.id)
+        throw new Error("Incorrect id in install manifest for existing add-on " + aOldAddon.id);
+    }
+    catch (e) {
+      logger.warn("updateMetadata: Add-on " + aOldAddon.id + " is invalid", e);
+      XPIDatabase.removeAddonMetadata(aOldAddon);
+      XPIStates.removeAddon(aOldAddon.location, aOldAddon.id);
+      if (!aInstallLocation.locked)
+        aInstallLocation.uninstallAddon(aOldAddon.id);
+      else
+        logger.warn("Could not uninstall invalid item from locked install location");
+
+      return null;
+    }
+
+    // Set the additional properties on the new AddonInternal
+    aNewAddon._installLocation = aInstallLocation;
+    aNewAddon.updateDate = aAddonState.mtime;
+
+    // Update the database
+    return XPIDatabase.updateAddonMetadata(aOldAddon, aNewAddon, aAddonState.descriptor);
+  },
+
+  /**
+   * Updates an add-on's descriptor for when the add-on has moved in the
+   * filesystem but hasn't changed in any other way.
+   *
+   * @param  aInstallLocation
+   *         The install location containing the add-on
+   * @param  aOldAddon
+   *         The AddonInternal as it appeared the last time the application
+   *         ran
+   * @param  aAddonState
+   *         The new state of the add-on
+   * @return a boolean indicating if flushing caches is required to complete
+   *         changing this add-on
+   */
+  updateDescriptor(aInstallLocation, aOldAddon, aAddonState) {
+    logger.debug("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor);
+    aOldAddon.descriptor = aAddonState.descriptor;
+    aOldAddon._sourceBundle.persistentDescriptor = aAddonState.descriptor;
+
+    return aOldAddon;
+  },
+
+  /**
+   * Called when no change has been detected for an add-on's metadata but the
+   * application has changed so compatibility may have changed.
+   *
+   * @param  aInstallLocation
+   *         The install location containing the add-on
+   * @param  aOldAddon
+   *         The AddonInternal as it appeared the last time the application
+   *         ran
+   * @param  aAddonState
+   *         The new state of the add-on
+   * @param  aOldAppVersion
+   *         The version of the application last run with this profile or null
+   *         if it is a new profile or the version is unknown
+   * @param  aOldPlatformVersion
+   *         The version of the platform last run with this profile or null
+   *         if it is a new profile or the version is unknown
+   * @return a boolean indicating if flushing caches is required to complete
+   *         changing this add-on
+   */
+  updateCompatibility(aInstallLocation, aOldAddon, aAddonState, aOldAppVersion, aOldPlatformVersion) {
+    logger.debug("Updating compatibility for add-on " + aOldAddon.id + " in " + aInstallLocation.name);
+
+    // If updating from a version of the app that didn't support signedState
+    // then fetch that property now
+    if (aOldAddon.signedState === undefined && ADDON_SIGNING &&
+        SIGNED_TYPES.has(aOldAddon.type)) {
+      let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+      file.persistentDescriptor = aAddonState.descriptor;
+      let manifest = syncLoadManifestFromFile(file);
+      aOldAddon.signedState = manifest.signedState;
+    }
+    // This updates the addon's JSON cached data in place
+    applyBlocklistChanges(aOldAddon, aOldAddon, aOldAppVersion,
+                          aOldPlatformVersion);
+    aOldAddon.appDisabled = !isUsableAddon(aOldAddon);
+
+    return aOldAddon;
+  },
+
+  /**
+   * Compares the add-ons that are currently installed to those that were
+   * known to be installed when the application last ran and applies any
+   * changes found to the database. Also sends "startupcache-invalidate" signal to
+   * observerservice if it detects that data may have changed.
+   * Always called after XPIProviderUtils.js and extensions.json have been loaded.
+   *
+   * @param  aManifests
+   *         A dictionary of cached AddonInstalls for add-ons that have been
+   *         installed
+   * @param  aUpdateCompatibility
+   *         true to update add-ons appDisabled property when the application
+   *         version has changed
+   * @param  aOldAppVersion
+   *         The version of the application last run with this profile or null
+   *         if it is a new profile or the version is unknown
+   * @param  aOldPlatformVersion
+   *         The version of the platform last run with this profile or null
+   *         if it is a new profile or the version is unknown
+   * @return a boolean indicating if a change requiring flushing the caches was
+   *         detected
+   */
+  processFileChanges(aManifests, aUpdateCompatibility, aOldAppVersion, aOldPlatformVersion) {
+    let loadedManifest = (aInstallLocation, aId) => {
+      if (!(aInstallLocation.name in aManifests))
+        return null;
+      if (!(aId in aManifests[aInstallLocation.name]))
+        return null;
+      return aManifests[aInstallLocation.name][aId];
+    };
+
+    // Get the previous add-ons from the database and put them into maps by location
+    let previousAddons = new Map();
+    for (let a of XPIDatabase.getAddons()) {
+      let locationAddonMap = previousAddons.get(a.location);
+      if (!locationAddonMap) {
+        locationAddonMap = new Map();
+        previousAddons.set(a.location, locationAddonMap);
+      }
+      locationAddonMap.set(a.id, a);
+    }
+
+    // Build the list of current add-ons into similar maps. When add-ons are still
+    // present we re-use the add-on objects from the database and update their
+    // details directly
+    let currentAddons = new Map();
+    for (let installLocation of XPIProvider.installLocations) {
+      let locationAddonMap = new Map();
+      currentAddons.set(installLocation.name, locationAddonMap);
+
+      // Get all the on-disk XPI states for this location, and keep track of which
+      // ones we see in the database.
+      let states = XPIStates.getLocation(installLocation.name);
+
+      // Iterate through the add-ons installed the last time the application
+      // ran
+      let dbAddons = previousAddons.get(installLocation.name);
+      if (dbAddons) {
+        for (let [id, oldAddon] of dbAddons) {
+          // Check if the add-on is still installed
+          let xpiState = states && states.get(id);
+          if (xpiState) {
+            // Here the add-on was present in the database and on disk
+            recordAddonTelemetry(oldAddon);
+
+            // Check if the add-on has been changed outside the XPI provider
+            if (oldAddon.updateDate != xpiState.mtime) {
+              // Did time change in the wrong direction?
+              if (xpiState.mtime < oldAddon.updateDate) {
+                XPIProvider.setTelemetry(oldAddon.id, "olderFile", {
+                  name: XPIProvider._mostRecentlyModifiedFile[id],
+                  mtime: xpiState.mtime,
+                  oldtime: oldAddon.updateDate
+                });
+              } else {
+                XPIProvider.setTelemetry(oldAddon.id, "modifiedFile",
+                                         XPIProvider._mostRecentlyModifiedFile[id]);
+              }
+            }
+
+            // The add-on has changed if the modification time has changed, or
+            // we have an updated manifest for it. Also reload the metadata for
+            // add-ons in the application directory when the application version
+            // has changed
+            let newAddon = loadedManifest(installLocation, id);
+            if (newAddon || oldAddon.updateDate != xpiState.mtime ||
+                (aUpdateCompatibility && (installLocation.name == KEY_APP_GLOBAL ||
+                                          installLocation.name == KEY_APP_SYSTEM_DEFAULTS))) {
+              newAddon = this.updateMetadata(installLocation, oldAddon, xpiState, newAddon);
+            }
+            else if (oldAddon.descriptor != xpiState.descriptor) {
+              newAddon = this.updateDescriptor(installLocation, oldAddon, xpiState);
+            }
+            else if (aUpdateCompatibility) {
+              newAddon = this.updateCompatibility(installLocation, oldAddon, xpiState,
+                                                  aOldAppVersion, aOldPlatformVersion);
+            }
+            else {
+              // No change
+              newAddon = oldAddon;
+            }
+
+            if (newAddon)
+              locationAddonMap.set(newAddon.id, newAddon);
+          }
+          else {
+            // The add-on is in the DB, but not in xpiState (and thus not on disk).
+            this.removeMetadata(oldAddon);
+          }
+        }
+      }
+
+      // Any add-on in our current location that we haven't seen needs to
+      // be added to the database.
+      // Get the migration data for this install location so we can include that as
+      // we add, in case this is a database upgrade or rebuild.
+      let locMigrateData = {};
+      if (XPIDatabase.migrateData && installLocation.name in XPIDatabase.migrateData)
+        locMigrateData = XPIDatabase.migrateData[installLocation.name];
+
+      if (states) {
+        for (let [id, xpiState] of states) {
+          if (locationAddonMap.has(id))
+            continue;
+          let migrateData = id in locMigrateData ? locMigrateData[id] : null;
+          let newAddon = loadedManifest(installLocation, id);
+          let addon = this.addMetadata(installLocation, id, xpiState, newAddon,
+                                       aOldAppVersion, aOldPlatformVersion, migrateData);
+          if (addon)
+            locationAddonMap.set(addon.id, addon);
+        }
+      }
+    }
+
+    // previousAddons may contain locations where the database contains add-ons
+    // but the browser is no longer configured to use that location. The metadata
+    // for those add-ons must be removed from the database.
+    for (let [locationName, addons] of previousAddons) {
+      if (!currentAddons.has(locationName)) {
+        for (let [id, oldAddon] of addons)
+          this.removeMetadata(oldAddon);
+      }
+    }
+
+    // Validate the updated system add-ons
+    let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS];
+    let addons = currentAddons.get(KEY_APP_SYSTEM_ADDONS) || new Map();
+
+    let hideLocation;
+    if (systemAddonLocation.isActive() && systemAddonLocation.isValid(addons)) {
+      // Hide the system add-on defaults
+      logger.info("Hiding the default system add-ons.");
+      hideLocation = KEY_APP_SYSTEM_DEFAULTS;
+    }
+    else {
+      // Hide the system add-on updates
+      logger.info("Hiding the updated system add-ons.");
+      hideLocation = KEY_APP_SYSTEM_ADDONS;
+    }
+
+    let previousVisible = this.getVisibleAddons(previousAddons);
+    let currentVisible = this.flattenByID(currentAddons, hideLocation);
+    let sawActiveTheme = false;
+    XPIProvider.bootstrappedAddons = {};
+
+    // Pass over the new set of visible add-ons, record any changes that occured
+    // during startup and call bootstrap install/uninstall scripts as necessary
+    for (let [id, currentAddon] of currentVisible) {
+      let previousAddon = previousVisible.get(id);
+
+      // Note if any visible add-on is not in the application install location
+      if (currentAddon._installLocation.name != KEY_APP_GLOBAL)
+        XPIProvider.allAppGlobal = false;
+
+      let isActive = !currentAddon.disabled;
+      let wasActive = previousAddon ? previousAddon.active : currentAddon.active
+
+      if (!previousAddon) {
+        // If we had a manifest for this add-on it was a staged install and
+        // so wasn't something recovered from a corrupt database
+        let wasStaged = !!loadedManifest(currentAddon._installLocation, id);
+
+        // We might be recovering from a corrupt database, if so use the
+        // list of known active add-ons to update the new add-on
+        if (!wasStaged && XPIDatabase.activeBundles) {
+          // For themes we know which is active by the current skin setting
+          if (currentAddon.type == "theme")
+            isActive = currentAddon.internalName == XPIProvider.currentSkin;
+          else
+            isActive = XPIDatabase.activeBundles.indexOf(currentAddon.descriptor) != -1;
+
+          // If the add-on wasn't active and it isn't already disabled in some way
+          // then it was probably either softDisabled or userDisabled
+          if (!isActive && !currentAddon.disabled) {
+            // If the add-on is softblocked then assume it is softDisabled
+            if (currentAddon.blocklistState == Blocklist.STATE_SOFTBLOCKED)
+              currentAddon.softDisabled = true;
+            else
+              currentAddon.userDisabled = true;
+          }
+        }
+        else {
+          // This is a new install
+          if (currentAddon.foreignInstall)
+            AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED, id);
+
+          if (currentAddon.bootstrap) {
+            // Visible bootstrapped add-ons need to have their install method called
+            XPIProvider.callBootstrapMethod(currentAddon, currentAddon._sourceBundle,
+                                            "install", BOOTSTRAP_REASONS.ADDON_INSTALL);
+            if (!isActive)
+              XPIProvider.unloadBootstrapScope(currentAddon.id);
+          }
+        }
+      }
+      else {
+        if (previousAddon !== currentAddon) {
+          // This is an add-on that has changed, either the metadata was reloaded
+          // or the version in a different location has become visible
+          AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, id);
+
+          let installReason = Services.vc.compare(previousAddon.version, currentAddon.version) < 0 ?
+                              BOOTSTRAP_REASONS.ADDON_UPGRADE :
+                              BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
+
+          // If the previous add-on was in a different location, bootstrapped
+          // and still exists then call its uninstall method.
+          if (previousAddon.bootstrap && previousAddon._installLocation &&
+              currentAddon._installLocation != previousAddon._installLocation &&
+              previousAddon._sourceBundle.exists()) {
+
+            XPIProvider.callBootstrapMethod(previousAddon, previousAddon._sourceBundle,
+                                            "uninstall", installReason,
+                                            { newVersion: currentAddon.version });
+            XPIProvider.unloadBootstrapScope(previousAddon.id);
+          }
+
+          // Make sure to flush the cache when an old add-on has gone away
+          flushStartupCache();
+
+          if (currentAddon.bootstrap) {
+            // Visible bootstrapped add-ons need to have their install method called
+            let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+            file.persistentDescriptor = currentAddon._sourceBundle.persistentDescriptor;
+            XPIProvider.callBootstrapMethod(currentAddon, file,
+                                            "install", installReason,
+                                            { oldVersion: previousAddon.version });
+            if (currentAddon.disabled)
+              XPIProvider.unloadBootstrapScope(currentAddon.id);
+          }
+        }
+
+        if (isActive != wasActive) {
+          let change = isActive ? AddonManager.STARTUP_CHANGE_ENABLED
+                                : AddonManager.STARTUP_CHANGE_DISABLED;
+          AddonManagerPrivate.addStartupChange(change, id);
+        }
+      }
+
+      XPIDatabase.makeAddonVisible(currentAddon);
+      currentAddon.active = isActive;
+
+      // Make sure the bootstrap information is up to date for this ID
+      if (currentAddon.bootstrap && currentAddon.active) {
+        XPIProvider.bootstrappedAddons[id] = {
+          version: currentAddon.version,
+          type: currentAddon.type,
+          descriptor: currentAddon._sourceBundle.persistentDescriptor,
+          multiprocessCompatible: currentAddon.multiprocessCompatible
+        };
+      }
+
+      if (currentAddon.active && currentAddon.internalName == XPIProvider.selectedSkin)
+        sawActiveTheme = true;
+    }
+
+    // Pass over the set of previously visible add-ons that have now gone away
+    // and record the change.
+    for (let [id, previousAddon] of previousVisible) {
+      if (currentVisible.has(id))
+        continue;
+
+      // This add-on vanished
+      AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED, id);
+    }
+
+    // Make sure add-ons from hidden locations are marked invisible and inactive
+    let locationAddonMap = currentAddons.get(hideLocation);
+    if (locationAddonMap) {
+      for (let addon of locationAddonMap.values()) {
+        addon.visible = false;
+        addon.active = false;
+      }
+    }
+
+    // None of the active add-ons match the selected theme, enable the default.
+    if (!sawActiveTheme) {
+      XPIProvider.enableDefaultTheme();
+    }
+
+    // Finally update XPIStates to match everything
+    for (let [locationName, locationAddonMap] of currentAddons) {
+      for (let [id, addon] of locationAddonMap) {
+        let xpiState = XPIStates.getAddon(locationName, id);
+        xpiState.syncWithDB(addon);
+      }
+    }
+    XPIStates.save();
+
+    XPIProvider.persistBootstrappedAddons();
+
+    // Clear out any cached migration data.
+    XPIDatabase.migrateData = null;
+    XPIDatabase.saveChanges();
+
+    return true;
+  },
+}
new file mode 100644
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..838b1b6584a825747fd808c9d177f8013598eca7
GIT binary patch
literal 855
zc$^FHW@Zs#U}E54s4nDoKd|sF<6{N}h7F7i3_J`n3`zO<CB-F0i3NID#i1db49t?L
zr$TR~oC+<i;AUWC`36)5)Xl&!b%MV)vm?*Z_rEm{&Dpl)%&pa~0;}5NqBgz=J$84C
z?97=uo@uT}-@nsY@q=AcF8<Twz0-9xzfCb%d1H-T$5+?Q*V+O#io2KlZcfipDz#i-
z68+@hc8zb2i)QMqTD4S#Yc;n8v$FppiRaI|g8nGk{uY>{dT;ho>+<T#y|KC1lx?=F
zr^dW3%DNiN6#q@HWmoa5v;!|%F0sU|x~}h|7*YJcoAJk+tH$q+aJ~BU;K!<tTX%$4
zYV59ilgjIJd{?l+HPP#g@7Yo`-=%#ETz}`Q?$-%h7auRN*~a_ghueqw2kk!<{rWAQ
z9{>-i_7raSDYd!h4S>EcWny691_o4SUU5lcPL5ttN*Xwv*7}_aRgJ<3r`Abl^9~sZ
zw7jq5GN02sd5f(;Yk<nBwbR31)he>-{8ZmGUG2-uY#vU93A3Wp_TQY_8FbES?Th|`
zxF}=aO@*`0XgQrae{@n?=f}$_?Kec`Y|pjMn}7UTZl&2wpNd(ZZPM4>GpIM<kTtHG
zB2ZPcXrsWdW2@h;58-;$WaAsZ<AVEl4bJBpy5fh{&&@G7vS7oe>N#rF>0O&wPdKKm
zanwX&r=VQ(>xz{kyC#%A<(F$cchA7*&)21s<~K0fnXh%aXz}_!dqS#iRmp{r7vGeO
z#5?C~P(SwKQe^X&yH~zAE9fvZ8kI#RJ9NL&KDOxduC#@rV$+W@N?NdN7q!TjQoY{b
zJn1(7rEKT>>mq;84R|&G&|YWn&v(D~9hh3c?R5S1di4)VUv~b8-QZ!T;q>msr1t--
zl<EV#8JXmmaivcQ1`q%;8J0AHSg5I$6_Q%fVgT7h%!oubQ4>g^L@O&KS}_b{Wdmtq
N0>Z^WdL9#q2LQUpQ=k9<
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c346cf3b7d07a9ee7e33bee28f1829bf263a3244
GIT binary patch
literal 856
zc$^FHW@Zs#U}E54Xei`%zr8Nr{0RdC!v;nM1|9|(hNS%blH!u0!~(sn;?NLI24?xx
zQ=u|ITw1}+z{v6qs0^r^fnn+de{W_-o}=%7YaW`jZOfTe?%fJk<f>LCU0mI>In&z6
zI&#vPuCDucBLhD22ifd<a=cnUGT?gfp(RP1+ceBX&PGYQEj%OVKRfOCjgl+RIxgi*
zXnzp+rnw}|XuX!dr%0H10h`-AmyOnz;Zq)6zgXOHG4j#enfr^kpMHJzRgvwX<KI^F
ztj_g|V^-xzeV?=@dU+mOkDrF`gQZn}4~aCHe%~+Aa6ZoVaoCy#df)oicda;UJF#oi
z>*I0P4wS61W=)PQl>LzBFzr!Zn*7&OvG;?%M@Coqv?tfP95-+J&+~8M^vn16S1<>_
zL#j80+r9Ps%QFT*@0T($FmMAyDl@ORBrzvPuP7xA98f#`PK9bkVFXm`q_cU43<O%<
z*KwK8>2<y(*Wsig^z@py_SbzXY&t*HH%(Xj@-mx;Q(?la=(PPe=XM62v0nS4zaTEk
z*jw$f$;|03BJ)p+@(MprKgqZ8q-Fl?vO9MDYj1x#Sz`4x^VsH|omGeb98@Sd^CQsd
z#mCNLF)yUoZ`&WF*}=WZr1qhc{N2SWa~5xj;EK1rnb6^wnETzr``fuE>EU|4?g3qy
z21Uv?Y}Y>qDVFHHz9etMHFs~~j32MPS?w8_Yx1Jn78PFG&wt=n)Rz|vG%vn&P0<sw
zO!V)$ICV4I%iSwqwmU>HF{QrQbgV(_X?TyTc<EWE)jE3J%ti$qdD;aRO}xSyoG0Dp
zzm)BKf8EMIei~o>AKqKI^yj;3_Xl1(SQozjdR@3e@K?+qy$BU~5ykS7gNN%wC)o#h
zGcw6B<4T|s3?KkxGAwBXu~3sMD<rw1#Q?I2m=TF=q85-siB?ufv|<>@$_CQJ1cZx$
J^n4}|4*+j7PjvtQ
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/bootstrap.js
@@ -0,0 +1,18 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const ID = "system1@tests.mozilla.org";
+const VERSION = "1.0";
+
+function install(data, reason) {
+}
+
+function startup(data, reason) {
+  Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION);
+}
+
+function shutdown(data, reason) {
+  Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version");
+}
+
+function uninstall(data, reason) {
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/install.rdf
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>system1@tests.mozilla.org</em:id>
+    <em:version>1.0</em:version>
+    <em:bootstrap>true</em:bootstrap>
+
+    <!-- Front End MetaData -->
+    <em:name>System Add-on 1</em:name>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>xpcshell@tests.mozilla.org</em:id>
+        <em:minVersion>1</em:minVersion>
+        <em:maxVersion>5</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/bootstrap.js
@@ -0,0 +1,18 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const ID = "system2@tests.mozilla.org";
+const VERSION = "1.0";
+
+function install(data, reason) {
+}
+
+function startup(data, reason) {
+  Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION);
+}
+
+function shutdown(data, reason) {
+  Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version");
+}
+
+function uninstall(data, reason) {
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/install.rdf
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>system2@tests.mozilla.org</em:id>
+    <em:version>1.0</em:version>
+    <em:bootstrap>true</em:bootstrap>
+
+    <!-- Front End MetaData -->
+    <em:name>System Add-on 2</em:name>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>xpcshell@tests.mozilla.org</em:id>
+        <em:minVersion>1</em:minVersion>
+        <em:maxVersion>5</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+  </Description>
+</RDF>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f82f607f9c76b85f3bef538b6fd7a5ea9f6cb320
GIT binary patch
literal 858
zc$^FHW@Zs#U}E54Xes1&FFO!=_z438!v;nM1|9|(hNS%blH!u0!~(sn;?NLI24?lt
zQ=!U0Tw1}+z{v6qs0^r^fnn+de{W_-o}=%7YaW`jZOfTe?%fJk<f>LCU0mI>In&z6
zI&#vPuCDucBLhD22ifd<a=cnUGT?gfp(RP1+ceBX&PGYQEj%OVKRfOCjgl+RIxgi*
zXnzp+rnw}|XuX!dr%0H10h`-AmyOnT;Zq)6zgXOHG4j#enfr^kpMHJzRgvu>^=~VB
zR_FS~F{^T<zE4^cy*!Vt$4|re!P2U~heVo8zwei5I3H*GIBd-Vy>ET%yH=dFo!GVM
z_3^lC2TImhvnIzD%6`amnD!_yP5$et*!w}>BcrQ)+LLQtj+;0A=lM5r`sMrkE0_b|
zA=Q<_?OroukG>Jm`=v|_4BWtw%FHV+NzBR7D@sWN2h<k7Q=ukN7y;Ef>1<xJfk4aq
zI<Dz+dK<U3D>zAYKD*|v{q-G-`o<^nDf+EnUcR2;Afy_!)A0A%cRY(CzIi>8&zoyd
zCAhRuNY}fi>wH%4UzgpkB@7uwUc1+I?|y#chOO`O&z8PMvr>;-vwqYk72&^SwkyZ{
zc?oBB?)s?pS$j!JqT@6b{VA&_SBw2DmJk;$kJgs@U}$iAhL~7jc4=MNQzz?$AfcmM
zZWOZLE8P`RFtvMEi937D()<VN?|ub2vL`g1_dTuScPOW3enYR}g9DqEvE2T2AV$R`
z!N29fR#T}DyVreWXAd(>m@(nB)L9Yz{Z2Z2R%JhmUYZywv7nPV?Ym5vukz7p2d6yw
z*4%UJ%J%$i?+z^ev;4!00H^x-)qINUTB0X>UHs3gK|Jw{>-#3wPmY2m?&4SehpYYq
z20J5@95b#YD!~8(KqkYIMi2`%!LmXUELt2On}`{e$R=t5DU^t1g+wfdfvjvGO-w+z
K7)UQ<0`UMbFi!RW
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7fa67926282ea1dc82d7f05d6bf2cfeb132d8508
GIT binary patch
literal 857
zc$^FHW@Zs#U}E54XfNb;zqDFW^$7z5!v;nM1|9|(hNS%blH!u0!~(sn;?NLI24>yV
zQ=wWwTw1}+z{v6qs0^r^fnn+de{W_-o}=%7YaW`jZOa+0dpb=4)i0KIxvn=lx9shS
zHz}S*+FEtx>5fP3JwE=JB>#SIies!l=j5)lQj2Z`m}KWlv>H|0<j#^!z8pAP*=w_-
zT$B4o{!24Y?pbA{8W<6KKvJSo=;*sAF@at2?&lTV(mTu3{++w4x88j9>8~8}w-+0&
z+h$qGJ4Ip2-tLWgb9YG^SS>VZoc`)7cQD7bJO7gy?5e-^L~m@`b;Ej})*`d-E?TG7
z$yY}*U*7mmq^I;~>4DvhkzKoI)?U#rul2s0p8L{Jw(qN;?0t@Z#y{NS{ObQd;tha@
z)Pxjn_pQaQ)rLUtmohOha05dsGq1QLF(*f_C?yRXPzU@@g*rrG1XSy!vw4RM1X|wL
zahcEQb<W^-Q55McdcAaNRlE@Mv_HZ(y@h{$F>PZJm^v#uZU4==ok2QQYhP?Hh>J4z
zS9@#{Ii*EJezhsD@Z;wv<u;zQ%)ecB$M5*Hw?9(MW`8t!9C<8eZ|c7c0jsn>8j4@O
zxFqX+nHzpvex=9}*2r0Q#S8u41qz)Dj9SyOeol_Tkp&wzvd>XxPyf`dJ^h%n#!(ZA
zouYEhuS-@6SWPc|*)P{RZ(q`kAGf_(?U|WtGNalS6<*uVf8bcumlq2(FUq>4tP`?K
z_V2m4bu-(`*(+b}c8FkPN`15IScBNp^d8sgrDvU1XU2%Jr#uk2<M&`w=9D!I?JD2p
zmt1e#7aRO@Z@{bhhxR&qf4=&???7(>zti>C`sp9EzU=(ryussNV2jNKR{sB?o^^}?
z-i%Cg%(zmh1Oo^FnG8!BK`hkt$_h!ZXi<P{B4$h?o2Uh(P~w#p60aBrva*3RF#+LX
KAiaPI!~+26GDbH5
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/bootstrap.js
@@ -0,0 +1,18 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const ID = "system1@tests.mozilla.org";
+const VERSION = "2.0";
+
+function install(data, reason) {
+}
+
+function startup(data, reason) {
+  Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION);
+}
+
+function shutdown(data, reason) {
+  Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version");
+}
+
+function uninstall(data, reason) {
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/install.rdf
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>system1@tests.mozilla.org</em:id>
+    <em:version>2.0</em:version>
+    <em:bootstrap>true</em:bootstrap>
+
+    <!-- Front End MetaData -->
+    <em:name>System Add-on 1</em:name>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>xpcshell@tests.mozilla.org</em:id>
+        <em:minVersion>1</em:minVersion>
+        <em:maxVersion>5</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/bootstrap.js
@@ -0,0 +1,18 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const ID = "system3@tests.mozilla.org";
+const VERSION = "1.0";
+
+function install(data, reason) {
+}
+
+function startup(data, reason) {
+  Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION);
+}
+
+function shutdown(data, reason) {
+  Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version");
+}
+
+function uninstall(data, reason) {
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/install.rdf
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>system3@tests.mozilla.org</em:id>
+    <em:version>1.0</em:version>
+    <em:bootstrap>true</em:bootstrap>
+
+    <!-- Front End MetaData -->
+    <em:name>System Add-on 3</em:name>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>xpcshell@tests.mozilla.org</em:id>
+        <em:minVersion>1</em:minVersion>
+        <em:maxVersion>5</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+  </Description>
+</RDF>
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -1021,17 +1021,17 @@ function getFileForAddon(aDir, aId) {
   var dir = aDir.clone();
   dir.append(do_get_expected_addon_name(aId));
   return dir;
 }
 
 function registerDirectory(aKey, aDir) {
   var dirProvider = {
     getFile: function(aProp, aPersistent) {
-      aPersistent.value = true;
+      aPersistent.value = false;
       if (aProp == aKey)
         return aDir.clone();
       return null;
     },
 
     QueryInterface: XPCOMUtils.generateQI([AM_Ci.nsIDirectoryServiceProvider,
                                            AM_Ci.nsISupports])
   };
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
@@ -1062,19 +1062,18 @@ function run_test_21() {
     do_check_eq(getShutdownReason(), APP_SHUTDOWN);
     do_check_eq(getShutdownNewVersion(), 0);
 
     // This won't be set as the bootstrap script was gone so we couldn't
     // uninstall it properly
     do_check_eq(getUninstallReason(), -1);
     do_check_eq(getUninstallNewVersion(), -1);
 
-    // TODO this reason should probably be ADDON_DOWNGRADE (bug 607818)
-    do_check_eq(getInstallReason(), ADDON_INSTALL);
-    do_check_eq(getInstallOldVersion(), 0);
+    do_check_eq(getInstallReason(), ADDON_DOWNGRADE);
+    do_check_eq(getInstallOldVersion(), 2);
 
     do_check_eq(getStartupReason(), APP_STARTUP);
     do_check_eq(getStartupOldVersion(), 0);
 
     do_check_bootstrappedPref(function() {
       manuallyUninstall(userExtDir, "bootstrap1@tests.mozilla.org");
 
       restartManager();
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
@@ -0,0 +1,200 @@
+// Tests that we reset to the default system add-ons correctly when switching
+// application versions
+const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet";
+
+const featureDir = gProfD.clone();
+featureDir.append("features");
+
+const distroDir = do_get_file("data/system_addons/app0");
+registerDirectory("XREAppDist", distroDir);
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "0");
+
+function makeUUID() {
+  let uuidGen = AM_Cc["@mozilla.org/uuid-generator;1"].
+                getService(AM_Ci.nsIUUIDGenerator);
+  return uuidGen.generateUUID().toString();
+}
+
+function* check_installed(inProfile, ...versions) {
+  let expectedDir;
+  if (inProfile) {
+    expectedDir = featureDir;
+  }
+  else {
+    expectedDir = distroDir.clone();
+    expectedDir.append("features");
+  }
+
+  for (let i = 0; i < versions.length; i++) {
+    let id = "system" + (i + 1) + "@tests.mozilla.org";
+    let addon = yield promiseAddonByID(id);
+
+    if (versions[i]) {
+      // Add-on should be installed
+      do_check_neq(addon, null);
+      do_check_eq(addon.version, versions[i]);
+      do_check_true(addon.isActive);
+      do_check_false(addon.foreignInstall);
+
+      // Verify the add-ons file is in the right place
+      let file = expectedDir.clone();
+      file.append(id + ".xpi");
+      do_check_true(file.exists());
+      do_check_true(file.isFile());
+
+      let uri = addon.getResourceURI(null);
+      do_check_true(uri instanceof AM_Ci.nsIFileURL);
+      do_check_eq(uri.file.path, file.path);
+
+      // Verify the add-on actually started
+      let installed = Services.prefs.getCharPref("bootstraptest." + id + ".active_version");
+      do_check_eq(installed, versions[i]);
+    }
+    else {
+      // Add-on should not be installed
+      do_check_eq(addon, null);
+
+      try {
+        Services.prefs.getCharPref("bootstraptest." + id + ".active_version");
+        do_throw("Expected pref to be missing");
+      }
+      catch (e) {
+      }
+    }
+  }
+}
+
+// Test with a missing features directory
+add_task(function* test_missing_app_dir() {
+  startupManager();
+
+  yield check_installed(false, null, null, null);
+
+  do_check_false(featureDir.exists());
+
+  yield promiseShutdownManager();
+});
+
+// Add some features in a new version
+add_task(function* test_new_version() {
+  gAppInfo.version = "1";
+  distroDir.leafName = "app1";
+  startupManager();
+
+  yield check_installed(false, "1.0", "1.0", null);
+
+  do_check_false(featureDir.exists());
+
+  yield promiseShutdownManager();
+});
+
+// Another new version swaps one feature and upgrades another
+add_task(function* test_upgrade() {
+  gAppInfo.version = "2";
+  distroDir.leafName = "app2";
+  startupManager();
+
+  yield check_installed(false, "2.0", null, "1.0");
+
+  do_check_false(featureDir.exists());
+
+  yield promiseShutdownManager();
+});
+
+// Downgrade
+add_task(function* test_downgrade() {
+  gAppInfo.version = "1";
+  distroDir.leafName = "app1";
+  startupManager();
+
+  yield check_installed(false, "1.0", "1.0", null);
+
+  do_check_false(featureDir.exists());
+
+  yield promiseShutdownManager();
+});
+
+// Fake a mid-cycle install
+add_task(function* test_updated() {
+  // Create a random dir to install into
+  let dirname = makeUUID();
+  FileUtils.getDir("ProfD", ["features", dirname], true);
+  featureDir.append(dirname);
+
+  // Copy in the system add-ons
+  let file = do_get_file("data/system_addons/app1/features/system2@tests.mozilla.org.xpi");
+  file.copyTo(featureDir, file.leafName);
+  file = do_get_file("data/system_addons/app2/features/system3@tests.mozilla.org.xpi");
+  file.copyTo(featureDir, file.leafName);
+
+  // Inject it into the system set
+  let addonSet = {
+    schema: 1,
+    directory: dirname,
+    addons: {
+      "system2@tests.mozilla.org": {
+        version: "1.0"
+      },
+      "system3@tests.mozilla.org": {
+        version: "1.0"
+      },
+    }
+  };
+  Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, JSON.stringify(addonSet));
+
+  startupManager(false);
+
+  yield check_installed(true, null, "1.0", "1.0");
+
+  yield promiseShutdownManager();
+});
+
+// An additional add-on in the directory should be ignored
+add_task(function* test_skips_additional() {
+  // Copy in the system add-ons
+  let file = do_get_file("data/system_addons/app1/features/system1@tests.mozilla.org.xpi");
+  file.copyTo(featureDir, file.leafName);
+
+  startupManager(false);
+
+  yield check_installed(true, null, "1.0", "1.0");
+
+  yield promiseShutdownManager();
+});
+
+// Missing add-on should revert to the default set
+add_task(function* test_revert() {
+  manuallyUninstall(featureDir, "system2@tests.mozilla.org");
+
+  startupManager(false);
+
+  // With system add-on 2 gone the updated set is now invalid so it reverts to
+  // the default set which is system add-ons 1 and 2.
+  yield check_installed(false, "1.0", "1.0", null);
+
+  yield promiseShutdownManager();
+});
+
+// Putting it back will make the set work again
+add_task(function* test_reuse() {
+  let file = do_get_file("data/system_addons/app1/features/system2@tests.mozilla.org.xpi");
+  file.copyTo(featureDir, file.leafName);
+
+  startupManager(false);
+
+  yield check_installed(true, null, "1.0", "1.0");
+
+  yield promiseShutdownManager();
+});
+
+// Making the pref corrupt should revert to the default set
+add_task(function* test_corrupt_pref() {
+  Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, "foo");
+
+  startupManager(false);
+
+  yield check_installed(false, "1.0", "1.0", null);
+
+  yield promiseShutdownManager();
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -19,14 +19,15 @@ skip-if = appname != "firefox"
 [test_isReady.js]
 [test_metadata_update.js]
 [test_pluginInfoURL.js]
 [test_provider_markSafe.js]
 [test_provider_shutdown.js]
 [test_provider_unsafe_access_shutdown.js]
 [test_provider_unsafe_access_startup.js]
 [test_shutdown.js]
+[test_system_reset.js]
 [test_XPIcancel.js]
 [test_XPIStates.js]
 
 
 
 [include:xpcshell-shared.ini]